/** * v0.4 serve/describe machinery (SPEC §13.5 verbs, §13.7 "Descriptor and describe", §13.2 * rails) — the request-boundary dispatch an endpoint instance serves its registered commands * through: queue-grouped class serving, the scatter and stable-instance rails, contract- * digest-bound invoke with MANDATORY runtime schema validation, structural reply derivation, * and the reserved authorization-scoped `describe`. * * Only EPHEMERAL commands are rail-served: journal work rides `epj` submissions into the * canonicalizer and executes off the effects/pool durables (§13.4/§13.5), so a journal-class * command def REFUSES at construction, and a request DECLARING `class: journal` on a rail is * refused at the boundary. Incarnation fencing is not a subscription shape (§13.9: the epoch * is deliberately absent from serve subscriptions): the §13.1 takeover barrier fences a * superseded subscriber, every reply carries the responder's epoch in its SUBJECT (attributably * stale when superseded), and commits are epoch-fenced at the record seam * ({@link writeServiceStatus}). */ import type { NatsConnection } from "@nats-io/transport-node"; import { type EpCaller, type ParsedEpRequest } from "./endpoint-subjects.js"; import { type EndpointRequest } from "./endpoint-envelope.js"; import { type CompiledContract } from "./schema-profile.js"; import { type EpServeGrant } from "./endpoint-service.js"; import type { DescribeDescriptor } from "./endpoint-cluster.js"; import { type EpTraitEnforcement } from "./endpoint-traits.js"; import type { GuardObligation } from "./endpoint-guard.js"; /** The serving instance's identity: its stable logical instance id and fenced process epoch * (§13.1). Both ride every reply SUBJECT (attribution is structural, §13.2). */ export interface EpServeIdentity { endpoint: string; instanceId: string; epoch: number; } /** What a handler sees: the broker-authenticated SUBJECT shape (route, caller, target) beside * the validated body — provenance never comes from the body (§13.2/§13.3). `obligations` is * present exactly when a guard allowed WITH signed attenuations (§13.6), and every entry is * VERIFIED by the gate (D28 signature, anchor role/scope, window, space + request binding) * before it reaches a handler: the endpoint MUST apply them (monotonic); the applying policy * engine is an extension behind the seam. */ export interface EpServeContext { identity: EpServeIdentity; subject: ParsedEpRequest; request: EndpointRequest; obligations?: readonly GuardObligation[]; } /** One served command: its handler plus the COMPILED §13.7 contracts (schema-profile * {@link compileContract} — provenance-branded, so a fabricated `{validate, closureDigest}` * pair refuses at construction). Everything AUTHORITY-shaped about the command — its class, * whether it is targeted and which modes it admits, its schema closure digests — comes from * the serve artifact's digest-VERIFIED registered declaration, never from this def: the def * only supplies the code, and its compiled contracts must EQUAL the registered digests. * `input.validate` gates args before any effect (`bad-request`), `output.validate` gates * before the success publish (`internal` — an invalid reply is a server bug, §13.3/§13.7). * Runtime validation at the serving boundary is not optional. */ export interface EpCommandDef { command: string; contract: { input: CompiledContract; output: CompiledContract; }; handler: (ctx: EpServeContext) => Promise | unknown; } /** The FRESH target-resolver seam (§13.3/§13.9: targets resolve by `(alias, lifecycleUid)` * against the CURRENT mapping immediately before effect; static subject/body agreement is not * currency). Returns the alias's current mapping, or `undefined` when the alias has none. The * production reader is the D13 lifecycle registry's leader-served mapping read. */ export type EpTargetResolver = (target: { owner: string; actor: string; }) => Promise<{ lifecycleUid: string; mappingRevision: number; } | undefined> | { lifecycleUid: string; mappingRevision: number; } | undefined; /** The `child`-mode fresh-authorization seam (§13.2): TRUE iff the DURABLE spawner record of * `target` names `caller` as its spawner — read fresh at dispatch, never inferred from the * caller's grant alone (the grant pins the owner domain; the spawner relation is per-entity * state). The production reader is the D13 lifecycle registry's spawner record. */ export type EpChildAuthority = (args: { caller: EpCaller; target: { owner: string; actor: string; lifecycleUid: string; }; }) => Promise | boolean; /** The `ledger`-mode fresh-authorization seam (§13.2): TRUE iff a FRESH read of the * authorization ledger grants `caller` this op on `target`. Fail-closed by construction: no * seam, a seam failure, and a false answer all refuse — a ledger row is never cached into a * dispatch decision. */ export type EpLedgerAuthority = (args: { caller: EpCaller; target: { owner: string; actor: string; lifecycleUid: string; }; op: { endpoint: string; command: string; }; }) => Promise | boolean; export interface EpServeHandle { /** Drain every serve subscription, then await every in-flight handler: after `stop()` * resolves this incarnation performs no further effects and publishes no further replies. */ stop(): Promise; } /** * Serve an authorized instance's granted commands on the three §13.2 rails, exactly the * per-command forms the serve credential grants (§13.9 {@link epServeSubscribeRows}): the class * rail queue-qualified under the canonical queue group (`one` = queue-group anycast), the * scatter rail plain, and this instance's own `inst` rail. * * `serve` is the registry-authorized ARTIFACT {@link authorizeServeGrant} returned — the same * value the credential minted from. Construction refuses anything else (brand check), refuses * a foreign space, and binds every def to the artifact's digest-VERIFIED registered * declaration: the def must be a GRANTED command, its provenance-branded compiled contracts * must equal the registered schema digests, its class/targeted/modes come from the verified * declaration (a journal-class registered command never rail-serves, so it takes no def), and * every granted EPHEMERAL command must have a def (a rail nobody serves is a construction bug); * journal commands stay in the credential/descriptor surface but ride epj, so a journal-only or * mixed endpoint still constructs and serves describe. The reserved `describe` (§13.7: every endpoint MUST serve it) is built * HERE over the artifact's DERIVED deep-frozen descriptor — a `describe` def refuses at * construction, so the authorization seam cannot be replaced, and no hand-authored or * later-mutated descriptor can reach the wire. * * Boundary discipline per message: subject parse (a non-request subject has no sender and is * never handled), body validation with the exact §13.3 catalog codes, body-subject agreement, * class match against the REGISTERED class, digest binding, registered admission (targeted * commands refuse the untargeted form and vice versa), args schema validation, fresh target * currency, the per-mode fresh authorization (`child`/`ledger` seams), then — because those * seams await — target currency AGAIN immediately before dispatch (§13.2/§13.3: a mapping * rotated during the authority read must fail, never ride a pre-rotation read into the * effect), the §13.7 governed pre-effect gate for a command whose REGISTERED declaration * carries a governed trait (guard-then-priced, {@link assertGovernedPreEffect}; construction * already refused a governed surface/hook gap, so a bypass is structurally impossible), and * budgeted output schema validation before the success publish. A call's reply * (success OR structured error) is published on the DERIVED reply subject (§13.2: never a * body-supplied target); a cast is never replied to, even on error (§13.5: at-most-once, the * caller never reads the rail). A request whose body cannot be parsed carries no trustworthy * verb; it is answered (the derived subject is nonce-scoped to this caller, and a cast caller * simply holds no subscription there). A reply that does not serialize is replaced by a * structured `internal` error reply, never dropped. */ export declare function serveEndpoint(nc: NatsConnection, space: string, serve: EpServeGrant, commands: EpCommandDef[], describe: DescribeAuthorization, opts?: { resolveTarget?: EpTargetResolver; childAuthority?: EpChildAuthority; ledgerAuthority?: EpLedgerAuthority; /** The §13.9 trait seam: REQUIRED (with the matching hooks) when any granted command's * registered declaration carries a governed trait — construction refuses a governed * command it cannot enforce, and refuses an extraneous enforcement bundle on an * ungoverned surface (fail loud both ways, never a silent no-op). */ traits?: EpTraitEnforcement; }): EpServeHandle; /** A caller's authority view from the TRUSTED source (§13.7): the command set this caller may * see. `undefined` = no fresh view (stale beyond its bound, or the source has no answer) — * describe then fails CLOSED, never answers from a weaker source. */ export interface DescribeView { commands: string[]; } /** The describe authorization seam: either the deployment declared this descriptor PUBLIC (no * view is consulted and the answer says so), or a trusted view provider keyed by the * broker-authenticated caller identity (§13.7: payload/slot-asserted scope is ignored — it is * not even a parameter here). The provider owns its own freshness bound. */ export type DescribeAuthorization = { public: true; } | { public?: false; view: (caller: EpCaller) => Promise | DescribeView | undefined; }; /** The describe answer: `public` says which path produced it (§13.7: the answer says so). */ export interface DescribeAnswer { public: boolean; descriptor: DescribeDescriptor; } //# sourceMappingURL=endpoint-serve.d.ts.map