/* RFC-1112: canonical agent-action receipt + RFC 9457 problem-details contracts. One shape for both invocation surfaces (HTTP POST /api/agent/actions/{id} and MCP tools/call) — the receipt is the final v1 answer; errors carry a stable machine `code` so agents can branch without parsing prose. Do not store or transport lead payload fields — receipts carry identifiers and timestamps only (no PII at rest). Do not add transport logic — HTTP/MCP mapping lives in the engine (action-pipeline.ts, mcp/handler.ts). RFC-1112: initial receipt/problem-details contracts + deterministic eventId derivation. RFC-1187: phases 1-3 — contracts, Redis registry, AS core + Bearer pipeline RFC-1187: fo-review — auth-backend-unavailable code (503 retryable) for registry outages on AS endpoints */ import { byteHash } from "@warpgogol/werkstatt-shared/fingerprint"; /** * The single response shape for a successful agent action invocation (RFC-1112). * `receiptId` equals `IntegrationEvent.eventId` — deterministic when the caller * supplied an Idempotency-Key, random otherwise. */ export interface ActionReceipt { receiptId: string; status: "accepted" | "duplicate"; duplicate: boolean; /** ISO 8601 — stamped by the dispatch adapter at publish time. */ submittedAt: string; } /** Stable machine codes for agent-action failures (RFC-1112). Closed set. */ export type AgentErrorCode = | "invalid-json" | "schema-violation" | "unknown-capability" | "payload-too-large" | "rate-limited" | "dispatch-failed" | "idempotency-key-reuse" | "unauthorized" | "invalid-challenge" | "invalid-proof" | "already-registered" | "oauth-unavailable" | "auth-backend-unavailable" | "presentation-required" | "presentation-invalid"; /** RFC 9457 problem details with platform extension members (`code`, `retryable`, …). */ export interface AgentProblemDetails { type: string; title: string; status: number; detail?: string; code: AgentErrorCode; retryable: boolean; /** Per-field violations for schema-violation. */ errors?: { path: string; message: string }[]; /** Pointer into agent.openapi.json for schema-violation. */ schemaRef?: string; /** Seconds until the rate-limit window resets (rate-limited only). */ retryAfterSeconds?: number; } export const AGENT_ERROR_STATUS: Record = { "invalid-json": 400, "schema-violation": 422, "unknown-capability": 404, "payload-too-large": 413, "rate-limited": 429, "dispatch-failed": 502, "idempotency-key-reuse": 409, unauthorized: 401, "invalid-challenge": 400, "invalid-proof": 400, "already-registered": 409, "oauth-unavailable": 404, "auth-backend-unavailable": 503, "presentation-required": 403, "presentation-invalid": 403, }; export const AGENT_ERROR_RETRYABLE: Record = { "invalid-json": false, "schema-violation": false, "unknown-capability": false, "payload-too-large": false, "rate-limited": true, "dispatch-failed": true, "idempotency-key-reuse": false, unauthorized: false, "invalid-challenge": false, "invalid-proof": false, "already-registered": false, "oauth-unavailable": false, "auth-backend-unavailable": true, "presentation-required": false, "presentation-invalid": false, }; const AGENT_ERROR_TITLE: Record = { "invalid-json": "Request body is not valid JSON", "schema-violation": "Input failed schema validation", "unknown-capability": "Unknown capability", "payload-too-large": "Payload exceeds the capability limit", "rate-limited": "Rate limit exceeded", "dispatch-failed": "Delivery substrate rejected the event", "idempotency-key-reuse": "Idempotency-Key was used with a different payload", unauthorized: "Bearer authentication required", "invalid-challenge": "Registration challenge is unknown, expired, or already consumed", "invalid-proof": "Key proof signature did not verify", "already-registered": "Agent is already registered", "oauth-unavailable": "Authorization server is not configured on this site", "auth-backend-unavailable": "Authorization backend temporarily unavailable", "presentation-required": "A verifiable presentation is required for this capability", "presentation-invalid": "The verifiable presentation did not satisfy the requirement", }; /** Build a problem-details object for `code`; status/title/retryable derive from the closed tables. */ export function buildAgentProblem( code: AgentErrorCode, init?: Partial>, ): AgentProblemDetails { return { type: "about:blank", title: AGENT_ERROR_TITLE[code], status: AGENT_ERROR_STATUS[code], code, retryable: AGENT_ERROR_RETRYABLE[code], ...(init?.detail !== undefined ? { detail: init.detail } : {}), ...(init?.errors !== undefined ? { errors: init.errors } : {}), ...(init?.schemaRef !== undefined ? { schemaRef: init.schemaRef } : {}), ...(init?.retryAfterSeconds !== undefined ? { retryAfterSeconds: init.retryAfterSeconds } : {}), }; } /** * RFC-1112: deterministic eventId/receiptId from an Idempotency-Key. * sha256("gogol-agent-action:" + capabilityId + ":" + key), first 32 hex chars * formatted as a UUID. Same key + same capability → same eventId, so QStash * deduplication and the receipt store agree on identity. */ export function deriveActionEventId(capabilityId: string, idempotencyKey: string): string { const hex = byteHash(`gogol-agent-action:${capabilityId}:${idempotencyKey}`).slice( "sha256:".length, ); return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`; }