forked from bai/curriculum-project-hub
feat(hub): external capability registry for PDF/ASR transforms (ADR-0027) (#5)
Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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,
|
||||
baseUrl: payload.baseUrl,
|
||||
apiToken: payload.apiToken,
|
||||
projectId: payload.projectId,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user