/** * v0.4 NATS + JetStream binding (SPEC §13.12) — the per-space control-surface resources and * the §13.9 consumer-name grammar with the infrastructure consumer configs over them. * * Streams are space infrastructure: `STREAM.CREATE` is denied to agents, so * {@link createEndpointStreams} runs once at space setup (like `createSpaceStreams`). It is the * single source of the resource definitions — the table in §13.12 — so setup and every consumer * of a stream name can never diverge. Consumer CONFIGS here are equally single-source: each is * created by exactly one trusted principal (provisioner or the owning infra principal) and the * §13.9 grant rows are generated against these same names and filters. */ import { type ConsumerConfig, type JetStreamManager } from "@nats-io/jetstream"; import type { Kvm } from "@nats-io/kv"; import { type EpCaller } from "./endpoint-subjects.js"; import type { RecordKindDef } from "./endpoint-records.js"; import { epjStreamName, epfStreamName, canonDurable } from "./endpoint-journal.js"; import { recordsBucket } from "./endpoint-records.js"; export { epjStreamName, epfStreamName, canonDurable, recordsBucket }; /** §13.12 stream names for the remaining per-space control-surface streams. */ export declare function epeStreamName(space: string): string; export declare function eptReqStreamName(space: string): string; export declare function eptStreamName(space: string): string; export declare function eprStreamName(space: string): string; export declare function epwStreamName(space: string): string; export declare function epcStreamName(space: string): string; /** The workflow STEP JOURNAL stream. Deliberately outside the `ep*` plane letters: the step * journal is a runtime layer over the control surface, not part of the normative §13 endpoint * contract, and a reader that confuses the two would take a run's private trace for a decision * fact. It sits beside the §13 decision-fact journal, never on top of it. */ export declare function wfjStreamName(space: string): string; /** * The ONE subject a run's journal entries append to. * * ONE SUBJECT PER RUN, not one per entry — a deviation from the per-entry subject first sketched * for it, and a CHOICE rather than a necessity. Per-entry subjects can be fenced: * `Nats-Expected-Last-Subject-Sequence-Subject` evaluates the expectation against a wildcard * comparator, measured working on the repo's broker floor. They are not used because the three * properties a subject range is wanted for here — per-run ordering, replay by consumer filter, and * retirement by subject purge — are all properties of the RUN subject, while the entry level buys * only per-entry point reads, which an append-only journal replayed in full never issues. Against * that it costs one stream subject per entry forever in a stream with no age eviction, and a second * header whose absence degrades silently to a per-publish-subject comparison — on a fresh entry * subject that is `0`, i.e. no fence at all. */ export declare function wfjSubject(space: string, runId: string): string; /** The CLOSED set of streams a §13.1 retirement may record a frontier cutoff over: exactly the * per-space streams that carry a retired lifecycle's durable data a later durable reader can * replay (facts EPF, work EPW, events EPE, and the records KV). A retirement intent's * `frontierStreams` must be a subset of this set; it is NOT a caller-selectable arbitrary stream * list. This is the ONE source consumed by both the intent validation and the barrier's * `STREAM.INFO` grant, so the frontier authority and the frontier-writable set never drift * (nats-server subject ACLs cannot scope INFO to an intent-selected name). The auth store is * deliberately absent: it is the control plane, never a lifecycle-data frontier. */ export declare function retirementFrontierStreams(space: string): string[]; /** The per-space auth store (§13.12): credential ledger + issuance/source gates + session * ledger. Trusted auth path ONLY — no agent/endpoint/observer/admin/host profile holds any * grant — and `allow_direct=false`: every fence on it is a leader-served revision-pinned CAS, * and Direct Get's follower/mirror reads would defeat read-your-writes (§13.1). */ export declare function epAuthBucket(space: string): string; /** The per-space SESSION ledger store (P2 item 6, §13.6): the `session.` rows the manager's * session plane CASes over. DEDICATED — split out of the auth bucket deliberately. KV reads are * subject-BLIND (a `STREAM.MSG.GET` on a bucket serves any key, the campaign's known vector class), * so co-locating session rows with credentials + gates would let the standing session-ledger cred read * the whole control plane. A dedicated bucket makes that blind read STRUCTURALLY confined to session * rows and nothing else. `allow_direct=false` for the SAME reason the auth bucket carries it: every * ledger fence is a leader-served revision-pinned CAS (the one-use `createIssuing`, the finalize + * terminal updates), and Direct Get's follower/mirror reads would defeat read-your-writes (§13.1). */ export declare function sessionsBucket(space: string): string; /** Every stream owned by {@link createEndpointStreams}. Keep this downstream ownership list * independent from the creation statements: backup inventory tests compare it to the broker's own * enumeration, so a family added to either side without the other fails rather than self-confirms. * Deletion and its sole credential consume this same list because disagreement between those two * leaves either an undeletable stream or a grant for a stream teardown never removes. */ export declare function endpointPlaneStreamNames(space: string): string[]; /** The stores {@link createEndpointStreams} HARDENS with a one-time `STREAM.UPDATE` right after * creation (the records store, the two issued-authority stores and the run admission store). * Every credential that runs the creation seam needs an UPDATE row on exactly these and no other * stream; the provisioner and the restore-side infrastructure login both read this list, so a * store added to the hardening step cannot leave one of them without the grant. */ export declare function hardenedAuthorityStreamNames(space: string): string[]; /** EPJ duplicate window: the server MINIMUM (100 ms), set explicitly. A `0` is not accepted * (it normalizes to the 120 s default), and native dedupe is deliberately NOT relied upon — * submitters never set `Nats-Msg-Id`, and a wide window is exactly the cross-caller * suppression surface §13.4 refuses — so the window is pinned as small as the server allows. */ export declare const EPJ_DUPLICATE_WINDOW_MS = 100; /** Default age bound on raw submissions (EPJ). The §13.12 floor is "≥ recovery/redelivery * lag" of the canonicalizer; 24 h covers any realistic canonicalizer outage while keeping the * untrusted log from growing unbounded. */ export declare const EP_SUBMISSION_MAX_AGE_MS: number; /** Default age bound on events (EPE) — progress/catch-up telemetry, space policy. */ export declare const EP_EVENT_MAX_AGE_MS: number; /** Default age bound on the two writer-ingress streams (EPT_REQ, EPR). The floor is * "≥ writer recovery lag"; the same 24 h envelope as EPJ. */ export declare const EP_INGRESS_MAX_AGE_MS: number; /** Default age bound on authoritative schedules + fires (EPT). The floor is * "≥ max deadline + margin": a schedule stored longer than this cannot outlive its stream * row, so the default admits deadlines up to ~30 days. */ export declare const EP_TIMER_MAX_AGE_MS: number; /** Delete-marker TTL on the auth store — enables the stream's per-key TTL machinery * (`allow_msg_ttl`), which `cred.`/`bysrc.` rows use (per-key TTL ≤ credential TTL). The * bucket itself carries NO age retention: `gate.`/`srcgate.`/`session.` authority keys * persist until explicitly terminal (§13.12). */ export declare const EP_AUTH_MARKER_TTL_MS: number; export interface EndpointStreamOptions { /** Age bound on EPJ (default {@link EP_SUBMISSION_MAX_AGE_MS}). Floor: canonicalizer recovery lag. */ submissionMaxAgeMs?: number; /** Age bound on EPF; 0/omitted = no age eviction (facts are the canonical record; a horizon is * never realized by silently losing facts under it). A positive value below the declared * idempotency horizon is REFUSED at creation — see {@link assertFactRetentionFloor}. */ factMaxAgeMs?: number; /** The space's DECLARED idempotency horizon (§13.4 item 6; default * {@link IDEMPOTENCY_HORIZON_MS_DEFAULT}). Declared rather than compiled in, for the same reason * the admission ceiling is: a space that retains decisions longer must have its fact retention * measured against ITS horizon, not against this module's default. */ idempotencyHorizonMs?: number; /** The space's DECLARED result retention (§13.6 item 5; default * {@link RESULT_RETENTION_MS_DEFAULT}). A goal's full terminal payload lives on EPF, so this is * a second term in the §13.12 floor, not a separate store's policy. */ resultRetentionMs?: number; /** The space's DECLARED receipt retention (§13.10; default {@link RECEIPT_RETENTION_MS_DEFAULT}, * 90 d). **The LARGEST of the three terms by two orders of magnitude**, which is exactly why the * floor cannot be the horizon alone: a config at the 24 h horizon passed the old check and * evicted receipts on day one of ninety. */ receiptRetentionMs?: number; /** Age bound on EPE (default {@link EP_EVENT_MAX_AGE_MS}). */ eventMaxAgeMs?: number; /** Age bound on EPT_REQ + EPR (default {@link EP_INGRESS_MAX_AGE_MS}). Floor: writer recovery lag. */ ingressMaxAgeMs?: number; /** Age bound on EPT (default {@link EP_TIMER_MAX_AGE_MS}). Floor: max deadline + margin. */ timerMaxAgeMs?: number; } /** * The §13.12 RETENTION FLOOR on decision facts, enforced at the only site that exists. * * SPEC §13.12 requires EPF retention ≥ max(idempotency horizon, result retention, receipt retention), and §13.12 states it by OUTCOME: no * removal cause may drop a protected fact early. The §13.4 idempotency horizon is realized BY that * retention and never by a clock — the create-only CAS returns the recorded decision for exactly as * long as the fact exists. So a fact age below the horizon does not shorten a guarantee, it deletes * the mechanism: once the decision fact is evicted, a redelivered submission finds no winner to * read and is accepted as NEW WORK, which is the failure SPEC §13.8 names in as many words. * * WHY THIS IS A THROW AND NOT A CLAMP. The field's own contract was that horizons are "enforced by * policy above the broker, never by silently losing facts under a horizon" — and nothing was above * the broker: `IDEMPOTENCY_HORIZON_MS_DEFAULT` was exported with no readers anywhere in the tree, so * the constant naming the horizon participated in nothing. A delegation to a layer that does not * exist is an unenforced invariant with a comment on it, and the comment is what stops anyone * noticing. Refusing the configuration is not contrary to that position but the only implementation * of it: a throw at creation loses no facts; it declines a setup that would. */ export declare function assertFactRetentionFloor(factMaxAgeMs: number | undefined, terms: { horizonMs: number; resultRetentionMs?: number; receiptRetentionMs?: number; } | number): void; /** * Create (idempotently) the §13.12 per-space control-surface resources: the seven JetStream * streams, the work-pool WorkQueue, and the KV buckets (records + auth + the §13.6 session ledger). * Privileged — runs at space setup. `jsm.streams.add`/`kvm.create` are idempotent for an identical * config and FAIL LOUD on a config delta, which is wanted: a drifted resource is an operator error, * never silently adopted. * * The session byte SUBJECTS (`eps`) are deliberately absent: core-only, never captured (§13.12). * Only the durable `session.` ledger rows are captured — in their own dedicated bucket * ({@link createSessionsStore}), never the auth bucket. */ export declare function createEndpointStreams(jsm: JetStreamManager, kvm: Kvm, space: string, opts?: EndpointStreamOptions): Promise; /** * Ensure the per-space CONTRACT store (EPC) exists with its normative shape (§13.7/§13.12) — * content-addressed artifacts, one immutable message per digest subject, create-only mediated * publication, NO age eviction (artifacts are permanent). allow_direct: the subject-scoped * last-by-subject read IS the fetch path. * * PER-SUBJECT IMMUTABILITY is BROKER-ENFORCED, not left to publisher cooperation (the append-shadow * blocker, live-confirmed by the panel). The create-only fence `Nats-Expected-Last-Subject-Sequence: * 0` is a publisher-SET header the `epc.*` publish grant cannot compel, so a non-cooperative * grant-holder could APPEND a second message to an already-published digest subject; `last_by_subj` * would then return that shadow and a fail-closed read would make the honest artifact permanently * unfetchable (deny_delete/deny_purge block recovery — an operator-reprovision-only DoS, and at * §13.11's ep-only cut the SOLE contract path). The store closes this at the SOURCE: * `max_msgs_per_subject: 1` + `discard: new` + `discard_new_per_subject: true` makes a second * publish to an occupied digest subject BROKER-REJECTED (err 10077) regardless of headers, so a * digest subject holds exactly one message forever. `deny_delete`/`deny_purge` then keep that one * message from being removed. (The read path additionally defends itself version-agnostically — * {@link fetchContractArtifact} prefers the create-only winner over any shadow — so a broker or a * legacy stream that lacked the per-subject cap is still safe.) * * Create-or-verify-AND-harden, safe at every authority-daemon boot (the {@link * ensureAuthorityStores} discipline): a fresh space gets the store created with the full shape; a * CLEAN pre-existing store (incl. a pre-hardening one from an earlier release, OR the config-A * footgun `max_msgs_per_subject:1` + `discard:old` that would DELETE the honest artifact) is UPDATED * to the three config-B flags (idempotent, like the records store's rollup/deny update) and then * VERIFIED - a shape that cannot be brought to config B FAILS LOUD, never silently adopted. * * The config-B upgrade ENFORCES per-subject immutability GOING FORWARD; it does NOT heal a shadow * that predates it. Applying `max_msgs_per_subject:1` trims each subject to its newest message, so a * legacy store that ALREADY holds a shadow (some digest subject with >1 message) would have the * honest create-only winner trimmed away and the shadow cemented. Rather than silently cement it, * the upgrade REFUSES LOUD on such a store (`messages > num_subjects`), so the operator reprovisions * a clean store. This is a narrow guard: a fresh deploy is born at config B and never reaches it. * * FAIL-LOUD IS THE AGENT'S DEFENSE (critic completeness item): because this verify refuses to serve * unless all three config-B flags are present, the manager NEVER serves an un-hardened EPC store — * so a shadow-append cannot exist on any store an agent reads. That is why the ordinary agent * baseline needs only the subject-scoped `last_by_subj` read (never the bare `next_by_subj` the * shadow fallback uses): on every store the manager actually serves, `last_by_subj` always verifies * and the fallback never triggers. The {@link fetchContractArtifact} create-only-winner fallback is * defense-in-depth for the publisher path (the executor, which holds `next_by_subj`) and for a * hypothetical un-hardened store the manager would refuse to serve anyway. On the pinned broker * floor (nats-server >= 2.12, well past the 2.9 that added `discard_new_per_subject`) config B always * lands; an older broker that ignores the flag is caught here and the daemon fails to start. */ export declare function ensureContractStore(jsm: JetStreamManager, space: string): Promise; /** * Ensure the two per-space AUTHORITY stores exist with their normative shape (§13.12): * * - **Records KV** (`cotal_records_`) — per-key CAS; rows are never deleted. * deny_delete/deny_purge close stream-API erasure as defense in depth (a raw KV subject grant * can still emit a DEL marker, which every reader treats as corruption); rollups off. Fenced * reads stay leader-served STREAM.MSG.GET (§13.9). * - **Auth KV** (`cotal_auth_`) — leader-served only (`allow_direct=false`); per-key TTL * machinery on (`cred.`/`bysrc.` rows), NO bucket age. * * Create-or-verify, so it is safe at EVERY authority-daemon boot (not only first setup): a fresh * space gets both stores created; an existing store is verified against the exact flags above and * a drift FAILS LOUD naming the store — a drifted authority store is an operator error, never * silently adopted (§13.12: the flags are load-bearing for deny-new and the barrier CAS fences). */ export declare function ensureAuthorityStores(jsm: JetStreamManager, kvm: Kvm, space: string): Promise; /** Create (idempotently) the per-space SESSION ledger store (P2 item 6, §13.6): the DEDICATED * {@link sessionsBucket} the manager's session plane CASes `session.` rows over. Kept OUT of * {@link ensureAuthorityStores} deliberately — the auth path never touches session rows, and the * manager provisions this store from its own boot — but it wears the SAME authority-store shape as * the auth bucket: `allow_direct=false` (every ledger fence is a leader-served revision-pinned CAS, * and Direct Get's follower reads would defeat read-your-writes, §13.1) plus the per-key TTL * machinery (a terminal/expired session row can carry a delete-marker TTL). The dedication is the * security substance: the standing session-ledger cred's `kv.get` is a bucket-blind body-selected read, * and a bucket holding ONLY `session.>` rows makes that read expose nothing but session state — the * structural fix for the §13.9 subject-blindness a shared auth bucket would carry (creds + gates). * Create-or-verify, safe at every manager boot; a drifted store FAILS LOUD (§13.12). */ export declare function createSessionsStore(jsm: JetStreamManager, kvm: Kvm, space: string): Promise; /** `poolD = pool__` — parses uniquely from its LAST `_` because a pool token contains * no `_` (`[a-z0-9-]`) while `` may. */ export declare function poolDurable(endpoint: string, pool: string): string; /** `timerD = timerw_` — the space's single timer-writer durable. */ export declare function timerWriterDurable(space: string): string; /** `recwD-k = recw_-` — ONE record-writer durable per record kind (§13.9's writer * separation). Parses from its LAST `-`? No — from the FIRST `-` after the fixed prefix is * ambiguous when the space token contains `-`; the collision-freedom argument is simpler: the * durable exists once per (space, kind) pair inside a per-space stream, so only the `` * tail must be unique within one space, and kinds are unique by the registry. The kind token * is the `epr` subject's kind token (id grammar, dot-free). */ export declare function recordWriterDurable(space: string, kind: string): string; /** `effD = eff_` — the endpoint's ONE shared effects durable (instances pull-compete). */ export declare function effectsDurable(endpoint: string): string; /** `decD = dec_-` — a caller's decision-reader durable (one per journal capability). * Parses from its FIRST `-`: `` is `[a-z0-9]` and contains none. */ export declare function decisionReaderDurable(uid: string, endpoint: string): string; /** `goalD = goal_-` — a caller's goal-result durable (one per action capability). */ export declare function goalReaderDurable(uid: string, endpoint: string): string; /** `eveD = eve_---` — one per granted event subtree: `` is the mint-time * grant id, `` the subtree's zero-based index within THAT grant. INJECTIVE by construction: * `` is `-`-free (leading), `` is digits (trailing), `` is separator-free * (`assertGrantId`), so `` is the ONLY `-`-bearing component and its extent is unambiguous * (parse `` and `` off the right, `` off the left, `` is what remains). Without * the separator-free `` the two soft components `` and `` would collide * (`eve_-a-b-c-0` = endpoint `a-b`/gid `c` OR endpoint `a`/gid `b-c`, §13.9). */ export declare function eventReaderDurable(uid: string, endpoint: string, grantId: string, n: number): string; /** `recD = rec_--` — one per granted record subtree (grammar as {@link eventReaderDurable}; * `` separator-free, `` `-`-free, `` digits, so the single soft component is bounded). */ export declare function recordReaderDurable(uid: string, grantId: string, n: number): string; /** The canonicalizer's durable on EPJ (`canon_`): every raw submission to one endpoint. * Acks only after the durable decision (and, for pool routes, after the enqueue), §13.4. */ export declare function canonConsumerConfig(space: string, endpoint: string, opts?: { ackWaitMs?: number; maxAckPending?: number; }): Partial; /** The endpoint's ONE shared effects durable on EPF (`eff_`, filter `epf..dec.>`): * instances pull-compete so each accepted decision effects exactly once live (at-least-once); * ack ONLY after the effect is durably recorded (§13.9 ack barrier). */ export declare function effectsConsumerConfig(space: string, endpoint: string, opts?: { ackWaitMs?: number; maxAckPending?: number; }): Partial; /** A record kind's writer durable on EPR (`recw_-`) — one principal and one * consumer PER KIND, never a single writer draining every kind (§13.9). The filter is DERIVED * from the kind's qualifier arity: a NATS `>` matches one-or-more tokens (it does NOT match a * bare parent), so a kind with ≥1 qualifier filters `….>` while a ZERO-qualifier kind * (a single space-wide record) filters exactly `…` — else the writer would miss every * write for that registered grammar. Takes the RecordKindDef so the arity cannot be guessed. */ export declare function recordWriterConsumerConfig(space: string, def: RecordKindDef, opts?: { ackWaitMs?: number; }): Partial; /** The timer writer's durable on EPT_REQ (`timerw_`, full-tail filter on `.schedule`). * The writer validates each request (rejecting any client scheduling header and any * stale-generation request) before publishing the authoritative `.armed` on EPT. */ export declare function timerWriterConsumerConfig(space: string, opts?: { ackWaitMs?: number; }): Partial; /** A pool's durable on the EPW WorkQueue (`pool__`, exact filter * `epw...>`) — provisioner-pre-created; the owning endpoint binds it (§13.5). Exact * per-pool filters keep WorkQueue consumers non-overlapping by construction. `ack_wait` is * ONLY the broker's redelivery-to-owner timer; the authoritative lease deadline lives in the * owner's lease record (§13.12). */ export declare function poolConsumerConfig(space: string, endpoint: string, pool: string, opts?: { ackWaitMs?: number; }): Partial; /** A caller's decision-reader durable on EPF (`dec_-`, exact filter on the caller's * own `dec` triple) — pre-created PULL by the provisioner at capability mint; owned and bound * by the READ MEDIATOR, never the caller (§13.9 mediated reads). */ export declare function decisionReaderConfig(space: string, endpoint: string, caller: EpCaller, opts?: { ackWaitMs?: number; }): Partial; /** A caller's goal-result durable on EPF (`goal_-`; grammar as {@link decisionReaderConfig}). */ export declare function goalReaderConfig(space: string, endpoint: string, caller: EpCaller, opts?: { ackWaitMs?: number; }): Partial; /** `eveD = eve_---` — one per GRANTED event subtree (§13.9): a PULL durable the * provisioner pre-creates with the capability's EXACT full-tail event filter, bound by the read * mediator (never the caller). `subtree` is the granted `cotal..epe.…` tail verbatim * (`` is its zero-based index within the grant, sorted at mint). Live event progress is the * caller's own core subscription; this durable is the mediator's catch-up reader. */ export declare function eventReaderConfig(space: string, args: { uid: string; endpoint: string; grantId: string; index: number; subtree: string; }, opts?: { ackWaitMs?: number; }): Partial; /** `recD = rec_--` — one per GRANTED record subtree (§13.9): a PULL durable over the * records KV stream (`KV_cotal_records_`), pre-created by the provisioner with the * capability's EXACT full `$KV.cotal_records_.…` subtree tail, bound by the read * mediator. `` is the subtree's zero-based index within the grant. */ export declare function recordReaderConfig(space: string, args: { uid: string; grantId: string; index: number; subtree: string; }, opts?: { ackWaitMs?: number; }): Partial; /** The backing JetStream STREAM of the records KV (its grant rows key on `KV_`, §13.9). */ export declare function recordsKvStreamName(space: string): string; /** The canonicalizer principal's EPJ rows: it OWNS its durable (create) and consumes + acks it. */ export declare function canonicalizerGrants(space: string, endpoint: string): string[]; /** The canonicalizer principal's POOL-ROUTE rows (§13.9 matrix "Work-pool enqueue" + * "Work-pool reconciliation probe"): the `epw..>` enqueue publish (create-per-subject rides * the `Nats-Expected-Last-Subject-Sequence: 0` header, §13.6) and the FENCING leader-served * `STREAM.MSG.GET` reconciliation read the §13.6 predicate and the enqueue's CAS-loser * byte-identity check both require (EPW is `allow_direct=false`, so this is the ONLY read * path). The MSG.GET form is BODY-selected (no per-subject confinement in the grant), so these * rows are TRUSTED-canonicalizer-only: never on the pool owner (bind-only, * {@link poolOwnerBindGrants}), never on any caller, observer, or admin profile. The full * canonicalizer aggregate for an endpoint with pool routes is * `[...canonicalizerGrants(...), ...canonicalizerWorkGrants(...)]`. */ export declare function canonicalizerWorkGrants(space: string, endpoint: string): string[]; /** A run's journal replay durable on WFJ (`wfj_`, filter the run's own subject): the * driver reads the run's entries in append order to reproduce the deterministic prefix. Filtered * to ONE run, because a driver that can read every run's journal can read every run's effect * results, and a journal entry carries what an agent said. * * It is named rather than ephemeral so the create row can pin the durable AND the filter (an * ephemeral's server-generated name would need a `*` in the name token), and it is DELETED and * recreated at every takeover rather than resumed — see `replayRunJournal`: a durable remembers * how far it delivered, and a successor needs the prefix from the top, so a reused one would hand * it the empty tail. That is why the driver's grants carry a delete row. */ export declare function runJournalConsumerConfig(space: string, runId: string, /** * A token unique to ONE takeover attempt. A per-run durable is shared by contenders, and sharing * it makes replay a race with itself: `add` on an existing durable returns it, so one driver * inherits another's half-read consumer, and each contender's delete tears down the other's live * fetch. A consumer nobody else names cannot be inherited, nor deleted out from under its owner. * The grant pins this token EXACTLY — a consumer name is one * subject token and no pattern covers part of one — so it is chosen when the rows are minted, with * the lease, and not afterwards by the driver. */ takeoverId: string, opts?: { ackWaitMs?: number; maxAckPending?: number; inactiveThresholdMs?: number; }): Partial; /** * The RUN DRIVER's journal rows, minted per RUN and never per space. * * Publish on exactly one subject — the run's — plus the replay durable it owns on that same * subject, create, bind and DELETE, because every takeover recreates it to read the prefix from the * top. There is no row here for reading the subject's current sequence, and there does not need to * be one: the activation barrier's expectation is the last sequence the driver REPLAYED, so the * read it would otherwise make is the replay it makes anyway. * * There is no wildcard form of this on purpose. A space-wide `wfj.>` publish would let one run's * driver append to another run's journal, which is not a read leak but a corruption: the other run * would replay a step it never took. And the barrier's whole premise is that the run subject has * exactly one authoritative appender at a time; a grant that spans runs describes a different * system. */ export declare function runDriverJournalGrants(space: string, runId: string, takeoverId: string): string[]; /** * The durable that holds one `wait`'s position on a channel. Derived from the step's own request id, * which is why a resumed run finds the consumer its earlier attempt created rather than starting * again from "now", and why nothing about the wait has to be remembered across a crash. */ export declare function waitConsumerName(requestId: string): string; /** * The one definition of a wait's consumer, shared by the handler, by anything that has to recreate * it, and by the grant row that admits it (SPEC 14.6): a §13.9 family builder, so the create row * embeds exactly this filter and a body-selected one is refused at the broker. * * `deliver_policy: "new"` applies only to the FIRST create: an existing durable keeps its own * position, which is what a resume needs, and events from before the program asked are not this * wait's to see. */ export declare function waitConsumerConfig(space: string, requestId: string, channel: string): Partial; /** One wait's rows on the chat stream: create (filter pinned to its channel), bind, ack, delete. */ export declare function waitConsumerGrants(space: string, requestId: string, channel: string): string[]; /** The delete row alone: what a discharge holds for a cancelled wait whose channel it no longer * knows. A position nothing will read again is released, never re-read. */ export declare function waitConsumerReleaseGrant(space: string, requestId: string): string; /** The READ half of {@link runDriverJournalGrants}: one takeover attempt's replay durable, create * through delete, and no publish on the run's subject. What a reader of a run's journal holds * (the hosting manager's `run-status` / `run-answer`, SPEC 14.3) and exactly what a driver holds * beyond its append right. */ export declare function runJournalReplayGrants(space: string, runId: string, takeoverId: string): string[]; /** A serving instance's effects rows: BIND-ONLY on the provisioner-pre-created shared `eff_` * (INFO/MSG.NEXT/ACK, never create) — instances pull-compete, none owns the durable (§13.9). */ export declare function effectsBindGrants(space: string, endpoint: string): string[]; /** A per-kind record-writer principal's EPR rows: owns + consumes + acks its `recw_-`. */ export declare function recordWriterGrants(space: string, def: RecordKindDef): string[]; /** The timer-writer principal's EPT_REQ rows: owns + consumes + acks its `timerw_`. */ export declare function timerWriterGrants(space: string): string[]; /** A pool-owning endpoint's EPW rows: BIND-ONLY on the provisioner-pre-created `pool__` * (INFO/MSG.NEXT/ACK, never create — the bare create form is body-filter-selectable, §13.5/§13.9). */ export declare function poolOwnerBindGrants(space: string, endpoint: string, pool: string): string[]; /** The read mediator's BIND-ONLY rows for one caller-scoped reader durable, on the stream the * durable lives on (EPF for dec/goal, EPE for eve, `KV_cotal_records_` for rec). */ export declare function readerBindGrants(stream: string, cfg: Partial): string[]; /** One pre-created durable the provisioner owns: its stream + the config (durable + full-tail filter). */ export interface PreCreatedDurable { stream: string; config: Partial; } /** The provisioner's rows for a batch of pre-created durables (§13.9): the exact full-tail * CONSUMER.CREATE for every one it pre-creates, plus the matching CONSUMER.DELETE for * deprovisioning — and nothing else (it never consumes; owners bind). The create pins each * filter, so the provisioner can create ONLY the matrix's durables, not an arbitrary consumer. */ export declare function provisionerConsumerGrants(durables: PreCreatedDurable[]): string[]; /** The ADMISSION MEDIATOR principal's rows (§13.9 matrix "Acceptance obligation", §13.8): the * ONE writer of ITS endpoint's `oblig.` subtree (create-only winner + revision-pinned CAS; * the target position is a principal wildcard, the endpoint token is LITERAL) plus the * terminal-REJECTION publish on its own endpoint's create-only decision subjects, the fencing * leader reads (records / EPF / EPW `STREAM.MSG.GET`), and the §13.12 bind-time shape proof * (records `STREAM.INFO`). The reply inbox is connection-scoped (`_INBOX_.>`, never the * account-wide default): every JS API call is request/reply, so an account-wide inbox would * receive other principals' API replies. * * NO `CONSUMER.CREATE` on the records stream (SPEC 13.9, site 3 — nats-server#8274). A * consumer-create request BODY is not subject-ACL confinable: the extended * `CONSUMER.CREATE...` row this profile used to hold still admitted a * `durable_name` + PUSH `deliver_subject` body — a durable exporter of the endpoint's whole * `oblig.` subtree that SURVIVES this credential's connection and revoke (reproduced live). So the * mediator's drain-to-quiescence enumeration runs on the SEALED records scanner * ({@link ../../implementations/auth/src/records-scanner.ts openRecordsScanner}), a separate * self-minted credential the trusted process never hands out — this profile holds no consumer * lifecycle on the records stream at all. * * D32 residuals, EXPLICIT (accepted only for this trusted per-endpoint profile): (1) the * decision publish is payload-blind, so a compromised mediator can forge an ACCEPTANCE within * its own endpoint — an escalation to injecting executed work, never merely reject/stall — * because rejection-only is not subject-expressible (both decisions MUST share the create-only * decision subject for first-wins settlement); it can never forge beyond its endpoint. (2) its * own-endpoint `$KV...oblig` subject grant cannot enforce create-only/monotonic CAS: it can * overwrite a row to valid `terminal` and hide cleanup debt, or emit DEL/PURGE markers (the * latter fail loud as corruption; stream-level erasure is denied). (3) the body-selected * `STREAM.MSG.GET` fencing reads expose the records/EPF/EPW streams space-wide, and raw JS API * requests carry a caller-selected reply subject, so a compromised mediator can direct fetched * API/message bytes onto a foreign rail (confused-deputy injection, not foreign read access). * The former consumer-create durable-export reach is CLOSED: enumeration moved to the sealed * records scanner and this profile holds no records-stream CREATE. */ export declare function admissionMediatorGrants(space: string, endpoint: string, connId: string): { publish: string[]; subscribe: string[]; }; /** The RETIREMENT CLEANER principal's rows (§13.9 matrix "Terminal pool cleanup", §13.1 * barrier): minted per (retirement op × endpoint) with the EXACT pools the op intent * enumerates — never a pool wildcard, never space-wide EPW rights. Per listed pool: BIND-ONLY * on the provisioner-pre-created durable (INFO/MSG.NEXT/ACK, never create/update/delete). Plus * the leader-served EPF `STREAM.MSG.GET` its terminal-observe and acceptance re-bind reads * require — a STREAM-level grant whose read exposure is space-wide; that residual is EXPLICIT * per D32 and accepted only for this trusted, bounded-lived, per-op profile. The cleaner holds * NO terminal-publish or lease authority: the op-bounded executor CASes the lease and publishes * its derived terminal, and the cleaner re-reads it before ACK. D32 residuals: raw ACK cannot be * conditioned on a prior terminal, so compromise can suppress listed-pool work terminal-free; * the space-wide EPF `STREAM.MSG.GET` exposes fact content, and its caller-selected reply can * inject fetched API/message bytes onto a foreign rail. The reply inbox is * connection-scoped (`_INBOX_.>`, never the account-wide default). NO `epw.>` publish, * NO consumer create/update/delete, NO raw stream DELETE. The profile is revoked and its * principal cluster-verified-evicted by the barrier BEFORE any frontier records (§13.1). */ export declare function retirementCleanerGrants(space: string, endpoint: string, pools: string[], connId: string): { publish: string[]; subscribe: string[]; }; /** The endpoint's COMMIT PRINCIPAL rows (§13.9 matrix "Result/receipt/terminal/resume facts" + * "Claim / action / checkpoint commits"): the enumerated commit fact families on its OWN * endpoint — `goal.*.*.*.*.result` (the goal terminal; the `.bind` leaf under `goal.>` is the * canonicalizer's), `eff.>`, `receipt.>`, `wrk.>`, `cp.>` — and **never `dec.>`/`quar.>`** * (canonicalizer-only; structurally absent from these rows, not merely unused), plus its own * record keys per the §13.7 writer table (`goal..>`, `cp..>`, `lease..>`; the endpoint * qualifier is the FIRST qualifier of all three kinds, so the prefix is subject-expressible). * Read-back is FENCING and therefore leader-served (§13.9 read service): body-selected * `STREAM.MSG.GET` on `EPF_` (create-only CAS emission + idempotent re-commit decisions * over exactly its five fact families) and on `KV_cotal_records_` (the terminal-commit's * spec read and the epoch/deadline currency reads) — the follower-served `DIRECT.GET` forms are * deliberately NOT granted. The reply inbox is connection-scoped (`_INBOX_.>`, never * the account-wide default). * * D32 residuals, EXPLICIT (accepted only for this trusted per-endpoint profile): (1) every * fact publish is payload-blind create-only, so a compromised commit principal can forge an * in-endpoint `wrk`/`goal…result` terminal or `cp` resume for work that never ran — an * escalation to fabricating completed work within its own endpoint, never beyond it; (2) its * raw `$KV` subject grants cannot enforce the per-key CAS/monotonic discipline, so it can * overwrite its own endpoint's goal/cp/lease rows (DEL/PURGE markers fail loud as corruption; * stream-level erasure is denied by the store shape, §13.12); (3) the two body-selected * `STREAM.MSG.GET` fencing reads expose the EPF and records streams space-wide, and a raw JS * API request carries a caller-selected reply subject, so compromise can direct fetched * API/message bytes onto a foreign rail (confused-deputy injection, not foreign write). */ export declare function commitPrincipalGrants(space: string, endpoint: string, connId: string): { publish: string[]; subscribe: string[]; }; /** The SELF-MEDIATED GOAL-WRITER profile (P2 item 2 "spawn becomes an action"): a standing * connection that both BINDS a goal at accept AND COMMITS its terminal, for an endpoint that * accepts action goals INLINE on its ephemeral serve handler (Model B) rather than through a * separate canonicalizer + effects executor. It is exactly {@link commitPrincipalGrants} (the * `goal.*.*.*.*.result` terminal + `$KV..goal..>` record write + the two leader-served * `STREAM.MSG.GET` fencing reads the substrate uses) PLUS the ONE row commitPrincipalGrants * deliberately leaves to the canonicalizer — the goal `.bind` leaf * (`epf..goal.*.*.*.*.bind`) — so this single principal owns the whole `accepted → terminal` * goal-fact chain of its OWN endpoint. The endpoint's SERVE credential * ({@link import("./endpoint-grants.js").epServePublishRows}) holds NONE of these: a serve * connection is broker-DENIED every goal write, which is the item-2 privilege separation (the * dedicated writer is minted on a distinct connection, the serve rails stay serve-only). All of * commitPrincipalGrants' D32 residuals carry unchanged (payload-blind create-only publish; raw * `$KV` cannot enforce the per-key CAS the substrate layers on). **THREE body-selected * `STREAM.MSG.GET` reads, not two**: EPF and records space-wide from the commit-principal base, * PLUS the own-gate read on the AUTHORITY store (`KV_cotal_auth_`) added here. That third * one is the widest of the three and it was missing from this list while the builder emitted it — * a residual you do not name is a residual nobody weighs. It makes this the only endpoint-side * principal that reads the credential/gate store, which is why the D32 matrix audit now carries it * as an explicit holder-set entry. The reply inbox is connection-scoped * (`_INBOX_.>`). The `eff`/`wrk`/`receipt`/`cp`/`lease` families in the commit-principal * base are inert for a goal-only endpoint (the manager writes none) but are the commit-principal * profile's standard ceiling; a tighter goal-only ceiling is a follow-up if the panel prefers it. */ export declare function goalWriterGrants(space: string, endpoint: string, connId: string): { publish: string[]; subscribe: string[]; }; /** The manager's SESSION-LEDGER rows (P2 item 6): the standing connection that owns the §13.6 * session ledger and NOTHING else. It holds NO session rail — not the wildcard it used to hold, * not an exact one. That is the whole point of the split. * * §13.6 gives the ledger a job the byte rails do not have: it is "a DURABLE named authority that * survives the serving endpoint", the thing that still knows what to revoke after the endpoint * serving a session is gone. So it is standing and renewable, while the rails it records are * per-session, exact-subject, and die with their session ({@link import("./provision.js").Profile} * `session-serving` / `session-caller`). An earlier revision fused the two into one standing * credential carrying `eps..*..{in,out}`, which contradicted §13.9:2753 ("no * standing EPS grant exists on either side") and let one credential read and write every live * session's bytes at that epoch. Splitting on the lifetime boundary is what removes the wildcard: * the standing half no longer has rails to widen. * * Its store is the DEDICATED {@link sessionsBucket}, NOT the auth bucket. The write is * `$KV..session.*` (create-only CAS + revision-pinned update; `sessionLedgerKey` is the * single-token `session.`). The read is a bucket-blind leader `STREAM.MSG.GET` (allow_direct= * false, so `kv.get` is a body-selected read that cannot be key-pinned) — but the dedicated bucket * holds ONLY `session.>` rows, so that blind read exposes nothing but session ledger state, * structurally closing the §13.9 subject-blindness the auth bucket carries (creds + gates). The * reply inbox is connection-scoped. NO auth-bucket, records-bucket, or messaging-plane grant. * * NAMED RESIDUAL: `session.*` is one token wide and carries no endpoint component, because a * `sessionId` is an opaque unguessable token with no endpoint inside it. So this credential can * read and CAS any session row in its space, including another endpoint's. That was equally true * of the credential it replaces; it is not a regression, and it is confined to ledger STATE — row * state and credential ids — never to session bytes, which now require a per-session credential * this profile cannot mint. */ export declare function sessionLedgerGrants(space: string, connId: string): { publish: string[]; subscribe: string[]; }; /** The CONTRACT PUBLISHER principal's rows (§13.9 matrix "Contract-artifact publication" + * the trusted-infra half of "Contract-artifact read"): publish `epc.*` (the digest-hex is ONE * subject token; create-only rides `Nats-Expected-Last-Subject-Sequence: 0` at the typed path, * §13.7 — the grant cannot express it, the broker CAS enforces it) and the subject-confined * follower read-back `DIRECT.GET.EPC_.cotal..epc.>` (NON-fencing by design: * artifacts are content-addressed and verify-on-read is the tamper boundary, §13.7, so a * stale replica serves nothing forgeable). NO `STREAM.INFO`: the deny_delete/deny_purge shape * proof is the provisioner's (§13.12), not this profile's. The reply inbox is * connection-scoped (`_INBOX_.>`, never the account-wide default). * * D32 residuals, EXPLICIT: (1) the `epc.*` publish is payload-blind — a compromised publisher * can publish garbage artifacts at NEW (previously-unused) digest subjects (verify-on-read refuses * to SERVE non-canonical or digest-mismatched bytes, so this is a bounded storage flood carrying * no authority). It can NOT overwrite, shadow, or replace an EXISTING published artifact: the EPC * store's shape ({@link ensureContractStore}: `max_msgs_per_subject:1` + discard-new-per-subject) * makes a second publish to an occupied digest subject broker-REJECTED regardless of the * create-only header, so per-subject immutability is broker-enforced, not publisher-cooperative. * (Earlier revisions relied on the cooperative create-only header alone and a fail-closed read, * which the panel's live repro showed a non-cooperative publisher could defeat by raw append — a * permanent shadow-DoS; the stream shape + the create-only-winner read fallback close it.) (2) the * raw JS API request carries a caller-selected reply subject (the same confused-deputy injection * class as every API-holding profile). */ export declare function contractPublisherGrants(space: string, connId: string): { publish: string[]; subscribe: string[]; }; //# sourceMappingURL=endpoint-binding.d.ts.map