/** * v0.4 record-contract helpers (SPEC §13.4 "Record", §13.7 kinds, §13.9 writer table, §13.12 * binding) — the registered kind table with its pinned key grammars, the split-key CAS write * discipline, and the merged read/watch with the §13.4 staleness rules. * * A record is stored in the per-space `cotal_records_` KV as TWO keys with independent * revisions, `.spec` and `.status` — the split IS the broker-enforced writer boundary * (each writer role holds publish authority on its own key only). The single exception is the * lifecycle alias HEAD, one atomic unsplit key. Writes are per-key CAS and a lost race is a * LOUD `conflict` the caller re-reads and re-decides (§13.8) — deliberately unlike the ACL * registry's internal retry, because record writers are fenced principals whose lost CAS is a * decision point, never a last-writer-wins policy. * * These are the primitives the mediated writer principals (§13.9) build on; nothing here grants * anything. Untrusted callers never hold raw reads on this bucket — their reads ride the * mediator (§13.9 "Mediated reads"); the helpers below run inside trusted principals. */ import { type KV } from "@nats-io/kv"; import type { JetStreamManager } from "@nats-io/jetstream"; /** The per-space records bucket (§13.12): per-key CAS; `allow_direct=true`, but the lifecycle * head and every FENCED read are leader-served `STREAM.MSG.GET` (§13.9) — helpers here are the * non-fencing paths; a fence is always a revision-pinned CAS WRITE, never a read. */ export declare function recordsBucket(space: string): string; /** Open the records bucket. Auth mode OPENs the bucket pre-created at space setup; a privileged * caller passes `{ create: true }` to CREATE it (setup only). Mirrors {@link openAclRegistry}. */ export declare function openRecordsBucket(nc: import("@nats-io/transport-node").NatsConnection, space: string, opts?: { create?: boolean; }): Promise; /** LEADER-SERVED read of one records-KV key: `STREAM.MSG.GET last_by_subj` on the bucket's * backing stream, never `kv.get`/`DIRECT.GET`. A FENCING read — a fresh-check a mediated * writer acts on before publishing authority state — needs read-your-writes against the * leader; the bucket keeps `allow_direct=true` for the non-fencing paths, and a * follower-served Direct Get may answer with a superseded revision, which is exactly the * staleness a fence must not carry (§13.9, the same rule as {@link readLastFact}). * `undefined` = the key has never been written. A DEL/PURGE marker is a DELETION, never * absence — fail-closed refusal, the caller reconciles the store. */ export declare function readRecordLeader(jsm: JetStreamManager, space: string, key: string): Promise<{ value: unknown; revision: number; } | undefined>; /** One qualifier token between the kind token and the `.spec`/`.status` suffix. */ export interface RecordQualifier { name: string; /** Token validator/normalizer — fail-loud, same validators as the subject grammar. */ assert: (v: string) => string; } /** A registered record kind: its pinned key grammar, writer roles, and mediation class — * grants and merged watches are DERIVED from this entry (§13.7), so two implementations * always agree on which key carries what. */ export interface RecordKindDef { /** The wire kind token (single-label names are core-reserved; third-party kinds are * reverse-DNS, tokenized `.`→`_` exactly like endpoint names). */ kind: string; qualifiers: RecordQualifier[]; /** `.spec`/`.status`-split — every kind EXCEPT the lifecycle alias head (§13.7/§13.9). */ split: boolean; /** Writer PROFILES per the §13.9 writer table (declarative names; grant generation reads * them). For the unsplit head both name the one committing principal. */ writers: { spec: string; status: string; }; mediation: "mediated" | "direct"; } /** The lifecycle alias HEAD (§13.7/§13.9): `lifecycle..` — ONE atomic unsplit * key, the authoritative current mapping and the only `mappingRevision` source. Activation * and terminal retirement serialize on its CAS; a fresh authority read of it is a * leader-served `STREAM.MSG.GET` by the trusted mapping-reader, never a follower-served get. */ export declare const LIFECYCLE_HEAD: RecordKindDef; /** The §13.1 space-global UID RESERVATION (§13.7): `uid.` — ONE atomic unsplit * key, create-only and NEVER-DELETED for the life of the space. The KEY is the reservation * (the value records the reserving authority + intended alias, audit only): the minting * authority wins this create BEFORE any gate or head write, a create conflict BURNS the * candidate (the alias head alone cannot reject the same UID under a different alias, and * the gate./cred. families key by UID alone), and a DEL/PURGE marker is corruption, never * reusable absence. */ export declare const UID_RESERVATION: RecordKindDef; /** The fixed sentinel target token for an admission with no target lifecycle (§13.7/§13.8). */ export declare const OBLIGATION_EP_SENTINEL = "ep"; /** The §13.8 TARGET-INDEXED ACCEPTANCE OBLIGATION (§13.7): one atomic unsplit key per * acceptance identity, `oblig......` — the ONE * durable serialization coordinate on which a durable acceptance/start contends with its * authority head's movement (no cross-stream CAS exists). Target-first, so a retirement * barrier enumerates `oblig..>`; an admission under policy with NO target lifecycle * keys the row with the fixed sentinel target token `ep` (which the §13.1 UID grammar can * never produce), excluded from retirement drains and included in the endpoint's policy drain * via `oblig.*..>`. Create-only winner, monotonic value states * (`provisional → accepted → terminal` | `provisional → rejected`), NEVER-DELETED. Writer: * the admission mediator ONLY (§13.9; the canonicalizer holds no raw `oblig.` grant). */ export declare const OBLIGATION: RecordKindDef; /** The §13.6 IMMUTABLE ADMISSION-POLICY VERSION (§13.7): `policy..` — one * atomic unsplit key per policy version, create-only, NEVER-DELETED, never overwritten. * `` is the SHA-256 hex of the record's canonical value bytes, so the key is * SELF-CERTIFYING: a reader re-digests the value it read and refuses a mismatch. The govern * head's `enforcedPolicyKey`/`pendingPolicyKey` name keys of exactly this kind, which is what * keeps both the enforced and the pending policy readable through a mutation's whole drain * window (§13.6). Writer: the provisioner registration path ONLY (§13.9). */ export declare const POLICY_VERSION: RecordKindDef; /** The §13.1 PER-STREAM RETIREMENT FRONTIERS (§13.7): `frontier.` — ONE atomic * unsplit key per retired lifecycle, create-only, NEVER-DELETED, written by the terminal * retirement barrier AFTER the obligation drain, the pool cleaner, and the cleaner-credential * revoke+evict, and BEFORE the gate/head terminals (§13.1 order). The value records the * retirement `opId` and each bounded stream's last sequence at retirement — the cutoffs that * bound the predecessor's half-open interval `(activationFrontier, retirementFrontier]`; they * are never a successor's start (a successor captures its OWN activation frontier). */ export declare const RETIREMENT_FRONTIER: RecordKindDef; /** The endpoint-wide GOVERNANCE HEAD (§13.7 "a self-published descriptor cannot strip, forge, * or downgrade a governed annotation"): `govern.` — ONE atomic unsplit key holding * the endpoint's MONOTONIC (append-only) BINDING governed-trait imposition per command, plus * the single in-flight registration's PROVISIONAL slot (endpoint-service.ts): the head is the * endpoint's registration linearization point — every registration CAS-takes the slot under * its frozen gate, holds it through spec publication, and promotes its impositions to binding * only after the publish commits. Governance is a HISTORY-bearing, endpoint-wide property, not * a per-instance descriptor state: once BOUND, an imposition persists across instances AND * across command removal (a tombstone), until an authorized revocation (the D18 * governance-consent artifact) lifts it — so a re-registration, a fresh instanceId, and a * remove→re-add cannot launder a strip. * * NORMATIVE STATUS: `govern` is NOT yet in the frozen SPEC's §13.7 kind table, §13.9 writer * matrix, or §13.12 records-bucket binding — it is implemented ahead of a pending P0 * reconciliation decision (the spec's governance-continuity requirement implies a durable, * shared imposition record the frozen text does not name; multiple provisioners must read one * head, so a process-internal store cannot satisfy it). The amendment is recorded in the * control-surface STATUS; the operator decides spec changes. */ export declare const GOVERN_HEAD: RecordKindDef; /** The §13.7 core kinds, pinned. Keys: `.` then `.spec`/`.status`. */ export declare const RECORD_KINDS: Record; /** The canonical AUTHORITY-CONTROL record kinds (§13.9): the mapping head, the UID reservation, * the governance/policy heads, the acceptance obligation, and the retirement frontier. This is * the SINGLE SOURCE consumed BOTH by the registry below AND by the record-reader seam * ({@link ../endpoint-binding.ts}.recordReaderConfig): a caller reader durable may target NONE of * these authority-only subtrees (nats-server#8274, the sealed records scanner owns `oblig.`), so * adding a kind here extends registration AND the reader exclusion together — no parallel * hand-kept deny-list to drift. `lifecycle` is DUAL: its atomic HEAD (`LIFECYCLE_HEAD`) is * authority, while its deeper per-UID `RECORD_KINDS.lifecycle` detail is a caller-readable audit * record; the seam admits the detail but head-guards the atomic key. */ export declare const AUTHORITY_KIND_DEFS: readonly RecordKindDef[]; export declare function callerReadableRecordKind(kind: string): boolean; /** Register a third-party record kind. Reverse-DNS names ONLY — single-label kind names are * reserved for the kinds this module pins (§13.7); a re-registration throws, no silent * replacement. The kind name tokenizes `.`→`_` exactly like an endpoint name. */ export declare function registerRecordKind(def: Omit & { kind: string; }): RecordKindDef; /** The unsplit key of an atomic kind (the lifecycle head). Throws for a split kind. */ export declare function recordAtomicKey(def: RecordKindDef, qualifiers: string[]): string; /** The `.spec` key of a split kind. */ export declare function recordSpecKey(def: RecordKindDef, qualifiers: string[]): string; /** The `.status` key of a split kind. */ export declare function recordStatusKey(def: RecordKindDef, qualifiers: string[]): string; export interface ParsedRecordKey { def: RecordKindDef; qualifiers: string[]; part: "spec" | "status" | "atomic"; } /** Parse a records-bucket key against the registry. `null` = no registered grammar matches — * fail-closed, MUST NOT be handled (an unknown kind throws at the caller, no silent fallback). * Split-only trust model as in the subject parser: shape and registry dispatch here; token * grammars were enforced at build/mint time. */ export declare function parseRecordKey(key: string): ParsedRecordKey | null; /** JetStream's expected-last-subject-sequence failure — the ONE broker condition behind every * create-only/revision-pinned CAS loss here and in the journal (§13.4/§13.8). Keyed on the * STRUCTURED `err_code` (`JetStreamApiCodes.StreamWrongLastSequence` 10071 and its * `…Unknown` sibling 10164, the same pair the KV client's own create() classifies), never on * message text: wording varies across server versions and a missed classification would turn * a benign concurrent write into an unhandled throw inside a mediated writer. */ export declare function isCasLoss(e: unknown): boolean; /** Create-only write of one record key, CAS-fenced against the key's ENTIRE history (a * revision-0 expectation on the subject): an existing key OR a DEL/PURGE tombstone is a loud * `conflict`. Deliberately NOT the KV client's own `create()`, which silently RECREATES over a * tombstone — that would let whoever can delete a key re-open a one-use identity (rebind a * settled checkpoint's holder, reset a decided lease, resurrect a terminal goal). Here a * deletion permanently CLOSES the key (§13.8's create-only discipline; deletion is fail-closed * state, never absence), and this CAS is the arbiter — a caller's marker pre-check is only a * fast path, since a delete landing between check and create loses here, not there. Residual: * a tombstone COMPACTED out of the bucket's history is indistinguishable from true absence at * the broker; nothing in core compacts or deletes records keys, so that is operator store * surgery — reconcile-the-store territory. Any other broker failure propagates untranslated. * Returns the created revision. */ export declare function createRecordEntry(kv: KV, key: string, value: unknown): Promise; /** Revision-pinned CAS update of one record key. A moved revision is a loud `conflict`; any * other broker failure propagates untranslated. Returns the new revision. */ export declare function updateRecordEntry(kv: KV, key: string, value: unknown, expectedRevision: number): Promise; /** Revision-pinned CAS DELETE of one record key — the write behind an explicit deregistration * (§13.5: a deleted `svc` spec IS the deregistration). Pinned, never blind: between the read that * decided to remove a record and this delete, the very instance being removed may have written * again, and a blind delete would erase a live registration. A moved revision is a loud `conflict` * with the same re-read-and-re-decide remedy every other CAS here carries; any other broker * failure propagates untranslated. * * Applicable ONLY to the record kinds §13.5 says may be deleted. The lifecycle families are never * deleted, and {@link createRecordEntry} deliberately refuses to write over a tombstone so a * one-use identity can never be re-opened by whoever can delete a key. */ export declare function deleteRecordEntry(kv: KV, key: string, expectedRevision: number): Promise; /** Status values MUST carry `observedSpecRevision` (§13.4) — the merged-read staleness rules * key on it. Enforced at the write seam so a status without it can never exist. */ export declare function assertStatusValue>(value: T): T & { observedSpecRevision: number; }; export interface MergedRecord { spec: { value: S; revision: number; }; status?: { value: T; revision: number; observedSpecRevision: number; }; /** §13.4: `observedSpecRevision < spec.revision` is a STALE-BUT-VALID level-triggered * projection, not an error. `false` when status is absent or caught up. */ staleProjection: boolean; } /** Merged logical read of a split record (§13.4): both keys, both revisions, the staleness * classification. `observedSpecRevision > spec.revision` (a lagging spec read, possible across * replica freshness points) triggers bounded spec re-reads until caught up or the deadline — * the mismatched pair is NEVER returned. Absent record → `undefined`; a status without its * spec is torn state → `failed-precondition`. */ export declare function readRecord(kv: KV, def: RecordKindDef, qualifiers: string[], opts?: { deadlineMs?: number; }): Promise | undefined>; /** Read an atomic (unsplit) record — the lifecycle head. NOT a fence: an authority read of the * head (mapping currency before effect) is a leader-served `STREAM.MSG.GET` by the trusted * mapping-reader (§13.9); this helper is the ordinary non-fencing read, and every fence is a * revision-pinned CAS write against the returned revision. */ export declare function readAtomicRecord(kv: KV, def: RecordKindDef, qualifiers: string[]): Promise<{ value: V; revision: number; } | undefined>; /** Watch a split record: the current merged snapshot first, then a re-merged view per delta. * * Cursor discipline (§13.4/§13.8): ONE ordered consumer supplies BOTH the snapshot (the * per-key last values the watch replays first) and the deltas after it — the cursor is the * consumer's own position, so nothing between "snapshot" and "watch start" can ever be * skipped. (A cursor derived from independent `get` reads — `max(specRev,statusRev)+1` — is * provably gap-prone: mixed-freshness/TOCTOU reads let a higher revision on one key jump the * resume point past an unseen update on the other.) Replay entries accumulate silently; the * first merged view is yielded when the replay completes, and every later delta re-yields the * merged view. A watcher that errors, ends, or observes a status ahead of its cached spec * (impossible from honest writers in an unbroken ordered watch) RESYNCS with a fresh consumer * — a fresh full snapshot, duplicates tolerated, never a patch across a gap. * * Resyncs are budgeted on CONSECUTIVE no-progress incarnations, reset once an incarnation * delivers at least one ordered post-snapshot delta (a lifetime-cumulative budget would kill a * long-lived watch on accumulated benign blips; a snapshot alone must not count as progress or * an immediately-ending iterator would spin forever). Ends when the spec key is deleted (the * record is retired — a watch started on an already-retired record ends immediately) or when * `signal` aborts. A status whose spec key NEVER existed in the consistent replay view is real * torn state (`failed-precondition`) — the single-consumer view cannot false-positive this. */ export declare function watchRecord(kv: KV, def: RecordKindDef, qualifiers: string[], opts?: { signal?: AbortSignal; maxResyncs?: number; }): AsyncGenerator, void, void>; //# sourceMappingURL=endpoint-records.d.ts.map