forked from bai/curriculum-project-hub
64b3d1fc64
Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
71 lines
2.7 KiB
TypeScript
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,
|
|
};
|
|
}
|