/** * v0.4 service registry (SPEC §13.7 "Descriptor and describe", §13.5 scatter freeze, §13.9 * writer table) — the `svc` record kind's value shapes with their consuming-boundary * validators, service-name authority enforcement, the mediated registration and epoch-fenced * status writes, and the scatter expected-set freeze. * * Registry entries are DISCOVERY, never authority (§13.9): nothing here grants subscribe or * reply authority or scatter membership by itself — the serve credential is the authority, and * a foreign credential cannot subscribe a class rail, answer as an instance, or enter a frozen * scatter set. The helpers below run inside the trusted writer principals the §13.9 writer * table names (`provisioner-registration` for spec, `instance-commit-epoch-fenced` for status). */ import type { KV } from "@nats-io/kv"; import type { JetStreamManager } from "@nats-io/jetstream"; import { type EpAuthzMode } from "./endpoint-subjects.js"; import { type EpClass } from "./endpoint-envelope.js"; import { type DescribeDescriptor } from "./endpoint-cluster.js"; import { type SupervisorWriteGrant } from "./endpoint-supervisor.js"; import type { EpRegistrationState } from "./endpoint-verbs.js"; import type { EndpointRepairCursor } from "./lifecycle-state.js"; /** The `svc….spec` value: the instance's registered descriptor identity. The spec KEY's store * revision is the instance's `registrationRevision` (§13.7): it advances only when the * mediated registration path writes the key, so an advance during a scatter is exactly a * re-registration. */ export interface ServiceSpec { endpoint: string; /** The serving owner — determined by the NAME (§13.2 single-owner names), recorded here. */ owner: string; endpointType?: string; /** Complete-closure digests of the served cluster documents (§13.7). */ clusterDigests: string[]; /** The discovery protocol version — additive evolution only (§13.7). */ protocol: { v: 1; }; /** Virtual-endpoint activation policy (§13.6), opaque to the registry. */ activation?: Record; } /** The `svc….status` value: the instance's own convergence projection, written epoch-fenced * through its `epr` rail (§13.9: the writer reads the epoch from the broker-authenticated * subject, never from payload). `state` is a bounded token; readers key on * {@link SERVICE_READY}/{@link SERVICE_EXITED} (§13.6: an entity's convergence is observable * on its own status record). */ export interface ServiceStatus { epoch: number; state: string; observedSpecRevision: number; [key: string]: unknown; } /** The convergence states the SPEC keys on (§13.6 item 6). */ export declare const SERVICE_READY = "ready"; export declare const SERVICE_EXITED = "exited"; /** Restart-intensity escalation (§13.6 virtual endpoints): the instance stops restarting and * the lifecycle retires terminally; readers treat it as permanently not-startable. The state * is IRREVERSIBLE at {@link writeServiceStatus}: no later status write (any epoch) replaces * it — the only touch a stored escalated row admits is the supervisor's own revision-pinned * retirement mark, written directly by the reconciler, never through this writer. */ export declare const SERVICE_ESCALATED = "escalated"; /** The SUPERVISOR-OWNED status fields (§13.6 restart intensity): the durable restart history * and the retirement-complete mark. {@link writeServiceStatus} carries them forward on every * INSTANCE-side write and strips whatever the caller supplied; ONLY a holder of the branded * {@link SupervisorWriteGrant} may originate them or the `escalated` state. */ export declare const SERVICE_RESTART_HISTORY_FIELD = "restarts"; export declare const SERVICE_RETIRED_MARK_FIELD = "retiredAt"; /** The AUTHORITY to originate supervisor-owned state (the restart history, the retirement mark, * the `escalated` state) through {@link writeServiceStatus} — the type only. The MINT lives in * the package-internal `endpoint-supervisor` module (never re-exported), so it is not * ambiently obtainable: possession of a genuine grant proves the write came from a §13.6 * supervisor seam, not a scalar flag any caller could set. */ export type { SupervisorWriteGrant }; /** The §13.6 virtual activation policy (`spec.activation`), a CLOSED schema: `mode` is the * literal `on-demand` and `capacity` (the pool admission bound) is REQUIRED — an unbounded * pool is not a policy, and a free-floating capacity knob unbound from the registration was * the drift the panel refused. The restart knobs default per SPEC (3 within 60s). */ export interface VirtualActivationPolicy { mode: "on-demand"; capacity: number; maxRestarts?: number; restartWindowMs?: number; } export declare function parseActivationPolicy(raw: unknown): VirtualActivationPolicy; /** Validate a `svc….spec` value at its consuming boundary (§13.3: every plane is * runtime-validated; mediated-writer state that does not validate is a writer bug, never a * data error). The body's endpoint must AGREE with the key's endpoint qualifier. */ export declare function parseServiceSpec(raw: unknown, key: { endpoint: string; }): ServiceSpec; /** Validate a `svc….status` value at its consuming boundary. */ export declare function parseServiceStatus(raw: unknown): ServiceStatus; /** The deployment's name-authority source (pluggable — identity is an adapter, §13.9): core * single-label names require operator provisioning authority; reverse-DNS names bind to their * REGISTERED domain owner. The answer comes from the deployment's trusted registry, never from * the registrant's claim. */ export interface ServiceNameAuthority { /** ONE atomic leader-served authority decision for `(name, owner)` (§13.9), returning the * authorization result AND the name-authority binding revision from a SINGLE read so the two * can never TEAR across a concurrent transfer (a read is never a fence, §13.1; the returned * revision is a THIRD currency dimension bound into the issuance gate and re-checked at mint, * so a transfer AFTER authorization can never release an old-owner credential). `authorized` * is true iff `owner` may serve `name`: for a core single-label name, iff `owner` holds * operator provisioning authority; for a reverse-DNS name, iff `owner` is the REGISTERED * domain owner (an unregistered name is never authorized, fail-closed). `revision` advances * whenever the name transfers or its operator-authority grant changes. */ authorize(name: string, owner: string): Promise<{ authorized: boolean; revision: number; }> | { authorized: boolean; revision: number; }; } /** Enforce §13.9 name authority before a registration/serve grant is minted, from ONE atomic * snapshot: an endpoint name binds to exactly ONE owner (§13.2), so a registration claiming a * name its owner does not hold fails `permission-denied`, and an UNREGISTERED reverse-DNS name * fails closed. Returns the name-authority binding REVISION read atomically WITH the decision — * the caller binds it into the issuance gate so a transfer between decision and mint is fenced, * never a torn owner-vs-revision read. */ export declare function assertServiceNameAuthority(endpoint: string, owner: string, authority: ServiceNameAuthority): Promise; export declare function registerServiceInstance(kv: KV, args: { space: string; spec: ServiceSpec; instanceId: string; registrant: { owner: string; }; authority: ServiceNameAuthority; barrier: EpIssuanceBarrier; /** Content-store reader for the spec's cluster digests, REQUIRED (§13.7): governed * continuity is not an optional seam. It is the ONLY policy input - the governed set * itself is pinned internally to the canonical {@link GOVERNED_TRAIT_URNS} (the same * constant feeding serve-side enforcement), never a caller-supplied list: a tunable set * was the subset-narrowing escape (guarded-only wiring silently un-tracks priced), the * `previous:null` class one notch smaller. */ readClusterArtifact: (digest: string) => Promise | unknown; /** OPTIONAL observation seam for a FOREIGN instance's issuance-gate generation, consulted only * when another instance holds the endpoint's provisional governance slot (§13.7). It is a READ, * never a freeze, and it mirrors {@link deregisterServiceInstance}'s `observeGeneration`: core * holds this instance's own barrier and the RECORDS store, so it has no handle that reaches a * foreign `epgate` row, and a caller wires it with a credential already scoped for that read. * ABSENT = today's unconditional refusal, so no caller silently gains a reclaim it did not ask * for. See the orphan predicate at the slot-take below. */ observeHolderGeneration?: (holderInstanceId: string) => Promise | number; }): Promise<{ registrationRevision: number; }>; /** After holder-gone eviction, complete a frozen registration whose Phase-3 spec write committed * (gate.registrationRevision still names the pre-write spec). Returns `completed: false` when the * spec has not advanced, so the caller may abort-reopen. A failed spec/governance read stays frozen. */ export declare function completeFrozenRegistrationFromSpec(recordsKv: KV, args: { endpoint: string; instanceId: string; barrier: EpIssuanceBarrier; freezeToken: number; gate: { generation: number; processEpoch: number; registrationRevision: number; nameAuthorityRevision: number; }; }): Promise<{ completed: false; } | { completed: true; registrationRevision: number; processEpoch: number; }>; /** Write an instance's status with the FULL §13.9 writer fence. `epoch` is the * WRITER-AUTHENTICATED epoch — in production the record writer reads it from the * broker-authenticated `epr` subject (§13.9), never from the payload; this helper trusts its * caller to be that seam and additionally requires the payload to agree. The fence is * THREE-part, in order: * 1. a registered spec must exist and `observedSpecRevision` must not run AHEAD of it — a * spec-less status is the torn record state readers reject (§13.4), never written; * 2. the epoch must equal a FRESH read of the authoritative lifecycle mapping's * `processEpoch` (`expired` otherwise) — monotonicity against the stored status alone is * NOT sufficient: between the takeover CAS (N→N+1) and the completed revoke/evict barrier * the superseded N still equals the stored epoch (§13.9); * 3. a below-stored epoch is `conflict` (§13.9), distinct from the mapping fence. * `readProcessEpoch` is the trusted mapping-reader seam (leader-served, §13.9; the D13 * lifecycle registry provides the production reader). The racing CAS loss is a loud `conflict`. * `expectedStatusRevision` pins the CAS to the CALLER's observed status revision (0 = observed * ABSENT) for read-modify-write callers whose new value derives from the stored one (the §13.6 * restart-intensity history): without the pin, this function's own fresh internal read would * let two concurrent derivations silently merge-lose each other's contribution. It is PURELY a * CAS pin — the AUTHORITY to originate supervisor-owned state is the separate branded * {@link SupervisorWriteGrant} (`supervisor`), never revision presence. Without the grant this * is an instance-side write: it may not carry the restart history, the retirement mark, or the * `escalated` state (they are stripped on both create and update, and `escalated` refuses), * and the stored supervisor fields ride forward untouched. */ export declare function writeServiceStatus(kv: KV, args: { endpoint: string; instanceId: string; epoch: number; status: ServiceStatus; readProcessEpoch: () => Promise | number; expectedStatusRevision?: number; supervisor?: SupervisorWriteGrant; }): Promise; /** What a deregistration found and did. `removed: false` is a NORMAL outcome, not a failure: an * already-absent record and a record that moved under the read are both things a caller has to be * able to tell apart from a completed removal, and neither is worth a throw at this layer — the * operator verb refuses loudly on them, a manager's own clean-stop logs and carries on. */ export type ServiceDeregistration = { removed: true; specRevision: number; statusRevision?: number; } /** No live spec key at the coordinate: never registered, or already deregistered. */ | { removed: false; reason: "absent"; } /** A key moved between the read and its revision-pinned delete: something is WRITING to this * registration, so it is not the dead record that was inspected. Nothing was removed — the * status delete is attempted first precisely so this outcome leaves the record whole. */ | { removed: false; reason: "superseded"; } /** This instance currently holds the endpoint governance slot at the live gate generation * (a registration is in flight through spec publish and gate reopen). Nothing was removed. */ | { removed: false; reason: "registration-in-flight"; }; /** * DEREGISTER one service instance: the §13.5 explicit deregistration, which is the DELETE of its * `svc` spec key (and its status key with it). * * WHY THIS EXISTS AT ALL. The registry records REGISTRATION, not liveness, and nothing in the model * expires a row. An instance whose host dies leaves a record that claims a live state forever, and * every class scatter in the space then freezes that slot in and waits out the full deadline for an * answer that can never come. Registration therefore needs a way OUT that does not depend on the * dead instance's cooperation, and this is it. There is deliberately no automatic sweep behind it: * the two callers are an instance removing its OWN row on a clean stop, and an operator naming a * hard-dead instance explicitly. * * ORDER IS PART OF THE CONTRACT: status first, then spec. A reader that catches the pair mid-delete * sees "spec without status", which §13.4 already defines (an instance registered but not converged; * {@link freezeExpectedSet} skips it) and which {@link readRecord} reads cleanly. The other order * produces "status without spec", which is the TORN state readers refuse — a deregistration would * hand every concurrent reader a `failed-precondition` for the width of one round trip. * * BOTH DELETES ARE REVISION-PINNED to what this function just read. A blind delete of a registration * is a delete of whatever is there NOW, and what is there now may be a successor that re-registered * microseconds ago under the same instanceId — exactly the case a restart produces. A moved key * aborts with `superseded` and removes nothing. * * THE RECOVERY PATH, because a deregistration must never be a one-way door: the record is removed, * the §13.1 issuance gate is NOT. The same instance can register again and does so on its next * start — {@link registerServiceInstance} writes over the tombstone under a revision-pinned CAS and * advances the epoch as it does for any other restart. * * A LIVE GOVERNANCE SLOT AT THE CURRENT GATE GENERATION IS ALSO A REFUSAL. Registration holds that * slot from decision through reopen so a delete cannot invalidate the spec the freeze is completing. * `observeGeneration` is a READ of this instance's issuance-gate generation, never a freeze. The * observation is classified, not trusted as a typed number: * - throw, absent, or not a non-negative safe integer → fail closed (`unavailable`) * - equal to the slot generation → in-flight (refuse) * - strictly greater than the slot generation → leftover after reopen (delete proceeds) * - strictly less than the slot generation → ahead of the live gate (fail closed) * Non-equal is not "behind". Only `slot.generation < liveGeneration` is the permissive proof. */ export declare function deregisterServiceInstance(kv: KV, args: { endpoint: string; instanceId: string; /** Read this instance's live issuance-gate generation. A read, never a freeze. */ observeGeneration: () => Promise | number; }): Promise; /** One frozen scatter slot: `(instanceId, registrationRevision, epoch)` (§13.5). */ export interface FrozenInstance { instanceId: string; /** The `svc….spec` key's store revision at freeze time. */ registrationRevision: number; epoch: number; } /** Freeze the request-scoped expected set (§13.5): the LIVE instances of a class from the * service registry at send time — VALIDATED registered spec, status present and caught up to * the current registration (a stale projection is an instance not yet live under it, so * freezing `(new registrationRevision, pre-registration epoch)` would combine a registration * with liveness it never had), and not {@link SERVICE_EXITED}. An EMPTY or UNREADABLE registry * is `failed-precondition`, never an empty success (§13.5); a MALFORMED registry record fails * loud (`internal`, §13.9: readers fail loud on invalid mediated-writer state). The read grant * this runs under is a §13.9 matrix row. */ export declare function freezeExpectedSet(jsm: JetStreamManager, space: string, endpoint: string): Promise; /** The PRODUCTION `reconcileRegistration` hook for {@link epScatter} (§13.5): a bounded post-T * LEADER-SERVED read of every frozen slot's CURRENT `svc….spec` key (the same §13.9 read class as * the freeze). Per slot: a live spec is `{ registered: true, registrationRevision }` (the key's * CURRENT store revision — an advance past the frozen value is what the gather classifies as * `registration` churn); an absent OR deleted spec is the EXPLICIT `{ registered: false }` verdict * (a mid-scatter deregistration, §13.5: not churn). Only the spec KEY is read — the reconcile * compares registration currency, not liveness. A malformed spec fails loud (`internal`, §13.9); * an unreadable registry normalizes to `failed-precondition` (§13.5: never a fabricated verdict). * Leader-served so a follower-stale read can never miss an advanced revision and falsely retain a * counted reply (engineer/distsys). */ export declare function registrationReconciler(jsm: JetStreamManager, space: string, endpoint: string, frozen: readonly FrozenInstance[]): () => Promise>; /** The PRODUCTION `currentEpoch` hook for {@link epCall} on the `one` rail (§13.2): a LEADER-SERVED * read of the answering instance's CURRENT `svc….status` epoch. An unregistered instance or one * that never converged (no status) has no current epoch to verify a queue winner against and * refuses `failed-precondition` — the read's OWN failure, which {@link epCall} never mislabels as * responder staleness. Leader-served so a follower-stale status read can never accept a * just-superseded queue winner at an old epoch (engineer/distsys). A stale projection does not * refuse: `epoch` advances only through a takeover's status write (§13.5). */ export declare function serviceEpochReader(jsm: JetStreamManager, space: string, endpoint: string): (instanceId: string) => Promise; /** One command's VERIFIED registered authority: everything the serve boundary enforces about * the command, taken from a cluster document whose bytes hash to a digest the registered spec * names — never from a caller-supplied declaration. */ export interface EpCommandAuthority { /** The registered cluster (closure digest) that declares this command. */ clusterDigest: string; class: EpClass; targeted: boolean; /** Admitted authorization modes; empty exactly when untargeted (§13.7). */ modes: readonly EpAuthzMode[]; capability: string; inputDigest: string; outputDigest: string; /** Declared trait URNs (§13.7), out of the digest-verified cluster bytes; empty when the * declaration carries none. Governed entries (`ai.cotal.guarded`/`ai.cotal.priced`) are * what the serve boundary's pre-effect gate keys on; the rest are vocabulary. */ traits: readonly string[]; } /** The registry-authorized serve ARTIFACT {@link authorizeServeGrant} returns: ONE deep-frozen, * brand-registered value binding space, registered identity, epoch, owner, registration * revision, the FULL registered command set (§13.9: the instance credential binds its whole * registered surface; caller-specific scoping happens only in the response-time describe * answer, never in the registration), the digest-VERIFIED per-command surface, and the derived * descriptor — consumed by both the credential mint (`permissionsFor`/`mintCreds`, profile * `endpoint-serve`) and `serveEndpoint`, so neither ever accepts a raw spec/descriptor/command * list again. The registry stays discovery (§13.9); this seam is what turns a REGISTRATION * into serve authority. */ export interface EpServeGrant { space: string; endpoint: string; instanceId: string; epoch: number; /** The registered owner (the only principal this artifact mints for). */ owner: string; /** The `svc….spec` store revision the surface was verified at (§13.7 `registrationRevision`); * the mint's issuance fence refuses if the registration has advanced (a re-registration * supersedes the branded surface). */ registrationRevision: number; /** The name-authority binding revision the serving owner was verified against (§13.9); the * mint's issuance fence refuses if it has advanced (a name transfer supersedes the owner). */ nameAuthorityRevision: number; commands: readonly string[]; /** Command → its verified registered declaration. */ surface: Readonly>; /** DERIVED from the verified surface (never caller-asserted): true iff any registered command * is `class: "journal"` — the mint emits the shared `eff_` effects bind rows exactly then * (§13.9 "the credential also carries the effects bind"; an ephemeral-only endpoint gets * none, default-deny both directions). */ journalClass: boolean; /** The endpoint's owned work pools, sorted — PROVISIONING truth (the exact pools whose * `pool__` durables the provisioner pre-created), asserted by the authorizing * provisioner at this boundary because no registered record enumerates pool names (routes * are per-acceptance policy decisions, §13.6). Own-endpoint-confined by construction: every * emitted row names `pool__`, so a wrong pool name binds nothing foreign. */ pools: readonly string[]; /** The full authoritative descriptor describe publishes: DERIVED from verified registered * bytes, deep-frozen. */ descriptor: DescribeDescriptor; } /** Brand registry: authorized artifact → its immutable authorized snapshot. Like the §13.12 * consumer-config family bond, the brand (not structure) is what the consuming seams check, * so a structural copy or post-authorization mutation can never carry serve authority. */ interface AuthorizedServe { space: string; endpoint: string; instanceId: string; epoch: number; owner: string; registrationRevision: number; nameAuthorityRevision: number; commands: string[]; journalClass: boolean; pools: string[]; } /** * Reconstitute a serve artifact inside a TRUSTED host issuer after an explicitly typed remote * registration protocol has independently proved the registered records/gate coordinates and * digest-verified contract closure. This is not a caller shortcut: every field is validated here, * the caller still needs the data-account signer to mint, and the issuance gate is the release * fence. It exists because the normal brand is process-local and cannot cross the participant → * host protocol boundary as JSON. */ export declare function authorizeTrustedServeSnapshot(args: { space: string; endpoint: string; instanceId: string; epoch: number; owner: string; registrationRevision: number; nameAuthorityRevision: number; commands: string[]; surface: Record; descriptor: DescribeDescriptor; journalClass?: boolean; pools?: string[]; }): EpServeGrant; /** * Authorize a serve credential against the REGISTERED service (§13.9: serving is granted * authority, dual to calling — the registry is discovery, the serve grant is the authority). * Runs inside the provisioner. The fence, in order: * 1. the instance must be REGISTERED (its `svc….spec` record exists) — `failed-precondition`; * 2. the credential's holder must BE the registered owner (`permission-denied`), and the name * authority is re-checked FRESH (`permission-denied` on drift); * 3. every registered cluster is read through the two-stage §13.7 content-address protocol: * the MANIFEST is fetched at the registered CLOSURE digest and verified, `members` must be * empty (P1 single-document clusters; a non-empty closure is the D8 loader's, refused loud * until then), then the ROOT cluster document is fetched at `manifest.root` and verified. * The verified documents are the ONLY command source — the FULL union of their declared * commands is the surface (no caller subset; caller scoping is response-time describe). * `describe` is derived by the row builder, never a registered command; * 4. the epoch must EQUAL a fresh read of the authoritative mapping's `processEpoch` * (`expired`): a serve credential binds the CURRENT incarnation. * The returned artifact carries the verified surface, the derived descriptor, and the * registration revision. The MINT's fence is the durable issuance gate ({@link * finalizeServeIssuance}), NOT this authorization (a read is never a fence, §13.1): this seam * produces the surface, the gate serializes its release against takeover and re-registration. */ export declare function authorizeServeGrant(kv: KV, args: { space: string; endpoint: string; instanceId: string; epoch: number; holder: { owner: string; }; authority: ServiceNameAuthority; readProcessEpoch: () => Promise | number; /** The contract-store read seam (§13.7 digest subjects; the D8 tooling provides the * production reader): the ARTIFACT stored at a digest subject (`epc.`) — a * cluster MANIFEST at a closure digest, a cluster DOCUMENT at a root artifact digest — or * `undefined` when the store has no such artifact (fail-closed). */ readClusterArtifact: (digest: string) => Promise | unknown; /** The endpoint's owned work pools (PROVISIONING truth: exactly the pools whose durables * the calling provisioner pre-created; omitted = none). Validated tokens, no duplicates, * and only meaningful on a journal-class surface — a pool list on an ephemeral-only * endpoint is a caller bug and refuses loud. */ pools?: string[]; }): Promise; /** The brand check every consuming seam runs: `serve` must be the ARTIFACT * {@link authorizeServeGrant} returned, field-for-field equal to its authorized snapshot. A * structural copy, a raw literal, or a diverging value refuses — serve authority flows only * THROUGH the registry authorization. Returns the immutable snapshot (space/owner/epoch/ * registrationRevision the release fence checks against). */ export declare function assertServeGrantAuthorized(serve: EpServeGrant): AuthorizedServe; /** The mint-side CONTEXT binding (`permissionsFor`, profile `endpoint-serve`): brand + snapshot * equality plus the mint context bound to the artifact (same space, and the minted principal * IS the registered owner — an authorized artifact for space A/owner X emits rows for no other * space or principal). This is NOT the freshness fence: {@link finalizeServeIssuance} is, and * `mintCreds` runs it before releasing the credential. */ export declare function assertServeGrantMintable(serve: EpServeGrant, mint: { space: string; holderOwner: string; }): AuthorizedServe; /** The observed state of an instance's durable issuance gate (§13.1: the auth bucket's * `gate.`, leader-served with `allow_direct=false` so a read is read-your-writes, * never a follower's stale `open`). ONE key binds ALL THREE currency authorities the serve mint * depends on: `processEpoch` (advanced by a takeover barrier), `registrationRevision` (advanced * by a re-registration barrier), and `nameAuthorityRevision` (advanced when the endpoint NAME's * authority binding transfers, §13.9). `generation` is a monotonic freeze/reopen counter (every * barrier bumps it, so a superseded mint's rebuilt CAS loses even if two coordinates coincide). * `revision` is the KV store revision the mint's CAS and every barrier's freeze pin. */ export interface EpGateState { /** The gate's space. In production the gate physically lives in the per-space * `KV_cotal_auth_` bucket (§13.9:2393), so the space is the bucket and cannot be crossed; * carrying it here is defense-in-depth for the in-memory seam/fake, so a mint/registration * handed a gate constructed for another space is refused rather than trusting the caller wired * the right bucket. */ space: string; /** The gate's OWN instance identity, `(endpoint, lifecycleUid)` (§13.1). For an endpoint the * lifecycle identity is `instanceId`, which SPEC 13.1:1008-1013 makes unique only within * `(space, endpoint)` (its ≥128-bit CSPRNG entropy is what makes the SPEC's `gate.` * key collision-free within the space bucket). Binding the ENDPOINT here is the explicit * identity check that does not rely on that entropy: a caller that passes a DIFFERENT endpoint's * gate sharing the instance token (or any wrong gate) is refused, never confused, and the * credential family stays per-`(endpoint, instance)`. The durable keys carry the endpoint * explicitly: the normative DISJOINT endpoint families are `epgate..` * and `epcred...` (SPEC 13.9/13.12 — disjoint from the * agent `gate.`/`cred.…` families by PREFIX, never arity), so the key * derivation matches this check rather than leaning on the instance-token entropy alone. * The agent families stay endpoint-blind BY DESIGN (a lifecycle uid is space-globally * reserved, not an endpoint child). */ endpoint: string; lifecycleUid: string; /** The registered serving instance's CONNZ-attributable connection principal (`.` * dot-form, §13.1:1056-1069): the eviction target, and the value every `epcred` row MUST copy * as its `holderPrincipal`. The mint is bound to it — a credential whose minting `owner.actor` * is not this principal (a SIBLING ACTOR under the registered owner) cannot win the gate — so * the ledger/eviction target can never diverge from the registered serving principal. */ principal: string; state: "open" | "frozen" | "retired"; generation: number; processEpoch: number; registrationRevision: number; nameAuthorityRevision: number; revision: number; /** Present when the gate is frozen or retired: the op that owns the freeze / terminal. */ op?: { opId: string; kind: "activation" | "takeover" | "registration" | "retirement"; successor?: string; }; } /** The successor gate coordinate a barrier reopens at (§13.1): the three currency dimensions plus * the bumped `generation`. A re-registration advances `registrationRevision`; a takeover advances * `processEpoch`; a name transfer advances `nameAuthorityRevision`; each also bumps `generation`. */ export interface EpGateSuccessor { generation: number; processEpoch: number; registrationRevision: number; nameAuthorityRevision: number; } /** One staged credential-ledger row, durably keyed in the ENDPOINT family * `epcred...` (SPEC 13.9/13.12; disjoint by prefix from the * agent `cred..…` family, which stays endpoint-blind by design): written BEFORE * the winning CAS and carrying the NORMATIVE ledger fields (§13.1) so a later barrier's * enumeration can find the credential, prove which surface/incarnation it covered, and EVICT its * holder: * - `credentialId` is the PER-ISSUED-JWT identity (a digest of the credential), so standing * renewal (multiple JWTs for one nkey) writes a DISTINCT row each time — the §13.1 invariant * "every credential ever released resolves to a row" holds, and monotonic `state` is never * overwritten by a re-mint; * - `credentialKey` is the stable holder NKEY the broker revokes by (many JWTs share it); * - `holderPrincipal` (owner.actor) is what cluster-wide eviction targets; * - `lifecycleUid` is the instance's never-reused lifecycle identity (the gate key); * - `sourceChain` is the credential's §13.1 issuance lineage (`root` | `handle.…` | `session.…`); * - `state` is monotonic — a barrier flips `active`→`revoked`, never back; * - `exp` is the credential's expiry (for ledger audit/GC); * - the three currency coordinates + `generation` pin the incarnation the surface covered. */ export interface EpServeLedgerRow { credentialId: string; credentialKey: string; holderPrincipal: string; /** The served endpoint — the instance token is unique only within `(space, endpoint)`, so the * credential family is keyed by `(endpoint, lifecycleUid)`, never the instance token alone. */ endpoint: string; lifecycleUid: string; sourceChain: readonly string[]; state: "active" | "revoked"; exp?: number; generation: number; processEpoch: number; registrationRevision: number; nameAuthorityRevision: number; } /** The MINT half of the durable, single-key issuance-gate seam the serve release fence rides * (§13.1). One gate per instance; production wires it to the endpoint family's * `epgate..` in the credential ledger (the auth implementation's * `kvServeIssuanceGate`; `allow_direct=false`, revision-pinned CAS). A takeover, a * re-registration, and a name transfer are each a {@link EpIssuanceBarrier} that CASes this SAME * key to `frozen` before proceeding and reopens it at the successor coordinate, so mint-finalize * and every barrier serialize on one key — never a pseudo-transaction across two. */ export interface EpIssuanceGate { /** Leader-served read of the gate; `null` when there is no gate for this instance (fail * closed — a serve credential never mints against a missing gate). */ observe: () => Promise | EpGateState | null; /** Write the staged credential-ledger row (the §13.1 "write rows" step), before the CAS. * CREATE-ONLY / idempotent-if-identical: staging a `credentialId` that is already present must * succeed only when the row is byte-identical (a retry of the SAME issuance), and CONFLICT when * it differs (a different holder/lineage must never overwrite the row revocation/audit relies * on). Because `credentialId` is a per-JWT digest, a re-mint is a new id, never an overwrite. */ stage: (row: EpServeLedgerRow) => Promise | void; /** Revision-pinned CAS: keep the gate `open`, unchanged, at `expectedRevision`. TRUE iff this * mint won the single-key serialization; FALSE on any change (a freeze/retire, or a * reopen at a new generation/epoch/registrationRevision/nameAuthorityRevision advanced the * revision). */ commit: (expectedRevision: number) => Promise | boolean; /** Mark the staged row revoked on CAS loss / abort (the credential is never released). */ revoke: (row: EpServeLedgerRow) => Promise | void; } /** The BARRIER half of the SAME single-key gate (§13.1): the typed protocol a takeover, a * re-registration, or a name transfer runs to serialize itself against in-flight serve mints — * NOT ad-hoc mutation. A barrier freezes the gate FIRST (so a fresh mint observes `frozen` and * refuses, and a staged-but-uncommitted mint loses its revision-pinned CAS), enumerates the * ledger rows the superseded surface authorized, revokes/evicts them, then reopens at the * successor coordinate (advancing the dimension it changed). Both halves are exported TOGETHER * so core never publishes an independently-callable unsafe writer beside the fence: the spec * writer {@link registerServiceInstance} drives this seam and has no bare spec-key advance. */ export interface EpIssuanceBarrier { /** Stable operation id for this registration attempt. A fresh authority retry reuses it. */ readonly operationId?: string; /** Leader-served read of the gate (same key as the mint's {@link EpIssuanceGate.observe}). */ observe: () => Promise | EpGateState | null; /** Revision-pinned CAS `open` → `frozen` at `expectedRevision`, returning the FENCING TOKEN * (the frozen store revision) on success, or `null` on loss (another barrier froze/reopened, * or the gate retired) — a loser MUST abort and never write the spec. The token is consumed by * {@link reopen} so ONLY the barrier that still holds its freeze can reopen: a stalled/duplicate * barrier resuming after a reconciler cannot clobber the newer gate (§13.1). */ freeze: (expectedRevision: number) => Promise | number | null; /** Enumerate the credential-ledger rows under the frozen gate (§13.1 "enumerate the family"): * every credential the incarnation the barrier supersedes authorized. */ enumerate: () => Promise | EpServeLedgerRow[]; /** Flip one enumerated row `active`→`revoked` (§13.1: enforce revocation on the ledger). */ revoke: (row: EpServeLedgerRow) => Promise | void; /** VERIFIED cluster-wide eviction of a revoked `holderPrincipal` (§13.1): enforce the * revocation on every server, evict the principal's live connections, and RE-SCAN — returning * `true` only when the principal is verified GONE. FAIL-CLOSED: `false` (or a throw) means the * barrier MUST NOT complete (no spec write, no reopen); the gate stays frozen for reconciliation * so old authority is never published-over while it is still live. */ evict: (holderPrincipal: string) => Promise | boolean; /** Token-pinned CAS `frozen` → `open` at the successor coordinate (§13.1). TRUE iff the gate is * still frozen at THIS barrier's `token`; FALSE if a reconciler/newer barrier superseded it (a * stale reopen loses and never clobbers the newer gate). Advances the currency the barrier * changed, so a superseded mint's rebuilt CAS still loses. */ reopen: (token: number, successor: EpGateSuccessor) => Promise | boolean; /** Optional durable progress journal for a long-running registration's verified-eviction phase. * Production endpoint barriers provide it. In-memory test barriers may omit it. */ progress?: { load: () => Promise<{ cursor: EndpointRepairCursor; revision: number; } | null>; save: (cursor: EndpointRepairCursor, expectedRevision: number | null) => Promise; clear: (expectedRevision: number) => Promise; }; } /** The minted-credential context the release fence records into its §13.1 ledger row: the * credential's own identity, its holder ACTOR (the owner comes from the authorized grant, so the * eviction target `holderPrincipal` = `owner.actor`), its provenance lineage, and its expiry. * `mintCreds` supplies these from the same values it stamps into the JWT — the ledger row and * the credential describe ONE credential, never two. */ export interface EpServeCredential { /** PER-ISSUED-JWT identity (a digest of the credential): the ledger key, unique per JWT so a * standing renewal never overwrites the prior row. */ credentialId: string; /** The stable holder NKEY (public key) the broker revokes by; many JWTs share it. */ credentialKey: string; /** The holder's actor (owner.actor is the §13.1 eviction target). */ holderActor: string; /** The credential's §13.1 issuance lineage: each element `root` | `handle..` | * `session.` (a root serve mint is `["root"]`). */ sourceChain: readonly string[]; /** The credential's expiry (unix seconds), or `undefined` for a non-expiring credential. */ exp?: number; } /** * The serve-credential release fence (§13.1 "observe gate → write rows → CAS the gate → * release"). `mintCreds` calls this AFTER building the credential and BEFORE returning it, so a * credential is released only when its ledger row is durably written and its winning CAS proves * the gate was still `open` at the SAME `(processEpoch, registrationRevision, nameAuthorityRevision)` * the artifact was verified against: * - observe the gate; a missing gate or a `frozen`/`retired` state refuses (`expired`); * - the observed `processEpoch`, `registrationRevision`, and `nameAuthorityRevision` MUST each * equal the artifact's — a takeover (epoch), a re-registration (revision), or a name transfer * (name authority) that already froze+reopened advanced one of them, and this mint's surface * or its owner is superseded (`expired`); * - stage the NORMATIVE ledger row (`holderPrincipal`/`lifecycleUid`/`sourceChain`/`state`/`exp` * plus the three currency coordinates), then revision-pinned CAS the gate; a LOSS (a * concurrent barrier's freeze CAS won the single key) revokes the staged row and releases * nothing (`expired`). * The race is closed by serialization on ONE key: a mint that wins wrote its row before its * winning CAS, so a later barrier enumerates and revokes/evicts it by `holderPrincipal`; a mint * that loses never released. */ export declare function finalizeServeIssuance(gate: EpIssuanceGate, serve: EpServeGrant, credential: EpServeCredential): Promise; //# sourceMappingURL=endpoint-service.d.ts.map