/** * v0.4 endpoint control-surface envelope (SPEC §13.3) — the versioned typed shapes riding the * §13.2 rails (`EndpointRequest`/`EndpointReply`/`EndpointEvent`), the structured error catalog, * W3C Trace Context correlation, and the consuming-boundary validators. * * Validation is FAIL-LOUD with the exact catalog code the spec assigns to each violation * (`unsupported-version`, `bad-request`, `contract-mismatch`, `op-mismatch`, `target-mismatch`, * `sender-mismatch`) so a rejecting boundary never invents a classification. Unknown object * fields are ignored (§5); the parsers return a picked copy carrying exactly the defined fields, * so nothing downstream can quietly grow a dependency on an undeclared one. Contract-schema * validation of `args`/`data` is §13.7's job ({@link import("./schema-profile.js")}); the helpers * here only map its outcome to the invocation-time code (`bad-request`, distinct from * registration-time `contract-invalid`). */ import type { ValidateFunction } from "ajv/dist/2020.js"; import { type ParsedEpRequest } from "./endpoint-subjects.js"; import type { EndpointRef } from "./types.js"; import { type EpError } from "./endpoint-error.js"; export { EP_ERROR_CODES, EpEnvelopeError, EP_UNBOUND_RESPONDER, respondedButUnbound, EP_UNANSWERED, unansweredRequest, unansweredRail, EP_REGISTRY_READ_FAILED, registryReadFailed, EP_BIND_REFUSED, replyRefusedBeforeEffect, bindRefusalMarked, EP_LIFECYCLE_BLOCKED, lifecycleBlocked, lifecycleBlockedDetail, lifecycleBlockedFrom, renderLifecycleBlocked, type EpErrorCode, type EpError, type EpErrorDetail, type EpUnboundResponderDetail, type EpUnansweredDetail, type EpRegistryReadFailedDetail, type EpBindRefusedDetail, type EpLifecycleBlockedDetail, type LifecycleBlockedFacts, type LifecycleBlockedOp, type LifecycleHeadState, type EpEffectOutcome } from "./endpoint-error.js"; /** The envelope schema version — independent of the wire `protocolVersion`; starts at its own * v1 inside the v0.4 revision. Other values are rejected (`unsupported-version`). */ export declare const EP_ENVELOPE_V = 1; /** True iff `code` is a catalog token or a reverse-DNS extension token within the 64-byte bound. */ export declare function isEpErrorCode(code: string): boolean; /** W3C Trace Context correlation slot, propagated to downstream calls, events, facts, receipts. */ export interface EpCorrelation { traceparent?: string; tracestate?: string; baggage?: string; } /** The invocation binding: endpoint + command MUST agree with the subject (`op-mismatch`); the * digests pin the described contract and are REQUIRED on every command except `describe` * (`contract-mismatch` when missing — a payload-free side pins the void schema's digest). */ export interface EpOp { endpoint: string; command: string; inputDigest?: string; outputDigest?: string; } /** The body target block (§13.3): present exactly for the targeted modes, `owner` pinned to the * subject token; `actor`/`lifecycleUid` are validator-compared against the current mapping. */ export interface EpTargetBlock { owner: string; actor: string; lifecycleUid: string; mappingRevision?: number; } /** A submission's declared delivery contract (`record` is a state contract, never a request * class; an action command's submissions are `journal`). */ export type EpClass = "ephemeral" | "journal"; /** * The BOUND INCARNATION (§13.3): the endpoint incarnation the caller's `describe` resolved * against, and the only one it will accept an effect from. * * It confers nothing and narrows only: a request carrying it reaches exactly the instances the * subject already routes it to, and can only make one of them refuse (monotonic attenuation, * §13.3). Attribution still comes from the reply SUBJECT — this is the caller's DECLARATION of what * it bound, checked by the responder against its own identity, not a claim about who answered. * * The epoch is carried even on the `inst` rail, which already pins the instance, because the * subject grammar has no epoch token: an instance's SUCCESSOR answers an inst-addressed request * today, and the caller notices only afterwards. */ export interface EpBindBlock { instanceId: string; epoch: number; } export interface EndpointRequest { v: typeof EP_ENVELOPE_V; id: string; op: EpOp; class: EpClass; /** The verb: `true` = call (reply expected, `deadlineMs` required), `false` = cast. The * subject shape is identical for both; the verb never changes the grammar. */ replyExpected: boolean; /** MUST for a command whose contract declares the action composite (a contract-level rule the * serve machinery enforces with the contract in hand); shape-checked here when present. */ goalId?: string; target?: EpTargetBlock; /** The incarnation this caller resolved against; a responder that is not it REFUSES before any * effect (§13.2). Absent on `describe` (the bootstrap that PRODUCES the bind) and on the * scatter rail (which addresses every incarnation by construction). */ bind?: EpBindBlock; /** The input payload: a JSON object, or explicit `null` — a canonical-void side's payload is * absent OR `null` (§13.7), so `null` must survive parsing for the command's own schema * validator to decide (an object-input contract still rejects it there, as `bad-request`). */ args?: Record | null; from: EndpointRef; /** Caller deadline budget, bounded never unbounded: required for calls and for journal-class * submissions (there it is the decision deadline, §13.4). */ deadlineMs?: number; correlation?: EpCorrelation; /** Opaque signed authorization-context slot; identity never rides it. Its fingerprint binding * is {@link authDigest} over these bytes exactly as carried. */ auth?: string; } export interface EndpointReply { v: typeof EP_ENVELOPE_V; id: string; ok: boolean; data?: unknown; error?: EpError; /** Opaque signed receipt slot (§13.10). */ receipt?: string; } /** An event (incl. per-goal progress) on the `epe` plane. The publishing instance and epoch are * read from the SUBJECT (§13.2), never from payload fields. */ export interface EndpointEvent { v: typeof EP_ENVELOPE_V; topic: string; ts: number; data: unknown; correlation?: EpCorrelation; } /** `authDigest`: `sha256:` over the UTF-8 bytes of the `auth` slot EXACTLY as carried. The * slot is already a canonical signed artifact, so it is digested as bytes, never * re-canonicalized; absent from the §13.4 fingerprint iff `auth` is absent. A malformed-UTF-16 * slot is refused (`bad-request`): a lone surrogate has no UTF-8 encoding, so its "digest" * would be over a substituted value and two distinct slots could share one fingerprint. */ export declare function authDigest(auth: string): string; /** Shape-validate an incoming request envelope (the pre-subject half of the consuming boundary; * {@link checkRequestSubjectAgreement} is the other half). Throws {@link EpEnvelopeError} with * the exact catalog code; returns a picked copy with exactly the §13.3 fields. */ export declare function parseEndpointRequest(raw: unknown): EndpointRequest; /** The body↔subject agreement half of the consuming boundary (§13.3): `op` MUST agree with the * subject (`op-mismatch`); the body target is ABSENT for `self`/untargeted forms (a supplied * one is `target-mismatch`, never ignored) and REQUIRED with a subject-equal owner (and, in * `handle` mode, subject-equal actor + lifecycleUid) for the targeted forms; `from.id` MUST * equal the subject sender principal (`sender-mismatch`). Currency of `target.actor`/ * `target.lifecycleUid` against the live mapping is the handler's fresh-read (`expired`), * deliberately not checkable here. */ export declare function checkRequestSubjectAgreement(env: EndpointRequest, subject: ParsedEpRequest): void; /** The contract-class agreement check (`class-mismatch`): the envelope's declared class MUST * equal the command's contract class. Split out because it needs the contract in hand. */ export declare function assertClassMatches(env: EndpointRequest, declaredClass: EpClass): void; /** The ACTION-COMPOSITE agreement check: `goalId` is a MUST for a command whose registered * declaration carries the action marker and MUST be absent otherwise. Split out for * the same reason as the class check — it needs the contract in hand. * * Both directions are refusals, and neither is a default. A missing `goalId` on an action command * cannot be minted here: the goal id is CLIENT-generated, so a server-side substitute would invent * the very identity the caller uses to correlate its own work. A `goalId` on a non-action command * cannot be dropped: it names a goal the command has no machinery to bind, and accepting it * silently would let a caller believe work is tracked that nothing tracks. */ export declare function assertActionGoalId(env: EndpointRequest, declaresAction: boolean): void; /** Shape-validate a reply at the caller's consuming boundary. `data` is schema-validated by the * caller against its PINNED output digest (§13.7), not here. */ export declare function parseEndpointReply(raw: unknown): EndpointReply; /** Shape-validate an event at its consuming boundary (§13.3: every plane is runtime-validated). * The publishing instance and epoch come from the SUBJECT; payload claims are display data. */ export declare function parseEndpointEvent(raw: unknown): EndpointEvent; /** Validate `args` against the command's compiled input schema BEFORE any effect: failure is the * invocation-time `bad-request` (registration-time violations are `contract-invalid`, * {@link import("./schema-profile.js").ContractInvalidError}). Against the void schema the * payload is absent or `null` (§13.7), so `undefined` args validate as `null` here and only * here; an explicit `null` passes through unchanged, and it is the SCHEMA (an object-typed * input contract) that rejects null for non-void commands. * * The §13.8 validation budget is REPORTED here, not enforced — see {@link reportValidateBudget} * for why no available instrument can justify refusing a request on it. The only `bad-request` * this raises is the schema's own verdict. Enforcement lives at registration * ({@link import("./schema-profile.js").compileContract}), which is where the COMPILE-time DoS * lives - not the only place a DoS lives, see the note on `reportValidateBudget` - and where * a false positive fails loudly instead of lying to a caller. */ export declare function assertArgsValid(validate: ValidateFunction, args: Record | null | undefined): unknown; /** Validate an output payload against the command's compiled output schema at EITHER §13.7 * boundary — the responder's, before the success publish, or the caller's, on the consumed * reply — the symmetric half of {@link assertArgsValid}. An invalid output is a responder bug * (§13.3/§13.7) whichever side detects it, so it is structured `internal`, never the caller's * `bad-request`. The §13.8 budget is REPORTED, not enforced, for the same reason as the args side * ({@link reportValidateBudget}). A void output is `undefined`, validated as `null` against the * void schema, mirroring the args side. */ export declare function assertOutputValid(validate: ValidateFunction, data: unknown): void; //# sourceMappingURL=endpoint-envelope.d.ts.map