import { type ConsumerConfig, type JetStreamClient, type JetStreamManager } from "@nats-io/jetstream"; import { type NatsConnection } from "@nats-io/transport-node"; /** Per-(sender,channel)-subject retention cap on the chat stream — the bound past which the * oldest message on a subject is discarded (`DiscardPolicy.Old`). Also the horizon of focus * recall: only the last {@link MAX_MSGS_PER_SUBJECT} per sender-subject are recallable. */ export declare const MAX_MSGS_PER_SUBJECT = 1000; /** JetStream message-dedup window on the Plane-3 streams: a `Nats-Msg-Id` * (`::`) repeated within this window is collapsed. Sized generous (2h) so * an activation-catch-up copy and a racing fan-out copy of the same message dedup even for a slow/ * backlogged owner. **This window IS the cross-path exactly-once correctness horizon** — two writes * of the same logical copy separated by more than it (e.g. a manager crash after a DLV publish, the * dinbox ack lost, the window expiring, then a re-transfer after restart) are NOT collapsed at the * stream. The connector's commit-aware id-cache (`MeshAgent.ingest`) coalesces live↔durable and * redelivery duplicates within a SESSION, but it is in-memory and reset on agent restart, so it is * NOT a cross-restart guarantee. A persistent per-owner delivery ledger would lift the bound; not * built (the 2h horizon covers the realistic crash/redelivery lag). Keep the window ≥ worst-case lag. */ export declare const PLANE3_DEDUP_WINDOW_MS: number; /** Bound on the trusted reader's in-flight (un-acked) entries per owner — an offline owner with a large * backlog can't stall the reader's own redelivery by pinning unbounded pending. */ export declare const DINBOX_MAX_ACK_PENDING = 1000; /** Delivery-daemon single-flight lease TTL (ms) — the bucket-level `max_age` on `cotal_delivery_`. * A live holder renews at ~half this; a crashed holder stops renewing and the bucket TTL expires its * lease key, freeing it for a fresh daemon's CAS create. Sized well above the renew interval so a brief * GC/scheduling pause never self-evicts a healthy holder, yet short enough that a crash frees the shard * promptly. (The bucket holds ONLY lease keys, so a bucket TTL is exact here; per-key TTL is also * available on this stack — a deliberate simplicity choice, not a capability gap. See {@link deliveryBucket}.) */ export declare const LEASE_TTL_MS = 30000; /** Manager singleton-lease TTL (ms) — the bucket-level `max_age` on `cotal_manager_`. Shorter * than the delivery lease so a crashed manager frees the space for a replacement promptly. Tune here * (independent of the delivery lease above); the holder's pacing inside this window is * {@link MANAGER_LEASE_RENEW_MS} / {@link MANAGER_LEASE_ATTEMPT_MS}. */ export declare const MANAGER_LEASE_TTL_MS = 10000; /** How often the holder refreshes its manager lease, and the deadline it gives each attempt. * * THESE TWO NUMBERS ARE ONE BUDGET, and neither means anything alone. The TTL above is the whole * window a holder has to prove it is still there. Renewing at TTL/2 with the request deadline left * at the JetStream default (5s, which is also TTL/2) puts exactly ONE attempt inside the window and * lets that attempt's own deadline consume the entire remainder — so a single slow round trip is * terminal, by construction, on a holder that is otherwise healthy. At TTL/4 with a deadline under * the period, no single attempt can spend the window and there is room to ask again. * * HOW MUCH ROOM, EXACTLY, because "N attempts fit" is a claim about the composed path and not about * these two numbers. A renew that times out is followed by a re-read with a deadline of its own, and * the in-flight guard skips any tick that falls while the pair is running, so the unit to count is * the pair and not the tick. On the path this budget exists for — the renew gets no answer and the * re-read does — the pair costs about one deadline and three of them complete inside the TTL. Under a * TOTAL blackout, where both halves spend their deadline, the pair costs two and one completes inside * the TTL. Past the TTL the key expires at the broker; the holder keeps serving and, when the broker * answers again, reads the key gone and puts it back. Nothing here ends the holder's process. * * They are CLIENT-SIDE PACING ONLY. Unlike the TTL, which is written into the bucket at `cotal up` * and so has to be reconciled on an existing mesh (see {@link ttlBuckets}), changing these two * touches no stored config and needs no migration. */ export declare const MANAGER_LEASE_RENEW_MS: number; export declare const MANAGER_LEASE_ATTEMPT_MS = 2000; /** Bucket-level `max_bytes` cap on the derived membership feed (`cotal_membership_`). The * per-agent keying keeps each value tiny (a handful of channel patterns), so 64 MiB bounds the footprint * far above any realistic readership while keeping the bucket from growing unbounded. A deliberate cap, * not a guess at scale — the design is cap-safe by construction (per-agent, store-patterns-not-expanded). */ export declare const MEMBERSHIP_MAX_BYTES: number; /** Bucket-level `max_bytes` cap on the per-space artifact Object Store (`cotal_artifacts_`). * * THIS NUMBER IS THE ONLY THING BOUNDING ARTIFACT STORAGE, so it is a decision rather than a * default. A fresh Object Store bucket ships `max_bytes: -1`, and the space account is provisioned * `disk_storage: -1`, so nothing above it says no: without this cap an artifact flood grows until * the disk does, starving the chat/DM/delivery streams that share it. * * 4 GiB is roughly sixteen artifacts at the 256 MiB per-artifact ceiling, which is generous for the * transfer use case (screenshots, reports, build outputs) and small enough that filling it is a * visible event rather than a silent disk exhaustion. `discard: new` on the bucket means hitting it * REFUSES the write rather than evicting older artifacts — the loud failure, not the silent one * where a reference published yesterday quietly stops resolving. */ export declare const ARTIFACT_STORE_MAX_BYTES: number; export interface ClearSpaceHistoryResult { chat: number; dm?: number; } /** Auth material for a STANDALONE helper connection: a static/raw creds file, OR the user-mode * pair (a view bearer + the deny-all sentinel creds) — exactly what the endpoint's user mode * presents. Never both. Empty = open mode. */ export interface StandaloneAuth { creds?: string; bearer?: string; sentinelCreds?: string; /** Whether this connection must REQUIRE TLS rather than merely tolerate it. * * REQUIRED, and it is the point of the field. This helper is the STANDALONE connect path, and it * was derived from the endpoint path's auth half without its transport half: `authOpts`, * `probeConnect` and `RawAuth` all carry a TLS requirement, and this one did not. Every caller * of it therefore built connect options that carried the credentials and dropped the thing that * protects them in transit. * * Made required rather than optional because the omitted case is the dangerous one. A client * with no TLS requirement still connects to a TLS broker — it upgrades the same socket once it * reads `tls_required` in the server's unauthenticated INFO — so nothing looks wrong until an * on-path attacker forges an INFO without it and collects the credentials in the clear. An * optional field would leave the seam LOOKING transport-aware while callers kept omitting it. * * Note the honest limit of the compile error: smoke files are outside the tsconfigs, so the type * forces every TYPECHECKED caller to state a transport, not every caller. That is why * `standaloneConnectOpts` also throws at runtime — see there. */ tls: boolean; } /** Connection options for a privileged STANDALONE helper (`setupSpaceStreams`, `clearSpaceHistory`, * `clearChannel`, the channel-registry helpers): pin the reply inbox to the connection's own * identity. A scoped cred (provisioner/purger/operator) subscribes only `_INBOX_.>`, so without * this its JS-API replies land on the default `_INBOX.` — a subject the cred's sub rejects * (Permissions Violation), hanging every `jetstreamManager`/`streams.*` request. * * USER MODE (`bearer` + `sentinelCreds`) mirrors the endpoint's callout-shaped connect: the * sentinel creds land the connection in the callout account, the bearer rides `auth_token`, and a * client-chosen inbox NONCE goes out as the connect `name` — the callout scopes `_INBOX_.>` * from it (the client cannot know its nkey pre-connect). Open mode (no auth) connects bare. */ export declare function standaloneConnectOpts(auth: StandaloneAuth): Record; /** * Create (idempotently) the five message streams for a space. * * This is **privileged**: under auth mode `STREAM.CREATE` is denied to regular agents * (streams are space infrastructure, not per-agent), so it runs once at setup * (`cotal up`) or from a permissive endpoint. The single source of the stream * definitions, shared by the endpoint and the setup path so they can't diverge. */ export declare function createSpaceStreams(jsm: JetStreamManager, space: string): Promise; /** * The DM inbox durable for an instance — ONE definition, used both by the privileged * pre-create (manager/provisioner, auth mode) and the endpoint's open-mode self-create, so * an idempotent re-add can never error on a config delta. The `filter_subject` binds the * durable to inst..* — only the privileged creator sets it, which is the whole point: * an agent can't create a durable filtered to someone else's inbox. * * `inactive_threshold` is set ONLY when the caller passes one — i.e. the open-mode * self-create, where the agent owns the durable and a threshold cleanly retires its inbox * after it departs. The privileged auth pre-create OMITS it: the agent BINDS-only and is * denied CONSUMER.CREATE, so a threshold would retire the durable before a late/relaunched * agent binds it, and the bind would then fail permanently ("consumer not found"). Persisting * it is the price of bind-only; explicit cleanup on agent-stop is a follow-up. */ export declare function dmDurableConfig(space: string, owner: string, actor: string, lifecycleUid: string, opts?: { ackWaitMs?: number; inactiveThresholdMs?: number; activationFrontier?: number; }): Partial; /** * The TASK work-queue durable for a role — ONE definition, shared by the privileged * pre-create (auth mode) and the endpoint's open-mode self-create. The durable is shared * across all instances of a role (queue group); the privileged creator sets the * filter_subject to svc..* so an agent can't bind a consumer filtered to another * role's queue (the same create-time-filter attack surface as DM). Idempotent per role. */ export declare function taskDurableConfig(space: string, role: string, opts?: { ackWaitMs?: number; }): Partial; /** The single privileged trusted-reader consumer over the WHOLE INBOX (mixed pre-auth) store * (`dinbox.>`, all owners) — created + bound only by the manager. Explicit ack: the reader holds an * entry un-acked until it has transferred the re-authorized copy to DLV (a crash before transfer * redelivers). `max_ack_pending` bounds the reader's in-flight set. The per-message owner is * recovered from the subject (`parseDinboxOwner`). */ export declare function inboxReaderConfig(space: string, opts?: { ackWaitMs?: number; shard?: number; shards?: number; }): Partial; /** An agent's bind-only per-member DELIVER consumer (mirrors {@link dmDurableConfig}): the provisioner * pre-creates it filtered to `dlv.`; the agent BINDS it (denied CREATE on DLV) and acks via * native JetStream — the §8 "equivalent per-member at-least-once mechanism with the same ack * semantics". `inactive_threshold` only for an open-mode self-create (none today; Plane-3 is * auth-only). */ export declare function dlvDurableConfig(space: string, owner: string, actor: string, lifecycleUid: string, opts?: { ackWaitMs?: number; inactiveThresholdMs?: number; }): Partial; /** The single privileged fan-out consumer on CHAT (manager-pumped; routing, not auth). * `DeliverPolicy.New` at creation (pre-existing backlog is pre-membership); a DURABLE, so on a * manager restart it resumes from its ack cursor and fans out the gap, idempotent via `Nats-Msg-Id`. */ export declare function fanoutDurableConfig(space: string, opts?: { ackWaitMs?: number; shard?: number; shards?: number; }): Partial; /** Connect with the given (privileged) creds, create the space's streams, and disconnect. * Used by `cotal up` to pre-create streams once at setup. */ export declare const TTL_RECONCILE_CANARY_KEY = "_cotal_ttl_reconcile"; /** The server accepted and reported a TTL update, but its backing store did not enforce it. */ export declare class TtlPersistenceError extends Error { readonly stream: string; readonly ttlMs: number; readonly canarySubject: string; constructor(stream: string, ttlMs: number, canarySubject: string); } /** #286/#404: reconcile a TTL'd KV bucket and prove the backing store enforces the result. * * `STREAM.INFO` alone is not evidence. nats-server updates the stream's in-memory config before asking * the backing store to persist it, ignores the store's `UpdateConfig` error, and serves INFO from the * in-memory copy. A file-store metadata fault can therefore report UPDATE success and the requested * `max_age` while the store rolls back and never starts age enforcement. * * Before an update, this writes a durable marker on a reserved KV subject so an interrupted or false-green * attempt cannot disappear behind a later matching INFO read. After the update, it writes the enforcement * canary on that same subject and waits for subject-filtered stream state to report it gone within * `max_age` plus fixed grace. The post-update write is the timed proof even when the prior TTL was shorter * and expired the marker during the update. A clean matching bucket has no canary and stays a fast read-only * no-op. The transition is proof of enforcement rather than config: the false-green store leaves the canary * present and causes a named {@link TtlPersistenceError}, which `cotal up` surfaces. Successful canaries * self-delete through the policy they verify. */ export declare function reconcileBucketTtl(jsm: JetStreamManager, js: Pick, streamName: string, bucket: string, ttlMs: number): Promise; /** The TTL'd buckets and their intended `max_age`, in ONE place — and the ONLY place any of them is * created. * * Creation is DRIVEN from this list, not merely checked against it. That distinction is the whole * point: an earlier version had the reconcile paths read the list while `setupSpaceStreams` still * named each `kvm.create(..., { ttl })` separately, and claimed in this comment that a bucket could * therefore "never" be TTL'd on one path and forgotten on the other. It could — a fourth bucket * added at a create site would have been created with a TTL and never reconciled, which is #286's * shape exactly, recreated by the fix for it. (Caught in review; the claim was false when written.) * Now a TTL'd bucket cannot be created without appearing here, so it cannot be missed on upgrade. * * NOT mode-gated: an open mesh carries the same buckets and drifts identically. */ export declare function ttlBuckets(space: string): ReadonlyArray; /** What a reconcile actually CHANGED. Returned (rather than logged inside) so the caller can report * it: a `cotal up` against a running mesh now performs a config write, and a write the operator * cannot see is the kind of silent behaviour this change exists to remove. `undefined` means the * bucket already carried the intended TTL and nothing was written. */ export type TtlReconciled = { stream: string; fromMs: number; toMs: number; }; /** #286: reconcile all three TTL'd buckets for a space, over a connection of its own. * * Exists because `setupSpaceStreams` is only reachable on the CREATE path (`cotal up` starting a * mesh), while the drifted-bucket case this fixes is by definition a mesh that is ALREADY RUNNING — * an old deployment being upgraded in place. That path starts nothing and so must not run the * create-everything routine; it needs exactly the reconcile and nothing else. * * Read-first by construction: each bucket is skipped when its `max_age` already matches, so a * steady-state repeat `cotal up` issues three `STREAM.INFO` reads and ZERO writes. Returns only the * buckets it actually changed. `creds` is omitted on an open mesh, exactly as `setupSpaceStreams` * documents — the TTL'd buckets are NOT mode-gated, so an open mesh drifts identically. */ export declare function reconcileSpaceTtls(opts: { servers: string; space: string; /** Privileged creds for an authed mesh; omit on an open mesh (a bare connection has the rights). */ creds?: string; }): Promise; /** * Create the space's artifact Object Store, or VERIFY an existing one — never silently adopt it. * * `Objm.create(bucket, { max_bytes })` is create-if-MISSING. Measured on nats-server 2.14.4 with * `@nats-io/obj` 3.4.0: creating at `max_bytes: 1024`, then calling create again with `4096`, leaves * the stream at **1024** — it neither updates the config nor refuses. A bare `create()` with no * options does the same. * * That makes create-alone actively dangerous here, because {@link setupSpaceStreams} is idempotent * and re-runs on every `cotal up`. A store that predates this cap, or that an operator widened by * hand, would be adopted forever: the code would look like it enforces a 4 GiB ceiling while the * broker enforced whatever was there first, and nothing would ever say so. The cap is the ONLY thing * bounding artifact storage (account disk is provisioned unlimited), so an unenforced cap is not a * smaller cap — it is no cap. * * So: create, then read the config back and refuse loudly on drift. Same create-or-verify discipline * `ensureAuthorityStores` already uses, and the same reason: an idempotent setup path must either * converge the resource or report that it cannot. */ export declare function ensureArtifactStore(nc: NatsConnection, space: string): Promise; export declare function setupSpaceStreams(opts: { servers: string; space: string; /** Privileged creds for an authed mesh; omit on an open mesh (a bare connection has the rights). */ creds?: string; }): Promise; /** Purge retained message history for a running space. This intentionally leaves TASK alone: * anycast is queued work, not replay history. */ export declare function clearSpaceHistory(opts: { servers: string; space: string; creds?: string; /** User mode: a `purger`-view bearer + the space's sentinel creds (instead of a creds file). */ bearer?: string; sentinelCreds?: string; includeDms?: boolean; }): Promise; /** Delete one channel and its content: purge every retained message on the channel (across * all senders, via the `*` sender slot) from the chat stream, then drop the channel's * registry config so it stops surfacing as an empty channel. Needs PURGE rights — pass * privileged creds (e.g. `manager`); a bare connection (open mode) has them by default. * Throws on a wildcard channel (a subtree is not a deletable channel). A missing channel * registry bucket/key is a no-op — the purge alone already emptied the channel. */ export declare function clearChannel(opts: { servers: string; space: string; channel: string; creds?: string; /** User mode: a `channel-purger`-view bearer + the space's sentinel creds (instead of a creds file). */ bearer?: string; sentinelCreds?: string; }): Promise<{ channel: string; purged: number; }>; /** Delete a departed agent LIFECYCLE's provisioning footprint (#159 Part B) — the teardown counterpart * to {@link provisionAgent}. Removes exactly what the provisioner minted for THIS incarnation: its two * bind-only durables (`dm_--`, `dlv_--`) and its lifecycle-keyed read-ACL row. * Idempotent — a missing consumer / absent ACL row is a no-op (the agent may have exited before a * durable was created, or a re-run). LIFECYCLE-EXACT by construction (SPEC §13.1): every name this * deletes embeds the target uid, so a stale/replayed teardown for a retired lifecycle names only * retired resources — it structurally cannot touch a same-alias successor, and the deprovisioner * cred's exact-name grants make a wrong-uid delete broker-DENIED, not just a no-op. * * Does NOT touch the role-SHARED `svc_` TASK durable (deleting it would break the role's other * agents — it lives until space teardown), nor the ephemeral `chathist_…-` history consumers (they * self-clean on the agent's disconnect). The creds FILE is removed by the caller (a manager-local * filesystem concern, not a broker one). Pass a TARGET-PINNED `deprovisioner` cred (see * {@link mintCreds}); a bare connection (open mode) never calls this — an open mesh mints nothing. */ export declare function deprovisionAgent(opts: { servers: string; space: string; targetId: string; lifecycleUid: string; creds?: string; }): Promise; //# sourceMappingURL=streams.d.ts.map