Rindle

API index and search · Build metadata

Supporting declarations

packages/optimistic/src/backend.ts. These declarations explain referenced types. Only package-page symbols are package exports.

Exact source

MutationTx

/** The write handle a client mutator runs against (the client `MutationTx`, §4.2):
 *  reads see the current base + this transaction's own staged writes (§4.1).
 *
 *  Prefer the KEYED methods (`insert`/`update`/`upsert`/`delete`/`row`) — named columns,
 *  schema-checked. The positional methods (`get`/`add`/`remove`/`edit`) are the raw wire
 *  shape: bare cells in schema column order, `pk` cells in `primaryKey` order. */
export interface MutationTx {
    /** Read one row by primary key (e.g. `tx.row("issue", { id: 1 })`). */
    row(table: string, pk: KeyedRow): KeyedRow | undefined;
    /** Insert a FULL row (every column named; missing or unknown columns throw). */
    insert(table: string, row: KeyedRow): void;
    /** Update the row identified by the pk columns; only the named non-pk columns change.
     *  A missing row is a NO-OP (rebase-friendly: the row may have vanished upstream). */
    update(table: string, row: KeyedRow): void;
    /** Insert, or fully replace when the pk already exists (a FULL row, like `insert`). */
    upsert(table: string, row: KeyedRow): void;
    /** Insert a FULL row, or do nothing if the pk already exists (the isomorphic form of the classic
     *  `if (!tx.row(pk)) tx.insert(row)` upsert-if-absent; renders `ON CONFLICT DO NOTHING` server-side). */
    insertIgnore(table: string, row: KeyedRow): void;
    /** Delete the row identified by the pk columns. A missing row is a NO-OP. */
    delete(table: string, pk: KeyedRow): void;
    /** Run a one-shot read query (`where`/`orderBy`/`limit`/join) over the state this
     *  mutator is mutating — it sees this transaction's own writes-so-far, the same
     *  read-your-writes contract as `get`/`row` (§4.1; 203-MUTATOR-READS-DESIGN.md §5.2).
     *  Synchronous; returns the matching rows in the query's order, each with its materialized
     *  relationship children nested by name (presented identically to a `view.data` row). Pass
     *  a query from the typed builder, e.g. `tx.query(q.issue.where("owner", "=", me))`.
     *  Refused inside a FOLDED mutator (a reading mutator is non-absorbing, §9.1). */
    query(query: QueryArg): QueryResultRow[];
    get(table: string, pk: WireValue[]): WireValue[] | undefined;
    add(table: string, row: WireValue[]): void;
    remove(table: string, row: WireValue[]): void;
    edit(table: string, oldRow: WireValue[], newRow: WireValue[]): void;
}

ClientMutator

/** A client mutator: optimistic, deterministic, replayable — a pure function of `(base, args)` (§5:
 *  no clock, no randomness; it is RE-INVOKED on every rebase). Either shape is accepted:
 *  - a plain synchronous function `(tx, args) => void` (client-only), OR
 *  - a shared GENERATOR `(tx, args, ctx) => MutationGen` (the isomorphic form: the SAME body the API
 *    server runs against a live async transaction — MUTATORS-ISOMORPHIC). The driver detects which. */
export type ClientMutator = ((tx: MutationTx, args: never) => void) | ((tx: IsoTx, args: never, ctx: MutatorCtx) => MutationGen);

ClientRegistry

/** The client registry (§4.2) — one of the two registries; the server's authoritative
 *  twin shares names (and possibly code), never the wire. */
export type ClientRegistry = Record<string, ClientMutator>;

WriteRecord

/** One captured write, pk-granular (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §3.2 #1: "the
 *  writers already receive `(table, row)`... this is pure capture, no semantic change"). `row` is
 *  the post-write image (positional wire cells, schema column order); `undefined` for a `remove` —
 *  no row survives it, and `row === undefined` stays the remove marker.
 *
 *  `oldRow` is the PRE-IMAGE, captured for a `remove` AND an `edit` (H-ii): the full-width row
 *  read via `tx.get` immediately BEFORE the write staged — read-your-writes, so a write to the
 *  same pk earlier in the SAME invocation shows through — falling back to the caller's asserted
 *  old row when the pk is not txn-visible (a raw remove/edit of an absent row; incl. the
 *  pk-MOVING raw edit, whose record is keyed by the NEW pk yet whose pre-image is the caller's
 *  OLD row). A captured remove/edit thus always carries a full-width pre-image — with ONE
 *  exception: a record that collapses to a (re-)insert has none, see the matrix.
 *
 *  Coalescing within one invocation is last-write-wins per pk on `row` (the final image matches
 *  the engine head's own semantics for that pk) with `oldRow` pinned to the TXN-ENTRY BASE — the
 *  H-ii matrix:
 *    - edit-after-add / edit-after-remove: the record collapses to a (re-)insert — post-image
 *      only, NO `oldRow` (the pk did not pre-exist this invocation's base; presence hold-back
 *      uses `row`).
 *    - edit-after-edit: keeps the FIRST pre-image (the txn-entry base — the chain nets to ONE
 *      edit from the base to the final image).
 *    - remove-after-edit / remove-after-remove: keeps the ORIGINAL pre-image (the first write's
 *      captured base), NOT the edited transient — the net effect is a remove of the row the
 *      external world last knew.
 *    - remove-after-add: keeps the txn-visible pre-image (the transient added row — the pk had
 *      no base, and this is the only truthful full-width row there is; G-iii pinned it).
 *    - add-after-remove (a re-insert): drops `oldRow` (presence hold-back uses `row`).
 *  Across rebase re-invocations the write-set is union-never-shrink ({@link mergeWriteSet}): a
 *  re-run that no-ops keeps the prior record — and its pre-image — intact. */
export interface WriteRecord {
    table: string;
    pk: WireValue[];
    row: WireValue[] | undefined;
    oldRow?: WireValue[];
}

WriteSet

/** The pk-granular write-set captured over ONE mutator invocation: table → pk-key (a stable-JSON
 *  encoding of the pk cells, {@link stableJson}) → that pk's write, LAST-WRITE-WINS within the
 *  invocation — an add-then-edit or edit-then-edit of the SAME pk collapses to its final image,
 *  matching the engine head's own semantics for that pk. Chosen (over a flat array) because the
 *  later routing proof needs "is pk P in the writable scope" / "did we already see this pk in this
 *  invocation" as cheap lookups, and rebase re-invocation needs to MERGE a fresh write-set into an
 *  accumulated one ({@link mergeWriteSet}) — both are Map operations, not scans.
 *
 *  `touched` (the pre-existing table-granular `Set<string>` the pending axis reads, §7.2) is
 *  exactly `new Set(writeSet.keys())` — derived from this, never separately populated, so the two
 *  can never drift. */
export type WriteSet = Map<string, Map<string, WriteRecord>>;

ReadOutcome

/** Whether a recorded point read (`tx.get`/`tx.row`) found a row. */
export type ReadOutcome = "present" | "absent";

ReadRecord

/** One recorded point read, pk-granular (§3.2 #2). Recording-mode only — see {@link ReadLog}.
 *  Since H-ii this covers BOTH the public reads (`tx.get`/`tx.row`) and the keyed writers'
 *  internal pre-existence probes (§3.2 #3 — see the `rawGet` note in {@link trackingTx}). */
export interface ReadRecord {
    table: string;
    pk: WireValue[];
    outcome: ReadOutcome;
}

ReadLog

/** The read-log captured over ONE mutator invocation when recording is armed (RINDLE-REALTIME-
 *  QUERY-ENABLEMENT-DESIGN.md §3.2 #2): every point read (`reads` — the public `tx.get`/`tx.row`
 *  and, since H-ii, the keyed writers' internal pre-existence probes) plus every resolved query AST
 *  (`queries`, from `tx.query`). A SIBLING of the folded read TRAP (`FoldReadError` below) — the
 *  trap arms on the folded path and throws before any read completes (recording never runs there);
 *  recording arms on the ordinary (non-folded) prediction run and never throws. Pure capture for
 *  devtools/inspection (the §3 routing derivation it once fed was removed by
 *  302-ROOM-STORE-SEPARATION-DESIGN.md §5 — mutators DECLARE their domain now). */
export interface ReadLog {
    reads: ReadRecord[];
    queries: Ast[];
}

FoldClock

/** A virtual-clock seam for fold debounce timers and elapsed-time checks (FOLDED-MUTATIONS-DESIGN §9): the
 *  oracle injects a deterministic scheduler; production defaults to real timers + `Date.now`. */
export interface FoldClock {
    setTimeout(cb: () => void, ms: number): unknown;
    clearTimeout(handle: unknown): void;
    now(): number;
}

FoldOptions

/** Options for a folded call site (FOLDED-MUTATIONS-DESIGN §3). `key` is the identity half of the
 *  fold key (combined with the mutator name); the rest tune the debounce policy. */
export interface FoldOptions {
    /** The identity half of the fold key (typically the targeted primary key). Required. */
    key: unknown;
    /** Trailing debounce: the flush fires this long after the LAST invoke for `key`. Default 120ms. */
    debounceMs?: number;
    /** Elapsed-time threshold checked on each invocation. Once reached, that invocation flushes
     *  immediately. This does not arm a separate timer: without another invocation, the trailing
     *  debounce still controls the flush. Omit to use only the trailing debounce. */
    maxWaitMs?: number;
    /** For a declared room route, use this value for both `debounceMs` and `maxWaitMs`.
     *  The domain policy selects the route on the window's first invocation; it stays fixed
     *  for that window. `0` flushes each invocation. Omit to use the ordinary fold options.
     *  This does not detect collaborators or infer a route from the writes. */
    roomDebounceMs?: number;
    /** Keep deferring across overlapping non-fold writes for maximum economy, accepting the §4.2
     *  read-dependent reorder snap. Default `false` (flush-on-enqueue — correct-and-boring). */
    deferAcrossWrites?: boolean;
}

FoldHandle

/** A folded mutation window. `flush()` assigns its mutation ID and queues the latest arguments
 *  for delivery. `mid` resolves with that ID at flush; it does not wait for server acceptance
 *  or confirming-stream progress. Each call in the same window shares this promise. */
export interface FoldHandle {
    flush(): void;
    readonly mid: Promise<number>;
}

ScopeSessionsEvent

/** One I-iv doorbell event ({@link OptimisticBackend.onScopeSessions}, §4.1): a release folded
 *  scope-session rows for `scope`, and `others` is the count of OTHER clients' unexpired sessions
 *  there — {@link OptimisticBackend.otherScopeSessions} evaluated at fold time (the same one rule,
 *  on the injectable {@link FoldClock}, so a virtual-clock harness gets deterministic verdicts).
 *  The consumer (client.ts) triggers its one debounced re-lease on the 0→≥1 transition; expired
 *  and own-clientID rows never count, so a solo client's own row can never ring its own bell. */
export interface ScopeSessionsEvent {
    scope: string;
    others: number;
}

DowngradeStuckEvent

/** The I-v stuck-downgrade event ({@link OptimisticBackend.onDowngradeStuck}): the ghost's fence
 *  is satisfied but these SENT room-domain mids never resolved (an entry that never reached the
 *  room — sent-but-undelivered when the socket died — is undecidable in general, §7.5). The ghost
 *  HOLDS (fail LOUD, never silent; no timeout-retire is invented) and the mids are surfaced once,
 *  actionably. */
export interface DowngradeStuckEvent {
    sourceKey: string;
    doc: string;
    mids: number[];
}

OptimisticBackendOptions

export interface OptimisticBackendOptions {
    /** Stable per-client identity for the upstream envelopes (§8.1). */
    clientID: string;
    /** Supplies a shared mutator's local `ctx.user` on every run, including reconciliation.
     *  Keep the principal stable for this client's lifetime; recreate the client on account changes.
     *  The server obtains its own authenticated principal. Defaults to the empty string. Plain
     *  client-only mutators ignore this option. */
    user?: () => string;
    /** Buffered-frame ceiling before the §8.5 escape (drop + re-hydrate). */
    bufferCap?: number;
    /** Virtual-clock seam for the fold debounce timers (FOLDED-MUTATIONS-DESIGN §9). Defaults to
     *  real `setTimeout`/`clearTimeout`/`Date.now`; the fold oracle injects a deterministic clock. */
    clock?: FoldClock;
    /** The DECLARED confirming stream per mutation (302 §5: declared, not derived — there is no
     *  routing proof). A policy returning a string pins that domain verbatim: the mutation stages
     *  onto that room's namespaced tables and ships on its channel. Returning `undefined` (or
     *  configuring no policy) means `"daemon"`. The client layer builds this from the app's declared
     *  realtime mutators + the currently attached rooms; a misdeclaration fails SOFT (302 §5.1) —
     *  the write lands on the other authority's tables and the view simply stops feeling instant
     *  until the echo relays it. */
    domainPolicy?: (name: string, args: unknown) => string | undefined;
    /** A FINAL (authz/validation) mutation rejection's reason surface — the room plane's twin of the
     *  HTTP mutate route's `onRejected` (H-v; the H-iv-b `mutationOutcome {kind:"rejected"}` frame).
     *  The prediction's snap-back is NOT this callback's job: the room burns the mid and its lmid
     *  release drops the entry exactly as a daemon-path rejection does (processed-as-no-op) — this
     *  is where the REASON reaches the app, same contract as the queue's callback. Also invoked when
     *  a DEOPT's fresh re-invocation (the already-retired arm) throws — that mutation is dead on the
     *  current base with no stream left to confirm it, the closest thing to a rejection there is. */
    onRejected?: (envelope: MutationEnvelope, reason: string) => void;
}

FoldInspect

/** One folded entry's debounce window, for the timeline's fold drill-down (§4.1). */
export interface FoldInspect {
    /** The fold key (`${name}\0${identityJSON}`) collapsing same-key invokes into one entry. */
    foldKey: string;
    debounceMs: number;
    maxWaitMs?: number;
    deferAcrossWrites: boolean;
    /** Whether the window has flushed (a real `mid` was dealt); an un-flushed fold has `mid == null`. */
    flushed: boolean;
}

PendingInspect

/** One pending mutation, as the timeline sees it (DEBUG-TOOLS-BROWSER-DESIGN §4.1). */
export interface PendingInspect {
    /** Stable identity across snapshots: `m:<mid>` once a mid is assigned, else `f:<foldKey>` for an
     *  un-flushed folded entry (the mid is dealt at flush, FOLDED-MUTATIONS-DESIGN §4.1). */
    key: string;
    /** The wire mutation id, or `null` for an un-flushed fold. */
    mid: number | null;
    name: string;
    args: unknown;
    /** Tables this mutator touched at its last (re)invocation — the pending-axis basis (§7.2). */
    tables: string[];
    /** The pk-granular write-set captured at this entry's LAST invocation (RINDLE-REALTIME-QUERY-
     *  ENABLEMENT-DESIGN.md §3.2 #1), flattened from the {@link WriteSet} map for inspection — one
     *  entry per `(table, pk)` currently held. Pure capture; no routing consumer yet. */
    writes: WriteRecord[];
    /** The read-log captured at this entry's LAST *recorded* invocation (§3.2 #2). Empty for a
     *  folded entry — the read TRAP arms there, not recording (see {@link PendingMutation.reads}). */
    reads: ReadLog;
    /** Present iff this entry is a folded (debounced) write. */
    fold?: FoldInspect;
}

OptimisticInspect

/** A read-only snapshot of the optimistic loop ({@link OptimisticBackend.__inspect}). */
export interface OptimisticInspect {
    /** The pending stack in array order (assigned mids ascending, un-flushed folds interleaved by
     *  creation — the re-invoke order is derived from this in `runReconcileCycle`). */
    pending: PendingInspect[];
    /** High-water confirmed mid: an entry with `mid <= confirmedLmid` has been confirmed/dropped. */
    confirmedLmid: number;
    /** The next mid to be dealt (so `nextMid - 1` is the highest issued). */
    nextMid: number;
    /** The applied coherent-release watermark (`cvMin`, §8.6). */
    appliedCv: number;
    /** Frames still buffered awaiting their release point (§8.5) — a backpressure gauge. */
    bufferedFrames: number;
    /** Every table some pending mutation currently touches (the coarse pending indicator set, §7.2). */
    pendingTables: string[];
}

OptimisticBackend

export declare class OptimisticBackend<S extends ColsMap> implements Backend {
    private readonly local;
    private readonly sync;
    private readonly source;
    private readonly registry;
    private readonly clientID;
    /** The acting principal provider for a shared mutator's `ctx.user` (§ shared mutators). */
    private readonly user;
    private readonly bufferCap;
    /** Column order + pk indices per table, for the keyed `MutationTx` methods. */
    private readonly specs;
    /** Local-only table names (`201-LOCAL-ONLY-TABLES-DESIGN.md` §4). Drives: the agg-rewrite gate
     *  (L1 — a local-child count stays a native reduce), the mutator guard (M1 — a replayable
     *  mutator may not read/write one), and `writeLocal` (M2 — it accepts ONLY these). */
    private readonly localTables;
    /** Each table's full column count (union-row width) + column-name → base ColId — to learn a
     *  projected query's per-table projection off its `hello` and register it with the sync layer,
     *  so it scatters that query's narrower rows into the shared union (PROJECTION-SUPPORT-DESIGN
     *  §5.2). Without this a projected query's short rows reach the wasm `Db` un-scattered and fail
     *  its width check. */
    private readonly colCounts;
    private readonly colIndex;
    /** Per-table pk column indices — held so `connectSource` can build a fresh per-source
     *  `NormalizedSync` with the same layout the daemon's uses. */
    private readonly pkCols;
    /** The client's OWN typed per-table schemas + the reserved lmid table — the fixed base
     *  of the expected-schema set (CRIT#4 validation). Synthetic agg tables are appended as
     *  queries arrive (`ensureSyntheticTables`). */
    private readonly clientTablesBase;
    /** Synthetic aggregate tables (`__agg_*`) registered so far, by name (AGGREGATE-SYNC-DESIGN
     *  §3.3). Per aggregate DEFINITION (not per query), so two queries over the same count
     *  share one table. */
    private readonly synthetic;
    /** Synthetic table name → how many registered LOCAL queries reference it. A table is
     *  materialized on the `0→1` transition and reclaimed (engine source + baseline + refcount
     *  layer + overlay def) on `1→0` — so aggregate state is not permanent (§4). */
    private readonly syntheticRefs;
    /** Local qid → the synthetic tables it referenced at registration, to decrement on teardown. */
    private readonly queryAggTables;
    /** The optimistic aggregate overlay (§4–§6): the per-aggregate definitions + the per-group
     *  pending delta `displayed = server_base ⊕ local_pending_delta` is applied from. */
    private readonly overlay;
    private handler;
    private catchUpQids;
    /** Newly-hydrated qids whose reconcile ACTUALLY emitted a (catch-up-stamped) batch — recorded by the
     *  local-event forwarder alongside {@link catchUpQids}. After the reconcile, any newly-hydrated qid
     *  NOT in here folded nothing (0 rows, or its result already present via a sibling → 0 net muts, or
     *  the reconcile was skipped), so `onProgress` sends it an explicit empty catch-up — else its SSR
     *  seed would never retire (the view freezes). Non-null only for the reconcile's duration. */
    private catchUpEmitted;
    /** The Store's commit-boundary handler ({@link Backend.onCommitBoundary}), forwarded from the
     *  local engine's `dispatch` brackets so the Store folds every affected view before notifying any
     *  subscriber (cross-view-atomic notification). All this backend's data deltas originate from the
     *  local engine, so its commit brackets are this backend's commit brackets. */
    private boundaryHandler;
    private readonly devObservers;
    private pendingMutations;
    /** The next mid to deal, PER DOMAIN (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §7.1): a client
     *  writing through room + daemon concurrently must not alias one lmid counter. Seeded with the
     *  `"daemon"` stream at 1; a domain absent from the map starts at 1. In the single-domain
     *  configuration only `"daemon"` is ever touched, so the sequence is byte-for-byte as before. */
    private nextMid;
    /** The client-global deal counter behind {@link PendingMutation.seq}: one sequence across ALL
     *  domains, bumped whenever any domain's mid is dealt. The replay order (mids are per-domain and
     *  incomparable across domains — see the `seq` field doc). */
    private dealSeq;
    /** The explicit confirming-stream override (§7.1/§3) — see
     *  {@link OptimisticBackendOptions.domainPolicy}. `undefined` from it ⇒ H-iii derivation. */
    private readonly domainPolicy;
    /** The final-rejection reason surface ({@link OptimisticBackendOptions.onRejected}). */
    private readonly rejectedHandler;
    /** Processed `(domain, mid)` outcome frames (H-v) — the deopt handshake's idempotence guard: a
     *  duplicate frame (the original plus a reconnect re-send's re-answer, or two re-answers across
     *  two reconnects) must not double-invoke. Needed precisely because a deopt frame can arrive for
     *  an ALREADY-RETIRED mid (the replay gotcha) — "no matching entry" alone cannot distinguish
     *  "handle it fresh" from "already handled". Per-domain FIFO, capped like the shell's
     *  recorded-outcome map ({@link MAX_PROCESSED_OUTCOMES_PER_DOMAIN}); past the cap a duplicate of
     *  an evicted mid would be re-processed — the same bounded-window trade the shell makes, and it
     *  takes 512 interleaving non-applied outcomes on one domain to open it. */
    private readonly outcomesProcessed;
    /** THE room-table registry (302 §2 — one source per table): per connected room `sourceKey`, the
     *  wire-table → engine-table map for the tables that room OWNS (its writable scope). Written by
     *  {@link registerRoomTables} (same breath as the engine registration); read by the gate's
     *  release rename/filter, the mutator staging map, the view swap ({@link processSwapIns}), and
     *  the client's `__realtimeInspect` bookkeeping. The record outlives a downgrade's disconnect —
     *  the ghost's views still read the engine tables — and drops at {@link dropGhost} (or the last
     *  clean release via {@link unregisterRoomTables}). */
    private readonly roomTables;
    /** Local view qids currently REGISTERED on a room's namespaced tables (302 §4 swap-in), →
     *  their sourceKey. Set by {@link processSwapIns}; cleared by the swap-back ({@link dropGhost})
     *  and view teardown. The original AST stays in {@link asts} throughout — the swap re-registers
     *  only the ENGINE query. */
    private readonly roomSwappedViews;
    /** Room subs whose FIRST snapshot released in the current release — their views swap onto the
     *  room tables at the release tail ({@link processSwapIns}), strictly AFTER the reconcile folded
     *  the snapshot into those tables (swapping earlier would hydrate the view EMPTY, a flash). */
    private readonly pendingSwapIns;
    /** The live fold entries, by fold key `${name}\0${identityJSON}` — at most one per key
     *  (FOLDED-MUTATIONS-DESIGN §8). Insertion order is creation order (the drain/flush tiebreak). */
    private readonly folds;
    /** The fold debounce clock (real timers by default; the oracle injects a virtual one). */
    private readonly clock;
    /** The high-water confirmed mutation id, PER DOMAIN (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md
     *  §7.2 per-domain confirm-drop): an entry with `mid <= watermark[entry.domain]` has been
     *  confirmed. The `"daemon"` domain is folded from the lmid system query's RELEASED ops
     *  (lmid-as-data) — never from a frame; a room domain will fold from its own lmid stream (later
     *  slice). Seeded with `"daemon"` at 0; the daemon scalar `confirmedLmid` (devtools) is
     *  `watermark.get("daemon")`. */
    private watermark;
    /** The per-source coherence gates (§5.1), by source key. Seeded with the daemon gate at
     *  construction; a room channel attaches later (`connectSource`). Single-domain: one entry,
     *  and every gate-generalized path degenerates to the old single-buffer code. NOT the same
     *  space as {@link watermark}/{@link nextMid}: a DOMAIN can confirm with no gate connected
     *  (the `__testRelease` seam); a gate's `key` names the domain its lmid stream folds into. */
    private readonly gates;
    /** The daemon's gate — the always-present channel (constructor-attached). The devtools
     *  scalars (`__inspect`) read it directly; its `sync` IS {@link sync} (the agg overlay and
     *  synthetic tables are daemon-tracked by design). */
    private readonly daemonGate;
    /** System retains by source qid ({@link retainSystemQuery}): a subscription with NO store view
     *  and NO user-visible table — its frames buffer on its gate exactly like {@link LMID_QID}'s and
     *  fold at RELEASE time ({@link foldSystemFrames}), never entering the sync layer or the local
     *  engine. The spec names which system table the qid serves and the scope/doc it was minted for
     *  (the fold's row filter). Empty on every non-lifecycle client — every partition below is then
     *  a structural no-op and the release path is byte-identical to before. */
    private readonly systemQids;
    /** The §4.2 fence state: room doc → highest `flush_seq` delivered through the daemon plane
     *  (monotone max-fold; a remove never regresses it). Slice I-v's ghost-drop consumer — I-iii
     *  only maintains + exposes it (`__inspectDomains().lifecycle`). */
    private readonly roomWatermarks;
    /** The §4.1 occupancy state: scope → (client_id → expires_at) from the doorbell stream. Slice
     *  I-iv's doorbell consumer (the 1→2 re-lease reaction) — I-iii only maintains + exposes it.
     *  A snapshot REPLACES the scope's map (authoritative re-hydrate); a batch folds add/edit/remove
     *  incrementally (the age-out sweep's deletes arrive as removes). */
    private readonly scopeSessions;
    /** The I-iv doorbell event sink ({@link onScopeSessions}) — fired once per scope a release's
     *  scope-session fold touched, AFTER the whole release applied. Default no-op: a client that
     *  never registers (no lifecycle plane) pays nothing. */
    private scopeSessionsHandler;
    /** Deferred old-channel row GC for in-flight upgrade retargets ({@link retargetRemoteQuery}):
     *  sub sourceQid → the channel it left. The rows the OLD gate's sync holds for the qid stay
     *  visible (merge: daemon tier) until the sub's first snapshot RELEASES on its new room channel
     *  ({@link flushRetargetGc}) — dropping them at retarget time would emit net removes ahead of
     *  the room's re-adds, the flicker the two-phase cutover exists to avoid. Doubles as the
     *  wrong-channel GRACE window in {@link onFrame}: a frame already in flight from the old
     *  channel when the sub moved is stale, not a wiring bug. Empty on every non-upgrade client —
     *  every consultation below is then a structural no-op. */
    private readonly pendingRetargetGc;
    /** The §4.2 GHOSTS (Slice I-v): demoted room sources awaiting their watermark fence, by
     *  sourceKey. Written only by {@link demoteRoomSource}; evaluated after every release
     *  ({@link evaluateGhosts}) and dropped by {@link dropGhost} once the fence clears with no
     *  sent room-domain pending left. Empty on every non-downgrade client — the per-release
     *  evaluation is then a structural no-op. */
    private readonly ghosts;
    /** The I-v stuck-downgrade surface ({@link onDowngradeStuck}) — fired AT MOST ONCE per ghost
     *  when its fence is satisfied but sent room-domain mids remain unresolved (§7.5: they retire
     *  only through outcome resolution; the ghost holds rather than inventing a timeout-retire).
     *  Default no-op. */
    private downgradeStuckHandler;
    private readonly asts;
    /** Per query: the base tables its result can draw from (from the AST tree). */
    private readonly queryTables;
    private readonly remoteSubs;
    private readonly sourceToRemote;
    private readonly localToRemote;
    private readonly remoteRetainToLocal;
    private readonly resultTypes;
    /** Local view qids that are server-authoritative: a query with no remote sub (purely local) is
     *  hydrated on registration; a remote query is hydrated when its sub's first snapshot releases.
     *  An un-hydrated query reports `unknown` (still loading) — the basis of `resultType`. */
    private readonly hydrated;
    private resultTypeHandler;
    /** The pending AXIS (§7.2), split off `ResultType`: per query, whether any pending mutation
     *  touches its tables. Cached so `onPending` fires only on transitions (invoke ↔ confirm). */
    private readonly pendingState;
    private pendingHandler;
    /** The local-persistence write-through tap (`207-LOCAL-TABLE-PERSISTENCE-DESIGN.md` §5.1):
     *  {@link writeLocal} invokes it post-commit; {@link applyLocalReplica} deliberately does not. */
    private localWriteObserver;
    constructor(schema: Schema<S>, source: OptimisticSource, registry: ClientRegistry, opts: OptimisticBackendOptions);
    /** Wire one authority channel into its own coherence gate (§5.1): every frame the channel
     *  delivers buffers on THIS gate's cv timeline, its progress frames release THIS buffer, its
     *  restart resets THIS gate alone, and its reserved lmid stream folds into `watermark[key]`.
     *  Validates each server hello against our OWN typed schema → reject a schema skew (CRIT#4);
     *  the reserved lmid table is part of the expected set so the system query's hello passes, and
     *  synthetic agg tables join the set as queries register them. */
    private attachGate;
    /** Attach a SECOND authority channel (§5.1) — the seam Slice G's room upgrade calls with the
     *  ws-backed room feed. Rooms speak the daemon protocol verbatim (§2.4: the client cannot tell
     *  a room from the daemon), so the argument is a full {@link OptimisticSource} — exactly what
     *  `@rindle/remote` builds from `{roomUrl, leaseToken}`. The channel buffers/releases on its
     *  own cv timeline (an independent §5.1 gate: coherent within, eventual across) and its
     *  reserved lmid stream folds into `watermark[sourceKey]` — so `sourceKey` must equal the
     *  `domainPolicy` name for the mutations this authority confirms. The converse is NOT required:
     *  a domain may exist with no connected gate (`__testRelease` drives confirms gate-less); the
     *  live production path stays daemon-only until G calls this. */
    connectSource(sourceKey: string, source: OptimisticSource): void;
    /** Register the tables room `sourceKey` OWNS (its writable scope — 302 §2): each wire table
     *  gets its own namespaced ENGINE table (`{@link roomEngineTable}`), an ordinary tracked table
     *  whose sole authority is the room channel. From here on the channel's released deltas rename
     *  into these tables (wire tables outside the map are DROPPED — context stays daemon-owned,
     *  302 §6), room-domain mutators stage onto them, and a room-homed view swaps onto them once
     *  the room sub hydrates ({@link processSwapIns}). Idempotent per (sourceKey, table); a wire
     *  table unknown to the schema is skipped (nothing to hold rows for). */
    registerRoomTables(sourceKey: string, tables: readonly string[]): void;
    /** The wire-table → engine-table map for room `sourceKey`'s owned tables (empty when none) —
     *  the client's idempotence check and `__realtimeInspect` read THIS record (one source of
     *  truth; the client keeps no shadow copy). */
    roomTablesFor(sourceKey: string): ReadonlyMap<string, string>;
    /** `channel` (G-iii registration-time routing) names the authority channel the remote sub
     *  registers on — a `connectSource`d gate key; default `"daemon"` (every existing caller is
     *  byte-identical). Slice G-v threads the lease's `realtime.sourceKey` here. Validated FIRST
     *  (like the E3 check below): a bad channel must throw before any per-query state is recorded. */
    registerQuery(qid: QueryId, ast: Ast, remote?: RemoteQuery, channel?: string): void;
    /** Register every synthetic aggregate table `ast` needs that we haven't seen yet: on the
     *  local engine (which auto-tracks it for the optimistic rebase loop), on `NormalizedSync`
     *  (so its rows refcount/GC by group key), and into the source's expected-schema set (so
     *  the server's `hello` — which advertises the same table — passes CRIT#4 validation).
     *  Idempotent across queries that share an aggregate definition. */
    private ensureSyntheticTables;
    /** Decrement the refcount of every synthetic table query `qid` referenced; for each one that
     *  reaches 0 (no live reader left), remove it from the engine, the refcount layer, and the
     *  overlay — so aggregate state is reclaimed, not permanent (§4). Must run AFTER
     *  `local.unregisterQuery(qid)` so the engine source has no live connection when
     *  `unregisterTable` frees it (the engine refuses otherwise). */
    private releaseSyntheticTables;
    unregisterQuery(qid: QueryId): void;
    /** `channel` as in {@link registerQuery} (G-iii): the gate the remote sub registers on; default
     *  `"daemon"`. This is the split-retain seam G-v's resolve-then-register drives — resolve the
     *  lease, learn `realtime.sourceKey`, `connectSource` it, then retain the query on that channel.
     *  Validated FIRST so a bad channel throws before any synthetic-table refcount moves. */
    retainRemoteQuery(qid: QueryId, remote: RemoteQuery, localQueryId?: QueryId, ast?: Ast, channel?: string): void;
    releaseRemoteQuery(qid: QueryId): void;
    /** The Slice I-iv upgrade retarget (§4.1 "Retarget" / the doorbell reaction): move a LIVE
     *  (name, args) sub — every retain of it and every local view it feeds, wholesale — from the
     *  channel it lives on onto `sourceKey`'s (already-`connectSource`d, already-promoted) room
     *  channel, WITHOUT the view ever dropping its rows. Returns the sub's wire `sourceQid` (the
     *  identity the client's renewal loop re-subscribes with).
     *
     *  Why a dedicated primitive: the one-channel-per-(name,args) invariant ({@link retainRemote}'s
     *  loud throw) is correct — a sub's frames must never split across two cv timelines — so the
     *  upgrade cannot simply retain a second sub on the room and release the daemon one; and the
     *  naive release-then-retain order GCs the daemon sync's rows synchronously (net removes emit,
     *  the view flashes empty) a full ws round trip before the room's seq-0 snapshot refills it.
     *  The cutover is therefore TWO-PHASE around the room's first release:
     *
     *   1. NOW (here): unsubscribe the old channel's wire sub, sweep its still-buffered frames for
     *      this qid (their cv timeline continues without the sub — the hello-supersession
     *      precedent), flip `sub.channel`, re-arm `sub.hydrated` (the room's own snapshot is the
     *      cutover point), and register on the room source (its resolver presents the handed
     *      roomToken). The old gate's SYNC rows are deliberately NOT dropped: they keep the view's
     *      plain tables populated through the window — the view still reads them until the swap.
     *   2. AT THE ROOM'S FIRST RELEASED SNAPSHOT: the reconcile folds the snapshot into the room's
     *      namespaced tables, the release tail SWAPS every local view onto them (302 §4.1,
     *      {@link processSwapIns} — the accepted-flash boundary), and {@link flushRetargetGc}'s
     *      deferred `dropQuery`+reconcile on the OLD gate then GCs the plain-table rows the sub
     *      alone referenced — invisible to the swapped views.
     *
     *  Idempotent per target channel: a sub already on `sourceKey` returns immediately (the
     *  double-doorbell / re-entrancy guard — one retarget per (query, sourceKey)). Validates before
     *  mutating: a throw here leaves the sub fully daemon-attached (the client's fail-open). */
    retargetRemoteQuery(remote: RemoteQuery, sourceKey: string): QueryId;
    /** Phase 2 of {@link retargetRemoteQuery}, run at the end of every gate release: once a
     *  retargeted sub's first snapshot has RELEASED on its new channel (`sub.hydrated` re-armed at
     *  retarget, re-set by {@link markSubHydrated} inside this very release), drop the qid's rows
     *  from the OLD gate's sync and reconcile them out — after the room's rows are already applied,
     *  so the winner flip is value-equal (net-zero; see the phase table above). A sub torn down
     *  mid-window was already swept by `releaseRemoteQuery`/`unregisterQuery` (which delete the
     *  record); a vanished record here is pruned defensively. */
    private flushRetargetGc;
    /** The I-v downgrade orchestration primitive (§4.2/§7.4, re-expressed by 302 §4.2 as the
     *  SWAP-BACK GATE): retire room `sourceKey` behind the watermark fence. The caller has ALREADY
     *  retargeted every live sub off the channel ({@link retargetRemoteQuery} room→daemon —
     *  validated loudly below) and holds the fence from the api-server's downgrade response
     *  (`finalFlushSeq` = the room's last COMMITTED flush seq; `doc` keys the §4.2 watermark fold,
     *  {@link roomWatermarks}). Steps, in order:
     *
     *   1. **Disconnect** the channel ({@link disconnectSource}): handlers detached, gate + buffer
     *      dropped. `nextMid`/`watermark`/processed-outcomes for the domain are KEPT FOREVER (§7.1:
     *      an assigned mid pins its domain; a later re-upgrade of the same doc continues the
     *      sequence — {@link connectSource} attaches a fresh gate and the lmid snapshot max-folds
     *      into the surviving watermark). Disconnecting BEFORE the daemon sub's first release is
     *      load-bearing: it makes {@link flushRetargetGc}'s deferred old-channel GC a no-op (gate
     *      gone ⇒ record deleted, nothing dropped). The room's namespaced tables — and the views
     *      swapped onto them — deliberately stay: frozen at the room's last state, they keep the
     *      document visible while the falling-back follower may still lack the final flush.
     *      Swapping back earlier would show its pre-flush images — the regression §4.2 prevents.
     *   2. **Ghost + first evaluation**: the record joins {@link ghosts} and is evaluated once
     *      immediately — `finalFlushSeq === 0` (a never-flushed room) with no room-domain pending
     *      drops on the spot, the single-daemon first-frame case.
     *
     *  In-flight discipline (§7.5): entries with `mid !== null` on `sourceKey` stay PINNED (rule
     *  2 — never re-route a sent mutation); their resolution arrives via the daemon-carried
     *  ledger+outcome folds (I-iii) and blocks the drop until then. Idempotent per sourceKey (a
     *  second labeled query sharing the room demotes into the existing ghost). */
    demoteRoomSource(sourceKey: string, doc: string, finalFlushSeq: number): void;
    /** Detach one connected room channel (Slice I-v step 3): the source's handlers are replaced
     *  with no-ops (the {@link OptimisticSource} handler seam is single-registration, so this IS
     *  the detach — a late frame from a dying socket can no longer touch any bookkeeping), its
     *  reserved lmid sub is unregistered, and the gate — buffer, per-source sync, cv watermark —
     *  is dropped from {@link gates}. The DOMAIN state deliberately survives forever:
     *  `nextMid[sourceKey]`, `watermark[sourceKey]`, and the processed-outcome set are untouched
     *  (§7.1 — an assigned mid pins its domain; a re-upgrade must continue, never restart, the mid
     *  sequence; {@link connectSource} then attaches a fresh gate whose lmid snapshot max-folds
     *  into the surviving watermark via {@link foldConfirm}). Closing the underlying transport is
     *  the caller's job. Idempotent (a missing gate is a no-op). */
    disconnectSource(sourceKey: string): void;
    /** Register the I-v stuck-downgrade sink — see {@link DowngradeStuckEvent}. One handler (a
     *  later registration replaces it, the {@link onScopeSessions} convention); client.ts maps it
     *  onto the loud anomaly surface. */
    onDowngradeStuck(handler: (event: DowngradeStuckEvent) => void): void;
    /** The I-v ghost-drop watcher (§4.2), run after every applied release ({@link applyRelease} —
     *  the seam where {@link roomWatermarks} has just folded and the confirm-drop has just run) and
     *  once at demote time. For each ghost: the fence must be satisfied
     *  (`roomWatermarks[doc] ≥ finalFlushSeq`; 0 is trivially satisfied) AND no SENT room-domain
     *  pending may remain (§7.5 — such entries resolve only through the daemon-carried
     *  outcome/ledger folds; an entry that never reached the room is undecidable, so the ghost
     *  HOLDS and the stuck event fires exactly once, naming the mids). Both satisfied ⇒
     *  {@link dropGhost}. */
    private evaluateGhosts;
    /** Drop one cleared ghost — the 302 §4.2 SWAP-BACK: under the fence the daemon tables are
     *  value-equal-or-ahead of the room's final state, so (1) every view swapped onto the room's
     *  namespaced tables re-registers on its ORIGINAL (daemon-table) AST — visually a no-op, the
     *  Store folds the re-hello as an in-place reset; (2) the namespaced tables unregister (no
     *  reader is left after the swap); (3) ONE daemon reconcile re-invokes the pending set so any
     *  entry whose writes had staged onto the now-gone room tables re-stages onto the daemon tables
     *  (its domain policy stopped naming the dead room when the client dropped it). The whole drop
     *  runs under one commit boundary so the swap and the re-staged predictions notify as ONE step.
     *  After this, a FUTURE upgrade of the same doc registers again from scratch. */
    private dropGhost;
    /** Unregister room `sourceKey`'s namespaced engine tables and drop the {@link roomTables}
     *  record. Callers must have no view registered on them (the engine refuses otherwise —
     *  loud by design). No-op for an unknown sourceKey. */
    unregisterRoomTables(sourceKey: string): void;
    /** Retain one minted SYSTEM subscription (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §4, Slice
     *  I-iii): a wire sub with NO store view and NO user-visible table. Registered through the same
     *  {@link RemoteSub} bookkeeping as any remote retain — so qid→channel ownership, the overflow
     *  re-subscribe, and refcounted release all work unchanged — but with an EMPTY `localQids` set
     *  (no hydration/resultType coupling) and a {@link systemQids} record telling the release path
     *  which system table this qid's frames carry (`spec.table`) and which scope/doc it was minted
     *  for (the fold's row filter). Its frames then buffer on the channel's gate exactly like
     *  {@link LMID_QID}'s and fold at RELEASE time in {@link foldSystemFrames} — riding the SAME
     *  buffered cv path as the data they co-committed with (fence coherence: an out-of-band
     *  shortcut would break I-ii's co-commit ordering guarantee).
     *
     *  `channel` defaults to `"daemon"` — the system tables live in the DAEMON store (that is the
     *  point: outcome/ledger/watermark rows must be readable with no room socket alive, §7.1
     *  "load-bearing for §7.5"). Idempotence per (table, scope/doc) is the CALLER's job (client.ts
     *  keys its retains on exactly that); a duplicate retain of the SAME remote identity refcounts
     *  like any sub. */
    retainSystemQuery(retainQid: QueryId, remote: RemoteQuery, spec: SystemStreamSpec, channel?: string): void;
    /** Release a {@link retainSystemQuery} retain. Refcounted like any sub; the LAST release
     *  unregisters from the owning channel, sweeps its buffered frames, and drops the
     *  {@link systemQids} record. The folded lifecycle STATE (`roomWatermarks`/`scopeSessions`/
     *  processed outcomes) deliberately survives — the fence is monotone truth about the store, not
     *  about the subscription (a re-retained fence must not forget a cleared watermark). */
    releaseSystemQuery(retainQid: QueryId): void;
    /** Raw CRUD has no optimistic story (§9 replaces it with named mutators). Register a
     *  mutator — even a trivial one — and `invoke` it. */
    mutate(_mutations: Mutation[]): Promise<void>;
    /** Direct-commit a batch of LOCAL-only writes (`201-LOCAL-ONLY-TABLES-DESIGN.md` §6 / M2):
     *  straight to the local engine, OUTSIDE the optimistic pending stack — a local table is
     *  untracked, so it never rebases, reverts, or waits on a confirmation. Rejects a synced/tracked
     *  table (the local engine's `writeLocal` is the chokepoint). Reconcile is synchronous and
     *  non-reentrant (A5), so a local write can never interleave with an open server cycle.
     *
     *  Fires the {@link onLocalWrite} observer AFTER the engine commit but BEFORE subscriber
     *  delivery — i.e. for exactly the batches that passed the M2 guard and committed (the
     *  persistence layer's write-through tap, `207-LOCAL-TABLE-PERSISTENCE-DESIGN.md` §5.1). A
     *  subscriber throwing during delivery re-raises out of this call, but only after the tap has
     *  seen the batch: a committed write can never be invisible to the persistence plane. */
    writeLocal(mutations: Mutation[]): void;
    /** The write-through tap for the local-persistence layer (207 §5.1): `observer` sees every
     *  {@link writeLocal} batch post-commit. One observer (the layer); a later registration
     *  replaces it. The observer must not throw — a persistence failure degrades durability, never
     *  the write path (P9); the layer catches internally. */
    onLocalWrite(observer: (mutations: Mutation[]) => void): void;
    /** Apply a REPLICATED local batch (a restore snapshot / a leader `commit` — 207 §5.1): delegates
     *  to the engine's `writeLocal`, so the M2 locality guard still fires (P8 — a corrupt record
     *  naming a synced table dies loudly here), but does NOT invoke the {@link onLocalWrite}
     *  observer — the echo guard is structural, so the persistence layer can never re-enter itself.
     *  `onCommitted` fires post-commit pre-delivery (same anchor as {@link writeLocal}'s tap): the
     *  layer updates its mirror there, so a subscriber throw can never desync mirror from engine. */
    applyLocalReplica(mutations: Mutation[], onCommitted?: () => void): void;
    onEvent(handler: (qid: QueryId, ev: ChangeEvent) => void): void;
    onCommitBoundary(handler: (phase: "begin" | "end") => void): void;
    /** Bracket a multi-step optimistic apply as ONE notification commit (cross-view-atomic
     *  notification — {@link Backend.onCommitBoundary}). `invoke`/`invokeFolded` apply the prediction
     *  and then reconcile the `__agg` head in TWO `this.local` commits, but they are two halves of one
     *  logical mutation: a relationship-`count` view and the data view it counts must update together.
     *  The Store's `commitDepth` is a counter, so each inner commit's own `begin`/`end` nests under this
     *  outer pair and the Store flushes every affected view (data AND count) once, together, at the
     *  outer `end` — a subscriber re-reading a sibling view then sees post-commit data, never a torn
     *  half. Balanced on throw (the prediction mutator may reject) via the `finally`, so a thrown
     *  prediction never wedges the Store in deferred mode. */
    private inOneCommit;
    /** Run one client mutator against the staged `tx`, accepting BOTH forms (§ shared mutators):
     *  a plain sync function runs as-is; a shared GENERATOR is driven synchronously — every yielded
     *  write applies to the wasm txn now, every `tx.row` read is resolved against the same staged
     *  state (read-your-writes), the SAME body the API server drives asynchronously. `ctx.user` is
     *  the acting principal (re-read per invoke, stable across a rebase re-invoke). */
    private runMutator;
    /** Deal the next wire mid from `domain`'s ledger (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md
     *  §7.1) and advance that counter. A domain absent from the map starts at 1. Per-domain, so a
     *  client writing through room + daemon concurrently keeps two gapless, non-aliasing sequences.
     *  The client-global `seq` is stamped in the same breath — the ONE cross-domain total order
     *  (confirmation order is per-domain; replay order is client-global). Bundled here so no call
     *  site can deal a mid without its seq. ONE caller discards the seq deliberately: the H-v deopt
     *  flip ({@link handleMutationOutcome}) keeps the entry's ORIGINAL seq — its replay position —
     *  and takes only the fresh mid (the dealSeq bump is harmless: seq consumers order, never
     *  count). */
    private dealMid;
    /** The declared confirming stream for one invocation: the `domainPolicy`'s verdict, `"daemon"`
     *  when it abstains. Resolved BEFORE the prediction runs — the domain picks the staging map
     *  (a room domain stages its owned tables onto the room's namespaced twins). */
    private resolveDomain;
    /** The staging table map for a `domain`-routed prediction ({@link trackingTx}'s `stage`):
     *  wire table → the room's namespaced engine table for the tables the room owns; identity for
     *  everything else (including the whole map for the daemon domain). */
    private stagingMap;
    /** The PLAIN (daemon-homed) engine AST for `ast` — aggregate relationships rewritten to their
     *  synthetic `__agg_*` reads, no room renames. The ONE form every non-swapped engine
     *  registration uses ({@link registerQuery}, {@link dropGhost}'s swap-back) and the base the
     *  swap-in renames ({@link processSwapIns}). */
    private plainEngineAst;
    /** Mutator names the cross-authority warn below already fired for (once per name). */
    private readonly warnedCrossAuthority;
    /** 302 §5.1 dev-time guard: a room-DECLARED mutator wrote tables the room does not own. Those
     *  writes staged onto the PLAIN daemon tables (the staging map covers only owned tables), but
     *  the entry confirms on the ROOM stream — and only the room's OWNED tables flush back to the
     *  daemon, so nothing upstream ever echoes them: once the room confirm retires the entry, the
     *  next release's whole-store rewind reverts them for good. The first-party room shell refuses
     *  such a mutation (the §3.3 deopt/reject backstop re-routes it to the daemon), so this warns
     *  for the shapes where that backstop may be absent (a BYO relay) — loud, once, soft (§5.1:
     *  misdeclarations never throw). */
    private warnCrossAuthorityWrites;
    /** Run the named client mutator optimistically: the prediction applies to the live
     *  engine now (affected views update synchronously), `(mid, name, args)` joins the
     *  pending stack, and the envelope ships upstream. Returns the assigned `mid`. */
    invoke(name: string, args: unknown): number;
    /** {@link invoke} with an optional PINNED confirming domain (H-v): the deopt handshake's
     *  already-retired arm re-invokes the frame's echoed `(name, args)` as a FRESH invocation pinned
     *  to `"daemon"` — an honest re-prediction on the current base, never derived (`pin` bypasses
     *  {@link resolveDomain} entirely, so the router never runs and no Q6 counter moves). Every
     *  other step is `invoke` verbatim: prediction now, capture, drainOverlapping, mid dealt from
     *  the pinned domain's ledger, envelope on its channel. */
    private invokeWith;
    /** Run a FOLDED invoke (FOLDED-MUTATIONS-DESIGN §8): apply the prediction to the live engine now
     *  (like `invoke`), but collapse a run of same-key invokes into ONE pending entry whose `args`
     *  are overwritten in place, debounce the server write, and ship only the last value. The `mid`
     *  is assigned at flush, not here (§4.1) — so the return is a {@link FoldHandle}, not a mid. */
    invokeFolded(name: string, opts: FoldOptions, args: unknown): FoldHandle;
    /** Flush-on-enqueue (§4.2): for each outstanding fold whose touched tables overlap `tables`,
     *  assign its mid NOW and ship it — in creation (insertion) order, so the wire stays gapless. A
     *  `deferAcrossWrites` fold opts out (it keeps deferring, accepting the read-dependent snap). The
     *  incoming write's own fold key (if any) is skipped — it is being folded into, not flushed. */
    private drainOverlapping;
    /** Flush one fold (§8): deal its `mid` from `nextMid` (SEND order — never reserved, so gapless
     *  by construction), stamp the entry, ship the envelope with the LATEST args, resolve the handle.
     *  The entry stays on `pendingMutations` (now with a real mid) until the lmid release confirms it. */
    private flushFold;
    /** The transport a `domain`-confirmed mutation ships on (§7.5 sent-pins-domain: only the
     *  domain's own authority can confirm it, so its channel is the only correct transport). A
     *  domain with NO connected gate ships on the daemon channel — the gate-less configurations
     *  (`__testRelease`-driven tests) and today's entire live path resolve `"daemon"` anyway. */
    private channelFor;
    /** The gate a channel-keyed retain registers through (G-iii registration-time routing). The
     *  channel MUST already be connected (`connectSource`; the daemon is constructor-attached) —
     *  loud by design: a typo'd or not-yet-connected sourceKey must throw at retain time, never
     *  silently register on the daemon and split the query's frames across channels. */
    private requireGate;
    /** The channel that owns `sourceQid` — {@link RemoteSub.channel}, the ONE source of truth for
     *  qid routing (G-iii). `undefined` when no sub owns the qid (a harness-delivered raw feed, or
     *  a just-released sub): such frames buffer on whatever gate they arrive at. */
    private channelOf;
    /** Record `(domain, mid)` as processed; `false` if it already was (a duplicate frame —
     *  ignore it). FIFO-capped per domain ({@link MAX_PROCESSED_OUTCOMES_PER_DOMAIN}). */
    private markOutcomeProcessed;
    /** One `mutationOutcome` frame from `domain`'s channel (H-v — the §3.3 handshake's client
     *  half). The frame arrives OUT-OF-BAND (see {@link attachGate}); the state machine:
     *
     *  1. `mid` never issued on `domain` ⇒ ignore (a confused/foreign frame must not invent work).
     *  2. `(domain, mid)` already processed ⇒ ignore — idempotence under duplicate frames (the
     *     original + a re-send's re-answer; a deopt for a mid whose entry ALREADY FLIPPED also
     *     lands here harmlessly on its second frame).
     *  3. `kind:"rejected"` ⇒ FINAL. Surface the reason through {@link rejectedHandler} (room-plane
     *     parity with the HTTP queue's callback) and STOP — the drop + snap-back is the EXISTING
     *     failed-mutation machinery: the room burnt the mid, its lmid release retires the entry
     *     per-domain and the reconcile rewinds the prediction, exactly the daemon path's
     *     processed-as-no-op rejection. No new drop path.
     *  4. `kind:"deopt"`, entry found (pending `(domain, mid)`) ⇒ FLIP IN PLACE: `domain` becomes
     *     `"daemon"`, a fresh daemon mid is dealt and the envelope ships NOW on the daemon channel
     *     ("deal-and-send-now" — the conforming §3.3 re-enqueue: there is no flush machinery for
     *     non-fold entries, so the design's "mid: null until the daemon flush" is satisfied
     *     momentarily inside this call). THE ENTRY'S `seq` IS KEPT — settled (§5.3, commit
     *     68141096): `seq` is the client-global REPLAY order; re-sequencing would move the entry's
     *     overlay position and change read-dependent SIBLINGS' replay base. Everything else stays
     *     (writes/reads/touched/touchedSources/writeSources — union-never-shrink), the prediction
     *     stays applied (the entry never leaves `pendingMutations`, so no rewind fires), and the
     *     router does NOT re-run nor does `drainOverlapping` (§3.3 re-enqueues, never re-derives;
     *     any open overlapping fold was invoked later and flushes later with a larger mid).
     *  5. `kind:"deopt"`, entry NOT found ⇒ the burnt-mid confirm won the race, or the frame is a
     *     replay re-answer for an entry a previous session retired (the replay gotcha): re-invoke
     *     the frame's echoed `name`/`args` as a FRESH invocation PINNED to `"daemon"` — an honest
     *     re-prediction on the current base, never a derived route ({@link invokeWith}). A frame
     *     without `name` (not self-contained) has nothing to re-invoke and is dropped; a re-invoke
     *     that THROWS (the base moved from under it) is surfaced through {@link rejectedHandler} —
     *     the mutation is dead with no stream left to confirm it.
     *
     *  A `"deopt"` bump joins the Q6 routing counters either way (`routing.reasons.deopt`) —
     *  derived-and-deopted routes are visible beside derived successes. */
    private handleMutationOutcome;
    /** §7.5 rule 3 (H-v): re-send `domain`'s unconfirmed pending envelopes with their ORIGINAL
     *  mids, in mid order, on the domain's own channel. Folds with `mid === null` are excluded —
     *  nothing was ever sent for them (the flush deals their mid). Envelopes are reconstructed from
     *  the pending entries exactly as `invoke` shipped them (`clientID`/`mid`/`name`/`args` —
     *  entries carry everything the wire needs). Idempotent under the domain's ledger: an APPLIED
     *  mid dedups silently and its lmid coverage retires the entry; a NON-APPLIED mid is re-answered
     *  from the shell's recorded-outcome map into {@link handleMutationOutcome}. Confirmed entries
     *  are already gone from `pendingMutations`, so no filter against the watermark is needed. */
    private resendPending;
    /** Drain every outstanding fold immediately (FOLDED-MUTATIONS-DESIGN §3): the explicit
     *  `app.flushFolds()` and the `beforeunload`/`close` hook. Creation (insertion) order. */
    flushFolds(): void;
    /** A `trackingTx` op collector that records only the ops over a tracked aggregate's child
     *  table (the others can't move any count). Applied to the overlay by the caller AFTER the
     *  mutator succeeds, so a throwing mutator (whose staged write is discarded) leaves no delta. */
    private opCollector;
    /** Push the optimistic per-group delta onto the `__agg` head rows (§4):
     *  `target = server_base ⊕ delta`. A head-only write to the (tracked) synthetic table, so it
     *  joins the optimistic layer and is rewound/rebuilt by the reconcile cycle like any
     *  prediction. `server_base` is read from `NormalizedSync` (the authoritative base) — NOT
     *  from head, which already carries the optimistic layer (a torn read). Works standalone (an
     *  ordinary delivery) and inside an open cycle (the write buffers into it). */
    private reconcileAggHead;
    /** The SERVER CHANNEL's state for a query (§7): `unknown` while not hydrated, else `complete`.
     *  A pending local mutation no longer moves it — see {@link pending}. */
    resultType(qid: QueryId): ResultType;
    onResultType(handler: (qid: QueryId, rt: ResultType) => void): void;
    __attachDevtoolsServerDeltas(observer: BackendDevObserver): () => void;
    /** Whether any pending mutation (folded or not) touches this query's tables — "is a prediction
     *  pending here?" (FOLDED-MUTATIONS-DESIGN §7.2). Orthogonal to {@link resultType}; this is the
     *  same `queryTables ∩ pending.touched` computation that used to be smuggled into `unknown`. */
    pending(qid: QueryId): boolean;
    /** Reactive pending axis (§7.2): fires when a query's pending-ness flips (invoke ↔ confirm), so a
     *  "saving…" affordance clears on its own when `lmid` catches up. */
    onPending(handler: (qid: QueryId, pending: boolean) => void): void;
    /** The coarse, table-level pending indicator set (§7.2): every table some pending mutation touched. */
    pendingTables(): Set<string>;
    /** A read-only snapshot of the optimistic loop for a devtools pane (DEBUG-TOOLS-BROWSER-DESIGN
     *  §4.1). Built fresh per call from state the backend already holds — no new instrumentation, no
     *  mutation. Only ever called by `@rindle/devtools` (imported in dev). */
    __inspect(): OptimisticInspect;
    /** Test-only per-domain ledger snapshot (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §7.1/§8.5).
     *  Kept separate from {@link __inspect} so the devtools `OptimisticInspect` mirror stays byte-for-
     *  byte identical (daemon-scalar-only). Exposes the per-domain `nextMid`/`watermark` maps plus each
     *  pending entry's confirming domain — the axes the §8.5 ledger-isolation assertion checks. */
    __inspectDomains(): {
        nextMid: Record<string, number>;
        watermark: Record<string, number>;
        /** Per connected CHANNEL (§5.1): its release watermark + buffered-frame depth — the axis the
         *  gate-isolation assertions read (one source's laggy cvMin must never move the other's). */
        gates: Record<string, {
            appliedCv: number;
            bufferedFrames: number;
        }>;
        /** Per connected/registered room: its wire-table → engine-table map (302 §2) and which local
         *  view qids are currently swapped onto it (302 §4). */
        roomTables: Record<string, Record<string, string>>;
        swappedViews: Record<number, string>;
        /** The §4 lifecycle plane's folded state (Slice I-iii introspection): the per-doc §4.2 fence
         *  value (`roomWatermarks`, I-v's ghost-drop input), the per-scope §4.1 occupancy map
         *  (`scopeSessions`: scope → client_id → expires_at, I-iv's doorbell input), and the live
         *  I-v ghosts (demoted room sources still awaiting their swap-back fence). */
        lifecycle: {
            roomWatermarks: Record<string, number>;
            scopeSessions: Record<string, Record<string, number>>;
            ghosts: Record<string, {
                doc: string;
                finalFlushSeq: number;
            }>;
        };
        pending: {
            mid: number | null;
            seq: number | null;
            name: string;
            domain: string;
        }[];
    };
    /** Recompute the pending axis for every query and fire `onPending` on transitions only. Called
     *  from the two points that move the pending set: invoke/invokeFolded (add) and the confirm-drop
     *  (remove) — exactly where `:359`/`:468` used to flip ResultType (§7.3). */
    private refreshPending;
    private onFrame;
    private emitServerDelta;
    private localQidsForSource;
    /** One gate's release (§5.1 release gate): compute the coherent delta from THIS gate's cv-buffer,
     *  then apply it against the gate's source/domain. Split into {@link computeRelease} (buffer →
     *  delta, lmid → watermark) and {@link applyRelease} (per-source confirm-drop + reconcile) —
     *  N independent gates all feed the ONE apply half; {@link __testRelease} drives it directly. */
    private onGateProgress;
    /** Compute one coherent release from ONE gate's cv-buffer (§5.1) — gate-scoped: its buffer, its
     *  cvMin timeline. Take every buffered frame at `cv ≤ cvMin`, in (cv, arrival) order, and fold
     *  it: the lmid system-query frame advances `watermark[gate.key]` (via {@link foldLmidOps} — the
     *  daemon stream folds "daemon", a room stream folds its own domain); data frames fold through
     *  this SOURCE's cross-query refcount into ONE net base delta — the §1.3 `D`. Returns that delta
     *  plus the set of local views this release JUST hydrated (so their reconcile batch phases as a
     *  `snapshot`). Mutates the gate's buffer/`appliedCv`, hydration, and the gate's domain
     *  watermark; the pending set and the reconcile are {@link applyRelease}'s job. */
    private computeRelease;
    /** Apply one released delta against `sourceKey`'s domain (§7.2 per-domain confirm-drop + the §1.3
     *  reconcile cycle). `watermarkUpdate`, when given, advances `watermark[sourceKey]` first — the
     *  hook a per-source lmid confirm rides on (the daemon path folds its watermark in
     *  {@link computeRelease} and passes `undefined`). Then: drop every pending entry its OWN domain's
     *  watermark now covers (a room confirm can never retire a daemon entry, and vice-versa — the §7.1
     *  ledger-collision fix), and run the reconcile cycle against `sourceKey` when the base delta or the
     *  pending set changed. `newlyHydrated` stamps the initial-hydration batch as a catch-up. */
    private applyRelease;
    /** Test-only per-source release seam (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §7.2/§8.5): drive
     *  {@link applyRelease} for `sourceKey` directly — an explicit `watermarkUpdate` (a simulated lmid
     *  confirm for that domain) and `deltas` (a coherent base delta), with no real gate. Lets a harness
     *  exercise a room-domain confirm before the real second lmid stream / per-source gate is wired
     *  (E-iii-b/c). The `__`-prefix marks it a test hook, alongside {@link __inspect}. */
    __testRelease(sourceKey: string, deltas: Mutation[], watermarkUpdate?: number): void;
    /** Swap every view of each just-hydrated ROOM sub onto the room's namespaced tables (302 §4.1):
     *  re-register the local engine query with the AST's room-owned table references renamed
     *  ({@link remapAstTables}); the Store folds the re-hello as an in-place reset, so the caller's
     *  view reference survives and subscribers see ONE transition. Runs at the applyRelease tail —
     *  the reconcile has already folded the sub's snapshot into the room tables, so the swapped
     *  view hydrates straight to the room state (swapping earlier would flash it empty). The
     *  ORIGINAL ast stays in {@link asts}; the swap-back ({@link dropGhost}) re-registers it.
     *
     *  This is the accepted-flash boundary (302 §4.1/§7.1): the room's copy may be behind the
     *  daemon rows the view showed a moment ago — accepted by decision, revisit on a real
     *  two-region deploy. */
    private processSwapIns;
    /** Fold `domain`'s lmid system query's released ops (lmid-as-data): the one row's
     *  `last_mutation_id` cell is this client's confirmed high-water mid in that domain — it advances
     *  `watermark[domain]` and, on a fresh session ahead of our issued mids, `nextMid[domain]`. The
     *  daemon stream folds `"daemon"`; a room stream folds its own `"room:doc:X"`; the daemon-carried
     *  §7.1 ledger rows fold through the same {@link foldConfirm} core (Slice I-iii). */
    private foldLmidOps;
    /** THE one confirm fold (§7.1/§7.2): advance `watermark[domain]` to `lmid` (monotone max) and,
     *  on a fresh session ahead of our issued mids, adopt `nextMid[domain]`. Shared verbatim by the
     *  per-channel lmid system query ({@link foldLmidOps}) and the daemon-carried room-ledger rows
     *  ({@link foldSystemFrames} — one core so the two paths cannot drift). */
    private foldConfirm;
    /** Fold one release's SYSTEM frames in a FIXED category order — the order is STRUCTURAL (one
     *  function, categories in sequence), because it is the client half of THE NAMED INVARIANT
     *  (§3.3's shipped note; documented above {@link handleMutationOutcome}): **never retire a
     *  room-domain entry off a daemon-carried lmid without outcome resolution.**
     *
     *    1. **outcome rows** (`_rindle_room_mutation_outcomes`) — each row for OUR clientID is
     *       synthesized into a {@link MutationOutcomeFrame} and routed through
     *       {@link handleMutationOutcome}, the SAME H-v state machine the room socket's frames use
     *       (one verdict path: frames and rows cannot drift). A deopt flips its pending entry to
     *       the daemon IN PLACE (keep-seq, deal-and-send-now); a rejection surfaces + stays for the
     *       ordinary burnt-mid retire; a duplicate (frame already seen, or the row re-delivered) is
     *       absorbed by the processed set — which doubles as the resolved-verdict memory across
     *       releases (per-domain FIFO, {@link MAX_PROCESSED_OUTCOMES_PER_DOMAIN}, mirroring the
     *       shell's recorded-outcome cap).
     *    2. **room-ledger rows** (`_rindle_room_client_mutations`) — the FIRST daemon-carried
     *       room-lmid path: OUR row's `last_mutation_id` folds into `watermark[room:<doc>]` via
     *       {@link foldConfirm}. Because step 1 ALREADY resolved every non-applied verdict this
     *       release carries (and earlier releases' verdicts were resolved at their own release),
     *       the confirm-drop that follows in {@link applyRelease} retires only entries whose
     *       outcome is resolution-by-absence — which I-ii's co-commit atomicity defines as APPLIED
     *       (a room flush co-commits the ledger row and every non-applied mid's outcome row in ONE
     *       daemon transaction, so a covering lmid without a row IS the applied verdict).
     *       Processing this category before step 1 is the violation, in two proven directions
     *       (each run break→fail→revert against `test/system_streams.test.ts`): (a) the ledger's
     *       fresh-session `nextMid` ADOPTION must not run before historical outcome rows are
     *       judged — adopted-first, a previous session's retained deopt row passes the
     *       "never-issued" guard and spuriously re-invokes a mutation that session already handled
     *       (a double-apply); (b) the RETIRE must not precede resolution — it does not BECAUSE the
     *       confirm-drop runs in {@link applyRelease}, strictly after this whole function. That
     *       deferral is load-bearing: an "optimization" retiring inline with the watermark fold
     *       retires a deopted entry as a silent success (the exact lost-write H-v exists to
     *       prevent) and mis-attributes a rejected row's reason.
     *    3. **watermark rows** (`_rindle_room_watermark`) — the §4.2 fence value, max-folded per
     *       doc ({@link roomWatermarks}); I-v's ghost-drop consumer, no reaction here.
     *    4. **scope-session rows** (`_rindle_scope_sessions`) — the §4.1 occupancy map
     *       ({@link scopeSessions}); I-iv's doorbell consumer, no reaction here.
     *
     *  Ordinary data ops fold AFTER all of these (the caller's main loop) — outcome/ledger state
     *  must be in place before {@link applyRelease}'s confirm-drop + reconcile consume the release.
     *  Every row is filtered against the retain's {@link SystemStreamSpec} scope/doc AND (for the
     *  client-keyed tables) our own `clientID` — defense in depth: the server predicate may have
     *  been minted doc-only (no `clientId` at lease time), so other clients' rows are expected and
     *  must be ignored, and a row for a doc this retain was not minted for is never folded.
     *
     *  Returns the scopes category 4 touched (snapshot or ops) — the I-iv doorbell events' input;
     *  `null` when none (every non-lifecycle release). The events themselves fire from
     *  `onGateProgress` AFTER the release applies, never from inside the fold. */
    private foldSystemFrames;
    /** The I-iv occupancy count — THE one rule (§4.1/D7): unexpired (`expires_at >` the fold
     *  clock's now) sessions under `scope` from OTHER clientIDs. Shared by the doorbell events
     *  ({@link onGateProgress}) and the client's registration-time check (a doorbell that folded
     *  BEFORE a candidate registered must still be able to trigger it) so the two can never
     *  disagree. Own-clientID rows never count — a solo client cannot ring its own bell — and
     *  expiry is judged on the injectable {@link FoldClock} (deterministic in a virtual-clock
     *  harness, the folded-oracle discipline). */
    otherScopeSessions(scope: string): number;
    /** Register the I-iv doorbell event sink — see {@link ScopeSessionsEvent}. One handler (a later
     *  registration replaces it, the {@link onLocalWrite} convention); client.ts is the consumer. */
    onScopeSessions(handler: (event: ScopeSessionsEvent) => void): void;
    /** One §1.3 reconcile cycle: rewind the optimistic layer and fold the coherent SERVER
     *  delta into BOTH head AND the `sync` baseline (`serverBatchBegin`), re-invoke every
     *  still-pending mutator to re-stage the optimistic layer (the rewind un-applied it), then
     *  deliver the coalesced result (`serverBatchEnd`). This is the engine's only sync-moving
     *  boundary — `onProgress` releases and `unregisterQuery`'s GC both go through here so head
     *  and sync never diverge (the §1.2 invariant; CRIT#2). */
    private runReconcileCycle;
    /** ONE channel's authority restarted (a new boot id): it lost all materialization + `cv` state
     *  and its `cv` sequence reset, so previously-released `cv`s no longer bound the new stream. The
     *  source has already re-subscribed every query (reconnect → resync); drop THIS gate's buffer
     *  and `cv` watermark so the fresh, low-`cv` snapshots are RELEASED instead of dropped as stale
     *  (`onFrame`/`computeRelease` gate on `appliedCv`). The OTHER gates are untouched — an
     *  authority restart is per-channel (§5.1). Pending optimistic mutations stay put — they
     *  re-apply on the next reconcile, and the channel's lmid system query's fresh snapshot restores
     *  its domain's confirmation watermark. */
    private resetGate;
    /** The §8.5 escape: ONE gate's buffer outgrew its cap (a pinned `cvMin` under churn on that
     *  channel). Drop everything it buffered and re-register every query on that source — the fresh
     *  snapshots arrive as ordinary frames and the next release re-hydrates via the footprint diff
     *  (the §5.3 path); still-pending optimism re-applies in that cycle. The other gates' buffers
     *  and subscriptions are untouched. */
    private overflow;
    private setResultType;
    /** Recompute a query's server-channel state from hydration alone (§7): a pending mutation no
     *  longer affects it. Used when a remote sub attaches to or hydrates a local view. */
    private recomputeResultType;
    /** A remote sub's first snapshot landed: mark it (and every local view it feeds) hydrated, then
     *  lift those views out of `unknown` (loading). A ROOM sub's hydration additionally queues the
     *  302 §4.1 swap-in — performed at the applyRelease TAIL ({@link processSwapIns}), once the
     *  reconcile has folded this snapshot into the room tables. Idempotent — a re-hydrate snapshot
     *  re-marks harmlessly; a source qid with no sub (the lmid system query) is a no-op. */
    private markSubHydrated;
    /** `channel` (G-iii registration-time routing): the gate the sub registers on — the qid's
     *  ownership is fixed HERE, at retain time (no lazy claim; `onFrame` only asserts it). Default
     *  `"daemon"`, so every channel-less caller is byte-identical to before. */
    private retainRemote;
    private releaseRemote;
    private addServerDependencyTables;
}

roomEngineTable

/** The namespaced ENGINE table backing wire `table` for room `sourceKey` (302 §2: `room_deck` ≠
 *  `deck` — one authority per table). `@` appears in no schema table name — ENFORCED by
 *  `createSchema`/`extendSchema`'s addTableMeta ban (packages/client/src/schema.ts), so the name
 *  cannot collide with a real table. */
export declare function roomEngineTable(table: string, sourceKey: string): string;

remapAstTables

/** Rename every TABLE reference in a query AST through `map` (302 §2 point 3 — the room-homed
 *  view's rewrite): the root `table`, every `related` subquery, every `correlatedSubquery`
 *  (EXISTS) condition — walking the KNOWN wire-AST shape, never a blind key scan: `start.row` is
 *  keyed by COLUMN name (a schema column literally named `table` must keep its bound value), and
 *  the same goes for any future column-keyed record. Tables absent from the map keep their name —
 *  that is the client-side join across kinds (a room table joined to daemon-owned context,
 *  201-style). Structural clone; the input AST is never mutated. */
export declare function remapAstTables(ast: Ast, map: ReadonlyMap<string, string>): Ast;