import type { JetStreamClient, JetStreamManager } from "@nats-io/jetstream"; import { type EpCaller, type ParsedEpRequest } from "./endpoint-subjects.js"; /** §13.12 stream names for the two journal-side streams. */ export declare function epjStreamName(space: string): string; export declare function epfStreamName(space: string): string; /** The canonicalizer's durable (§13.9 consumer-name grammar): `canon_`. Uses the fail-loud * {@link endpointToken} (not the lenient `token`) so a malformed endpoint is refused, never * silently sanitized into a colliding durable name. */ export declare function canonDurable(endpoint: string): string; /** Decision facts live on the caller-scoped subject — distinct callers can never squat each * other's ids because the caller triple IS part of the subject (§13.4 item 3). Provenance is * STRUCTURAL, as for {@link deriveReplySubject}: the builder takes the broker-authenticated * PARSED submission and derives endpoint + caller internally — there is no argument through * which a body-supplied `from`/`op.endpoint` could address another caller's rail (the * canonicalizer holds broad `epf..dec.>` authority; the broker cannot catch a confused * call site, so the API must). */ export declare function epfDecisionSubject(space: string, request: ParsedEpRequest, id: string): string; /** Quarantine facts key on the source SEQUENCE — a family disjoint from caller-chosen `dec` * ids by construction, so no legal request id can collide with a quarantine key. */ export declare function epfQuarantineSubject(space: string, endpoint: string, sourceSeq: number): string; /** The per-goal first-wins bind (§13.4 item 3): stops a second id naming one goalId BEFORE * acceptance and effect. Structural provenance as {@link epfDecisionSubject}. */ export declare function epfGoalBindSubject(space: string, source: { endpoint: string; caller: { owner: string; actor: string; uid: string; }; }, goalId: string): string; /** The non-action effects completion fact (§13.9 ack barrier): * `epf..eff..`. */ export declare function epfEffectSubject(space: string, endpoint: string, caller: EpCaller, id: string): string; /** Default idempotency horizon (§13.4 item 6; space-configurable). The horizon is REALIZED by * decision-fact retention, never by a clock: the create-only CAS returns the recorded decision * for exactly as long as the fact exists. */ export declare const IDEMPOTENCY_HORIZON_MS_DEFAULT: number; /** §13.6 item 5: a goal's full terminal payload is retained at least this long (default 24 h). */ export declare const RESULT_RETENTION_MS_DEFAULT: number; /** §13.10: receipt retention, default **90 d** — the largest term in the §13.12 EPF floor by two * orders of magnitude, and the one a floor written against the horizon alone silently drops. */ export declare const RECEIPT_RETENTION_MS_DEFAULT: number; /** Build the §13.4 fingerprint for a PARSEABLE submission: the effect-defining subset it * carries — absent optional fields are OMITTED, never `null`, so two implementations digest * identical bytes; an incomplete envelope fingerprints the subset it has. The caller identity * and authorization mode come from the broker-authenticated SUBJECT, never the body. Throws * when the carried values are not canonicalizable I-JSON (lone surrogate, duplicate names * survive parsing as last-wins so cannot reach here, non-finite number) — that submission has * no interoperable RFC 8785 form, hence NO fingerprint, and takes the quarantine path. */ export declare function submissionFingerprint(raw: unknown, subject: ParsedEpRequest): { object: Record; fingerprint: string; }; /** Why a submission was quarantined. CLOSED and mutually exclusive: a reader branches on the * cause without knowing the order the canonicalizer evaluated its checks in. * * `no-usable-id` is deliberately DISTINCT from the three ceiling causes. Both end in quarantine, * but they are different facts about the submission — one says "you sent more than this endpoint * admits", the other says "these bytes name no request at all" — and an operator who cannot tell * them apart cannot tell a misconfigured caller from a corrupt one. */ export type AdmissionQuarantineCause = "submission-too-large" | "submission-too-deep" | "submission-too-many-items" | "no-canonical-form" | "no-usable-id"; /** The closed set of admission outcomes. Exactly three, and the third is the only one that * produces a caller-addressed decision fact: a quarantine has no fingerprint to address one * with, which is the whole reason the two paths differ. */ export type AdmissionOutcome = { outcome: "quarantine"; cause: AdmissionQuarantineCause; detail: string; } | { outcome: "reject"; code: "resource-exhausted"; detail: string; fingerprint: string; object: Record; } | { outcome: "admit"; fingerprint: string; object: Record; }; /** * Duplicate object names in the RAW bytes, which no post-parse check can see. * * `JSON.parse('{"id":"a","id":"b"}')` yields `{id:"b"}` and reports nothing. So a submission that * two conforming implementations could read DIFFERENTLY — one taking the first name, one the last — * arrives at the decision looking perfectly ordinary. That is the precise failure the declared ceiling * exists to prevent: two implementations deciding the same bytes differently and DURABLY. * * The module has claimed duplicate names as a `no-canonical-form` cause since it was written, and * the case was UNREACHABLE — not untested, unrepresentable, because the seam handed the decision a * value from which the duplicate had already been erased. Found by a reviewer, not by any of the * mutants aimed at this file. * * A scanner, not a regex: strings can contain braces, colons and escaped quotes, and a recogniser * that succeeds on a prefix is a parser that lies. This walks the bytes once and tracks a name set * per open object. It runs only on input already inside the byte ceiling. */ export declare function hasDuplicateNames(text: string): { duplicate: true; name: string; } | { duplicate: false; }; /** * Out-of-range numbers in the RAW bytes — the fourth I-JSON condition SPEC §13.4 names * ("unparseable, duplicate object names, lone surrogate, out-of-range number") and the only one * this module did not enforce. * * WHY RAW BYTES, and why no mutant could ever have found this. `JSON.parse('12345678901234567890')` * yields `12345678901234567000` and reports nothing: the caller sent one number and the durable * decision binds a different one. After the parse the information is GONE, so a mutant aimed at a * post-parse branch would have SURVIVED and read as a suite hole. This class is not reachable by * mutation by construction; it is reached by asking what the SPEC names and whether the code can * produce it at all. * * THE PREDICATE IS ROUND-TRIP STABILITY, not a safe-integer test, and the difference is not * academic: `1e-400` parses to exactly `0`, and `Number.isSafeInteger(0)` is `true` — the underflow * case does not merely escape a safe-integer test, it passes one AFFIRMATIVELY while the value it * came from has been destroyed. Stability is also the right criterion on its own terms: RFC 8785 * canonicalises from the double, so two implementations agree exactly when the literal survives * text → double → text. `0.1` is therefore fine (it is not exactly representable, but its shortest * round-trip form is `0.1` in every conforming reader); `12345678901234567890` is not. * * A scanner for the same reason `hasDuplicateNames` is one: digits inside a string are not numbers, * and a recogniser that succeeds on a prefix is a parser that lies. */ export declare function hasOutOfRangeNumber(text: string): { outOfRange: true; literal: string; reads: string; } | { outOfRange: false; }; /** Decide ONE submission against the command's DECLARED `admissionCeiling`. * * The ceiling is a parameter and never a constant, because two conforming implementations must * not be able to decide the same bytes differently and durably. That is also why nothing here * reads a clock: the outcome is a function of the bytes and the declaration alone, so a * redelivery of the same submission reaches the same durable answer however long any worker * took. A watchdog may re-deliver; it may never decide. * * ORDER IS PART OF THE CONTRACT, and the reason is the fingerprint. The raw-byte ceiling is * evaluated BEFORE parsing, because parsing is the work the ceiling exists to refuse. Everything * that fails before a fingerprint exists quarantines, because there is no caller-addressed * subject to write a decision to. Once a fingerprint EXISTS the caller is addressable, so a * breach becomes a REJECTION — a durable, caller-visible answer rather than a message they never * hear about. */ export declare function decideAdmission(rawBytes: Uint8Array, parsedBody: unknown, subject: ParsedEpRequest, ceiling: { maxBytes: number; maxDepth: number; maxItems: number; }): AdmissionOutcome; export interface FactCaller { id: string; lifecycleUid: string; } export interface AcceptanceFact { v: 1; id: string; decision: "accepted"; fingerprint: string; /** The canonical EndpointRequest, args INLINE (bounded by max_payload — preflighted). */ request: Record; caller: FactCaller; target?: { owner: string; actor: string; lifecycleUid: string; mappingRevision?: number; }; contractDigests: { input: string; output: string; }; authzDecision: { revision: number; epoch: number; }; /** The acceptance's SINGLE execution route: effects consumers MUST ack a pool-routed * acceptance without effect, and vice versa — no acceptance executes twice. */ route: "effects" | `pool.${string}`; readinessDeadlineMs?: number; workExpiry?: number; sourceSeq: number; ts: number; } export interface RejectionFact { v: 1; id: string; decision: "rejected"; fingerprint: string; error: { code: string; detail?: string; }; caller: FactCaller; authzDecision?: { revision: number; epoch: number; }; sourceSeq: number; ts: number; } export interface QuarantineFact { v: 1; decision: "quarantined"; sourceSeq: number; /** `sha256:` over the RAW stored submission bytes — never the poison bytes themselves. */ submissionDigest: string; error: { code: string; detail?: string; }; caller?: FactCaller; ts: number; } export type DecisionFact = AcceptanceFact | RejectionFact; export interface EffectFact { v: 1; id: string; fingerprint: string; caller: FactCaller; sourceSeq: number; ts: number; /** The REQUIRED outcome discriminant (the goal union's `state` bar, applied to effects): every * member of the completion union declares its outcome explicitly, so a cancelled fact is never * structurally assignable to the ran type and every reader is forced to read the outcome. */ outcome: "ran"; } /** The RETIREMENT-CANCELLED member of the effects completion union (§13.8 option-(i) closure): * the SAME identity spine as {@link EffectFact} (so every fingerprint/sourceSeq binding applies * unchanged), the `outcome: "cancelled"` discriminant, and the `cancelled` block binding the * acceptance to the RETIRING target and the retirement operation — a reader that sees it KNOWS * the effect did not run and was cancelled by that retirement; it is never a forged success. It publishes CREATE-ONLY on the SAME completion * subject the real marker would use, so first-terminal-wins is structural: a racing real * completion that lands first wins and the cancel loses its create harmlessly (and vice versa). * Actions need no such member: `goal.result` already carries the first-class `cancelled` * terminal state, with the retirement binding in its digest-bound `data`. */ export interface EffectCancelledFact { v: 1; id: string; fingerprint: string; caller: FactCaller; sourceSeq: number; ts: number; outcome: "cancelled"; cancelled: { opId: string; target: { owner: string; actor: string; lifecycleUid: string; }; }; } /** The closed non-action effects completion union: ran, or retirement-cancelled. */ export type EffectCompletionFact = EffectFact | EffectCancelledFact; /** Build the durable completion marker for one validated non-action `effects` acceptance. */ export declare function effectFactOf(acceptance: AcceptanceFact, ts: number): EffectFact; /** Build the retirement-cancelled completion marker for one validated non-action `effects` * acceptance (§13.8 option (i)): the acceptance's own identity spine + the retirement binding. * The caller supplies the retirement's `opId` and the RETIRING target; the acceptance's own * `target` must name that same lifecycle (a retirement never cancels a foreign target's work). */ export declare function effectCancelledFactOf(acceptance: AcceptanceFact, cancelled: { opId: string; target: { owner: string; actor: string; lifecycleUid: string; }; }, ts: number): EffectCancelledFact; /** The authenticated ADDRESS of a decision fact: the `epf….dec....` subject * tokens the broker enforced on the mediated writer's publish. */ export interface FactAddress { endpoint: string; caller: FactCaller; id: string; } /** Parse a decision-fact subject into its address — the mandatory seam through which every * consuming boundary proves body↔subject agreement. Throws `internal` on a non-decision * subject (a consumer wired to the wrong subject family is a bug, never a data error). */ export declare function parseDecisionFactSubject(subject: string): FactAddress; /** Parse an effect-completion fact subject into its authenticated acceptance identity. */ export declare function parseEffectFactSubject(subject: string): FactAddress; /** Validate a decision fact at its consuming boundary (§13.3: every plane is runtime-validated; * effects and replay read THE FACT, never the raw submission). The fact must be * SELF-SUFFICIENT canonical authority: every field is grammar-validated (caller principal, * positive source sequence/timestamp, digest forms, the embedded canonical request, the * resolved target triple), and the body's id/caller/endpoint must AGREE with the * broker-authenticated fact subject — a mismatch is a writer bug or corrupt store and fails * loud before effects or replay can attribute the decision. */ export declare function parseDecisionFact(raw: unknown, subject: string): DecisionFact; /** Validate a non-action effects completion fact at the consuming boundary — the CLOSED * two-member union: the effect ran ({@link EffectFact}) or it was retirement-cancelled * ({@link EffectCancelledFact}; the `cancelled` discriminator, whose block must itself be * closed and complete). A bare marker on the right subject is not enough: the body must bind * the accepted fingerprint and source sequence so the drain can compare it to the decision * that authorized this exact effect — for BOTH members. */ export declare function parseEffectFact(raw: unknown, subject: string): EffectCompletionFact; /** Validate a quarantine fact at its consuming boundary; the body's source sequence must agree * with the `epf….quar.` subject it was stored under. */ export declare function parseQuarantineFact(raw: unknown, subject: string): QuarantineFact; /** Preflight a fact's SERIALIZED size against the broker's max_payload (§13.4 item 5): an * acceptance that would not fit is rejected `resource-exhausted` BEFORE any decision exists * (the rejection fact always fits by construction — every field bounded or fixed-size). */ export declare function assertFactFits(fact: DecisionFact | QuarantineFact, maxPayload: number): Uint8Array; /** Append a submission PLAIN (§13.4 item 1): never a `Nats-Msg-Id`, no dedupe header of any * kind — the guarantee rests on the header rule, not the window. Returns the stream ack. */ export declare function appendSubmission(js: JetStreamClient, subject: string, envelope: unknown): Promise<{ seq: number; }>; /** Publish a fact with create-only CAS (expected last sequence ON THE SUBJECT = 0). Returns * `{ won: true, seq }` or `{ won: false }` on a lost CAS — the loser then READS the winning * fact ({@link readLastFact}) instead of deciding again. Any non-CAS failure propagates. */ export declare function publishFactCreateOnly(js: JetStreamClient, subject: string, factBytes: Uint8Array): Promise<{ won: true; seq: number; } | { won: false; }>; /** Last-by-subject read of a fact — LEADER-SERVED (`STREAM.MSG.GET`, never `DIRECT.GET`). The * §13.4 loser-reads-winner contract needs read-your-writes against the leader that just * rejected the CAS: a follower-served Direct Get carries no such guarantee and, after legal * post-horizon id reuse, can return a STALE prior fact (semantically a DIFFERENT decision) or * nothing — and no retry can distinguish a stale nonempty fact from the current winner. EPF * keeps `allow_direct=true` for other trusted subject-confined reads (§13.12); the CAS-winner * read is deliberately not one of them. `undefined` when no fact exists on the subject. */ export declare function readLastFact(jsm: JetStreamManager, stream: string, subject: string): Promise; //# sourceMappingURL=endpoint-journal.d.ts.map