/** * Core.invoke:工具调用的唯一出口(架构设计 4 / 术语表规则 3)。 * * 一次调用经过五道关(A4 控制面):身份 → 策略 → (需要确认就先开确认卡)→ 凭据 → 执行 → 审计。 * 三条不变量: * 1. 只有一个出口:引擎不论怎么调工具,都落到这里 * 2. 平台的允许不能覆盖业务系统的拒绝:Provider 回 forbidden 就是 forbidden,不改写成成功、不重试写操作 * 3. 库里与日志里没有参数与凭据明文:参数只留摘要(确认卡用的参数密文单独存),凭据只在执行期经句柄取用 */ import { randomUUID } from 'node:crypto'; import type { ExecutionReceipt } from '@aps/contracts'; import { isErrorKind, ProviderError, type Capability, type ErrorKind, type ExecutionContext, type ExecutionIdentity, type ImpactPreview, type Provider } from '@aps/provider-sdk'; import type { ControlPlane } from '../control-plane/control-plane.js'; import type { AccountBinding } from '../identity/context.js'; import type { CapabilityRegistry } from '../registry/registry.js'; import { digestParams, ExecutionStore, type ExecutionRecord } from './store.js'; export interface InvokeRequest { /** 调用者身份(A4.1 起由身份上下文补齐,现在只需要用户与租户) */ subject: { userId: string; tenantId: string }; sessionId: string; projectId?: string | null; capabilityId: string; params: unknown; idempotencyKey?: string; /** 覆盖默认超时(毫秒) */ timeoutMs?: number; } export interface ApprovalInput { decision: 'approve' | 'reject'; revision: number; idempotencyKey: string; note?: string; } export type ApprovalResult = | { kind: 'ok'; approvalId: string; decision: 'approved' | 'rejected' | 'expired'; receipt?: ExecutionReceipt } | { kind: 'not-found' } | { kind: 'forbidden' }; export interface CoreOptions { registry: CapabilityRegistry; executions?: ExecutionStore; defaultTimeoutMs?: number; /** A4 控制面:不传则退化为「无策略的直通执行」(只用于老用例与本地试验) */ controlPlane?: ControlPlane; } export interface Core { invoke(request: InvokeRequest): Promise; /** 确认卡响应:校验归属与版本 → 签发一次性授权 → 执行 → 落回执 */ approve(subject: { userId: string; tenantId: string }, approvalId: string, input: ApprovalInput): Promise; executions: ExecutionStore; registry: CapabilityRegistry; approvals?: ControlPlane['approvals']; } const DEFAULT_TIMEOUT_MS = 30_000; function failed(executionId: string, errorKind: ErrorKind, message: string): ExecutionReceipt { return { executionId, status: 'failed', errorKind, result: { message } }; } /** 把任意异常归一化成六分类之一(实施设计 5) */ function normalizeError(error: unknown): { kind: ErrorKind; message: string } { if (error instanceof ProviderError) return { kind: error.kind, message: error.message }; if (error instanceof Error) return { kind: 'unknown_result', message: error.message }; return { kind: 'unknown_result', message: 'Provider 抛出未知错误' }; } interface RunInput { executionId: string; capability: Capability; provider: Provider; providerId: string; subject: { userId: string; tenantId: string }; sessionId: string; projectId: string | null; params: unknown; idempotencyKey?: string; timeoutMs: number; binding?: AccountBinding; } export function createCore(options: CoreOptions): Core { const executions = options.executions ?? new ExecutionStore(); const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS; const control = options.controlPlane; /** 真正落到 Provider 的那一步:签发一次性授权 → 调用 → 归一化 → 审计 */ async function run(input: RunInput): Promise { const paramsDigest = digestParams(input.params); const timeoutMs = input.timeoutMs; const context: ExecutionContext = { executionId: input.executionId, sessionId: input.sessionId, projectId: input.projectId, subject: { userId: input.subject.userId, tenantId: input.subject.tenantId }, ...(input.binding === undefined ? {} : { identity: { accountId: input.binding.accountId, displayName: input.binding.displayName } satisfies ExecutionIdentity }), ...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }), deadlineAt: control ? control.now() + timeoutMs : Date.now() + timeoutMs, }; // 凭据:只在这一层解开,句柄随上下文传下去;参数里永远没有它 const grant = control && input.binding && control.credentials.has(input.subject, input.providerId) ? control.credentials.issue({ subject: input.subject, providerId: input.providerId, accountId: input.binding.accountId, executionId: input.executionId, ttlMs: timeoutMs, }) : undefined; if (grant) { context.credentials = { grantId: grant.grantId, read: async () => grant.read() }; } executions.create({ executionId: input.executionId, capabilityId: input.capability.id, providerId: input.providerId, sessionId: input.sessionId, projectId: input.projectId, subjectUserId: input.subject.userId, paramsDigest, ...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }), receipt: { executionId: input.executionId, status: 'accepted' }, }); control?.audit.append({ actorUserId: input.subject.userId, tenantId: input.subject.tenantId, event: 'execution.started', executionId: input.executionId, sessionId: input.sessionId, capabilityId: input.capability.id, providerId: input.providerId, paramsDigest, ...(input.binding === undefined ? {} : { decision: input.binding.accountId }), }); // 超时判定:超时不等于成功也不等于失败——结果未知(实施设计 5) const timeout = new Promise((resolve) => { setTimeout(() => resolve(failed(input.executionId, 'unknown_result', `调用超过 ${timeoutMs}ms,结果未知`)), timeoutMs).unref(); }); let receipt: ExecutionReceipt; try { receipt = await Promise.race([input.provider.invoke(context, { capabilityId: input.capability.id, params: input.params }), timeout]); } catch (error) { const normalized = normalizeError(error); receipt = failed(input.executionId, normalized.kind, normalized.message); } finally { // 一次性授权用完即废:结果未知也不延长 if (grant) control?.credentials.revoke(grant.grantId); } // Provider 返回的分类非法时归一为 unknown_result,避免脏值进记录 if (receipt.status === 'failed' && receipt.errorKind !== undefined && !isErrorKind(receipt.errorKind)) { receipt = failed(input.executionId, 'unknown_result', `Provider 返回了未知错误分类 ${String(receipt.errorKind)}`); } executions.complete(input.executionId, receipt); control?.audit.append({ actorUserId: input.subject.userId, tenantId: input.subject.tenantId, event: receipt.status === 'failed' && receipt.errorKind === 'forbidden' ? 'provider.denied' : 'execution.finished', executionId: input.executionId, sessionId: input.sessionId, capabilityId: input.capability.id, providerId: input.providerId, paramsDigest, decision: receipt.status === 'failed' ? `failed:${receipt.errorKind ?? 'unknown_result'}` : receipt.status, detail: receipt.status === 'failed' && receipt.errorKind === 'forbidden' ? { by: 'provider', message: '业务系统拒绝了这次调用,平台不覆盖该结果' } : {}, }); return receipt; } async function invoke(request: InvokeRequest): Promise { const capabilityId = request.capabilityId; if (typeof capabilityId !== 'string' || capabilityId.trim() === '') { return failed(`exec-${randomUUID()}`, 'invalid_argument', 'capabilityId 不能为空'); } // 幂等:同一键的重试返回原回执,不重复执行 if (request.idempotencyKey) { const existing = executions.findByIdempotencyKey(request.idempotencyKey); if (existing) return existing.receipt; } const resolved = options.registry.resolve(capabilityId); const executionId = `exec-${randomUUID()}`; if (!resolved) { return failed(executionId, 'invalid_argument', `能力 ${capabilityId} 未注册`); } const projectId = request.projectId ?? null; const base: RunInput = { executionId, capability: resolved.capability, provider: resolved.provider, providerId: resolved.providerId, subject: request.subject, sessionId: request.sessionId, projectId, params: request.params, ...(request.idempotencyKey === undefined ? {} : { idempotencyKey: request.idempotencyKey }), timeoutMs: request.timeoutMs ?? defaultTimeoutMs, }; if (!control) return run(base); const binding = control.identity.resolve(request.subject, resolved.providerId); const decision = control.policy.decide({ subject: request.subject, capability: resolved.capability, providerId: resolved.providerId, binding, visible: control.isVisible(resolved.capability, request.subject), requiresIdentity: resolved.requiresIdentity, projectId, }); if (decision.decision === 'deny') { control.audit.append({ actorUserId: request.subject.userId, tenantId: request.subject.tenantId, event: 'policy.denied', executionId, sessionId: request.sessionId, capabilityId, providerId: resolved.providerId, paramsDigest: digestParams(request.params), decision: decision.code, detail: { reason: decision.reason }, }); return failed(executionId, 'forbidden', decision.reason); } if (decision.decision === 'require-approval') { return openApproval(base, binding, decision.code, decision.reason); } return run({ ...base, ...(binding === undefined ? {} : { binding }) }); } /** 写操作:先开确认卡,落一条待确认动作,等用户点头再执行 */ async function openApproval(input: RunInput, binding: AccountBinding | undefined, code: string, reason: string): Promise { if (!control) return failed(input.executionId, 'unavailable', '平台未启用控制面,写操作不可用'); // 同一写请求重试:返回原来那张确认卡,不再开第二张 if (input.idempotencyKey !== undefined) { const existing = control.approvals.findByIdempotencyKey(input.sessionId, input.idempotencyKey); if (existing) return existing.receipt; } const paramsDigest = digestParams(input.params); const preview = await previewFor(input); const action = control.approvals.create({ sessionId: input.sessionId, projectId: input.projectId, ownerUserId: input.subject.userId, tenantId: input.subject.tenantId, capabilityId: input.capability.id, providerId: input.providerId, paramsDigest, paramsPreview: preview.paramsPreview ?? {}, paramsSealed: control.credentials.sealText(JSON.stringify(input.params ?? null)), impactSummary: preview.impactSummary, revision: preview.revision, expiresAt: control.now() + control.approvalTtlMs, ...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }), }); const common = { actorUserId: input.subject.userId, tenantId: input.subject.tenantId, executionId: action.executionId, approvalId: action.approvalId, sessionId: input.sessionId, capabilityId: input.capability.id, providerId: input.providerId, paramsDigest, } as const; control.audit.append({ ...common, event: 'policy.requires_confirmation', decision: code, detail: { reason } }); control.audit.append({ ...common, event: 'approval.requested', decision: 'pending-confirmation', detail: { accountId: binding?.accountId ?? null, revision: action.revision, expiresAt: action.expiresAt } }); return { executionId: action.executionId, status: 'pending-confirmation', approvalId: action.approvalId }; } /** 影响预览:Provider 说了算;它没说就退回能力描述(只影响确认卡信息量) */ async function previewFor(input: RunInput): Promise { if (!control) return { impactSummary: input.capability.description, revision: 0 }; if (input.provider.preview) { try { const context: ExecutionContext = { executionId: input.executionId, sessionId: input.sessionId, projectId: input.projectId, subject: { userId: input.subject.userId, tenantId: input.subject.tenantId }, deadlineAt: control.now() + input.timeoutMs, }; return await input.provider.preview(context, { capabilityId: input.capability.id, params: input.params }); } catch { /* 预览失败不阻塞确认流程:退回能力描述 */ } } return { impactSummary: input.capability.description, revision: 0 }; } async function approve(subject: { userId: string; tenantId: string }, approvalId: string, input: ApprovalInput): Promise { if (!control) return { kind: 'not-found' }; const action = control.approvals.get(approvalId); if (!action) return { kind: 'not-found' }; // 确认人是会话发起人本人:单用户场景不做委托与转派 if (action.ownerUserId !== subject.userId || action.tenantId !== subject.tenantId) return { kind: 'forbidden' }; const decided = control.approvals.decide(approvalId, { decision: input.decision, revision: input.revision, decidedBy: subject.userId, idempotencyKey: input.idempotencyKey, }); if (!decided) return { kind: 'not-found' }; const { kind, action: current } = decided; const common = { actorUserId: subject.userId, tenantId: subject.tenantId, executionId: current.executionId, approvalId, sessionId: current.sessionId, capabilityId: current.capabilityId, providerId: current.providerId, paramsDigest: current.paramsDigest, } as const; // 同一请求重放:返回同一结果,不重复执行 if (kind === 'replayed') { return { kind: 'ok', approvalId, decision: current.decision ?? 'expired', ...(current.decision === 'approved' ? { receipt: current.receipt } : {}), }; } if (kind === 'expired' || kind === 'revision-mismatch') { control.audit.append({ ...common, event: 'approval.expired', decision: kind, detail: { requestedRevision: input.revision, currentRevision: action.revision } }); return { kind: 'ok', approvalId, decision: 'expired', ...(current.receipt.status === 'pending-confirmation' ? {} : { receipt: current.receipt }) }; } if (kind === 'rejected') { control.audit.append({ ...common, event: 'approval.decided', decision: 'rejected', ...(input.note === undefined ? {} : { detail: { note: input.note } }) }); return { kind: 'ok', approvalId, decision: 'rejected' }; } control.audit.append({ ...common, event: 'approval.decided', decision: 'approved', ...(input.note === undefined ? {} : { detail: { note: input.note } }) }); const resolved = options.registry.resolve(current.capabilityId); if (!resolved) { const receipt = failed(current.executionId, 'invalid_argument', `能力 ${current.capabilityId} 已不在能力目录里`); control.approvals.complete(current.executionId, receipt); return { kind: 'ok', approvalId, decision: 'approved', receipt }; } const plaintext = control.credentials.openText(current.paramsSealed); const params: unknown = plaintext === undefined ? null : (JSON.parse(plaintext) as unknown); const binding = control.identity.resolve(subject, current.providerId); const receipt = await run({ executionId: current.executionId, capability: resolved.capability, provider: resolved.provider, providerId: current.providerId, subject, sessionId: current.sessionId, projectId: current.projectId, params, ...(current.idempotencyKey === undefined ? {} : { idempotencyKey: current.idempotencyKey }), timeoutMs: defaultTimeoutMs, ...(binding === undefined ? {} : { binding }), }); control.approvals.complete(current.executionId, receipt); return { kind: 'ok', approvalId, decision: 'approved', receipt }; } return { invoke, approve, executions, registry: options.registry, ...(control === undefined ? {} : { approvals: control.approvals }) }; } export type { ExecutionRecord };