/* RFC-0290: the dependency-injection ports the Agent Gate runs against. Keeps the gate framework-agnostic — `astro.ts` is the only module that constructs concrete port implementations (fetch-based knowledge reads, QStash dispatch). Do not implement any port here — that is astro.ts (or test fixtures' fakes). RFC-0290: initial ports contract. RFC-0291: add optional createRateLimiter port. RFC-1112: dispatch.send returns ActionReceipt; add optional idempotency port. RFC-1172: closed agent.traffic telemetry — ports.telemetry sink, TrafficRecorder + boundary emit via withTrafficEmit RFC-1187: phases 1-3 — contracts, Redis registry, AS core + Bearer pipeline */ import type { IntegrationEvent } from "@warpgogol/werkstatt-shared/integration/port"; import type { ActionReceipt, AgentTrafficEvent, AgentUsageRecord, PresentationJournalRecord, } from "@warpgogol/werkstatt-shared/agent"; import type { PresentationVerifier } from "./presentation.ts"; import type { AgentAuthPorts } from "./agent-auth.ts"; import type { RateLimiter } from "./limits.ts"; /** * RFC-1112: durable receipt store for Idempotency-Key replay. `get` returns the * stored record or null on a miss; `put` persists the receipt under the key for * `ttlSeconds`. Implementations throw on transport errors — the pipeline treats * a `get` throw as a miss (fail-open) and a `put` throw as non-fatal. */ export interface IdempotencyPort { get(key: string): Promise<{ receipt: ActionReceipt; bodyHash: string } | null>; put(key: string, bodyHash: string, receipt: ActionReceipt, ttlSeconds: number): Promise; } export interface AgentGatePorts { /** Read a static public artifact by site-relative path (e.g. "/api/agent/v1/offer.json"). */ knowledge: { read(path: string): Promise }; /** * Hand a validated event to the reliable delivery substrate. Returns the * ActionReceipt the caller sees (receiptId == event.eventId, submittedAt * stamped by the adapter at publish time). Throws on transport failure. */ dispatch: { send(event: IntegrationEvent): Promise }; now(): Date; /** * RFC-0291: optional rate limiter factory. If absent, no rate limiting (fail-open). * RFC-1212: optional `windowSeconds` — metering free-allowance windows are * day/month-scale; adapters default to their throttle window when omitted. */ createRateLimiter?: (maxPerWindow: number, windowSeconds?: number) => RateLimiter; /** * RFC-1112: optional receipt store for Idempotency-Key dedup. If absent, * requests proceed without dedup and responses carry `X-Agent-Idempotency: disabled`. */ idempotency?: IdempotencyPort; /** * RFC-1172: optional sink for the closed `werkstatt.agent.traffic` event. * Called exactly once per request at the response boundary; the emit helper * swallows sink errors, so implementations may be fire-and-forget. * Production adapters wire `console.log(JSON.stringify(event))` (Workers * captures structured stdout); absent port = telemetry disabled. */ telemetry?: (event: AgentTrafficEvent) => void; /** * RFC-1212: optional sink for journaled `gogol.agent-usage/1` records — * called exactly once per dispatched metered call (post-dispatch only; * rejected/replayed calls never reach it). Fire-and-forget: the emit helper * swallows sink errors. The engine only journals — the pull-model bridge in * `werkstatt-site/src/billing` drains records into billing; the engine * never imports billing packages (AC-9). Absent port = metering records * drop silently (metering never breaks the request path). */ usage?: (record: AgentUsageRecord) => void; /** * RFC-1213: injected cryptographic verifier for verifiable presentations — * a maintained VC DM 2.0 / OIDC4VP library plugs in here. Absent = no * verifier surface; capabilities declaring requires.verifiablePresentation * fail closed at the boundary (AGP-02 blocks the declaration at author time). */ presentationVerifier?: PresentationVerifier; /** * RFC-1213: journaled sink for `gogol.presentation-verdict/1` records — * called once per completed verification (success or failure) carrying * {requirementId, verified, issuer, failureReason, presentationHash} only; * claim values never reach the journal. Fire-and-forget; the emit helper * swallows sink errors. */ presentationJournal?: (record: PresentationJournalRecord) => void; /** * RFC-1187: authorization-server ports. Present only when the site enabled * `agent.auth` AND the runtime wiring exists (Upstash Redis secrets + * SIGNING_PRIVATE_KEY). Absent port = anonymous tier everywhere — the same * object backs Bearer resolution on action routes and the AS route * handlers (challenge/register/token/jwks/whoami/rotate). */ auth?: AgentAuthPorts; }