Files
curriculum-project-hub/hub/src/capability/capabilityConnections.ts
T
2026-07-18 16:42:42 +08:00

71 lines
2.7 KiB
TypeScript

/**
* ADR-0027: org-scoped capability credential resolver. Reuses the ADR-0024
* envelope decryption machinery (LocalSecretEnvelope) with
* purpose="capability", distinct from the model-provider and Feishu
* application connections. Fail-closed: no process-global fallback.
*
* Mirrors the Feishu application connection resolver shape, minus the
* readiness probe (capability probes are per-capability and injected by the
* adapter wiring, not this resolver).
*/
import type { PrismaClient } from "@prisma/client";
import { LocalSecretEnvelope, type SecretEnvelopeV1 } from "../security/secretEnvelope.js";
import {
CapabilityConnectionUnavailable,
type CapabilitySecretPayload,
type CapabilityId,
} from "./types.js";
const CAPABILITY_PURPOSE = "capability";
export interface ResolvedCapabilityCredential extends CapabilitySecretPayload {
readonly connectionId: string;
readonly organizationId: string;
readonly capabilityId: string;
}
/**
* Resolve the active capability credential for an organization. Throws
* CapabilityConnectionUnavailable when the org has no ACTIVE connection
* (fail-closed, ADR-0024). Decrypted plaintext exists only in the returned
* object for the duration of the capability call; it is never cached, logged,
* or passed to the Agent process.
*/
export async function resolveCapabilityCredential(
prisma: PrismaClient,
secrets: LocalSecretEnvelope,
input: { readonly organizationId: string; readonly capabilityId: CapabilityId },
): Promise<ResolvedCapabilityCredential> {
const connection = await prisma.organizationCapabilityConnection.findFirst({
where: {
organizationId: input.organizationId,
capabilityId: input.capabilityId,
status: "ACTIVE",
},
include: { activeSecretVersion: true },
});
if (connection === null || connection.activeSecretVersion === null) {
throw new CapabilityConnectionUnavailable(input.capabilityId, input.organizationId);
}
const version = connection.activeSecretVersion;
const binding = {
purpose: CAPABILITY_PURPOSE,
organizationId: connection.organizationId,
connectionId: connection.id,
secretVersionId: version.id,
};
const payload = secrets.decryptJson<CapabilitySecretPayload>(binding, version.envelope as unknown as SecretEnvelopeV1);
if (payload.schemaVersion !== 1) {
throw new Error(`unsupported capability secret schemaVersion: ${payload.schemaVersion}`);
}
return {
connectionId: connection.id,
organizationId: connection.organizationId,
capabilityId: connection.capabilityId,
schemaVersion: 1,
accessKeyId: payload.accessKeyId,
accessKeySecret: payload.accessKeySecret,
endpoint: payload.endpoint,
};
}