/*
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;
}