@rindle/wasm 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,29 @@
1
1
  import { Store } from "@rindle/client";
2
- import type { Ast, Backend, ChangeEvent, ColsMap, Mutation, QueryId, Schema } from "@rindle/client";
2
+ import type { Ast, Backend, ChangeEvent, ColsMap, Condition, Mutation, QueryId, Schema } from "@rindle/client";
3
3
  export * from "@rindle/client";
4
+ /** A serializable writable-scope descriptor: how the merge decides, per row, whether an attached
5
+ * room may write it (the room copy then wins, RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5.2).
6
+ * `"all"` = the room owns every row it holds; `"none"` = a context table the daemon stays
7
+ * authoritative for; `"colEq"` = writable iff `row[col] === value` (harness stand-in). The REAL
8
+ * shape (Slice G) is `"predicate"`: `where` is the SAME wire {@link Condition} JSON a query AST
9
+ * carries, restricted to what evaluates against a single row — `simple` column-vs-literal leaves
10
+ * under `and`/`or`; a `correlatedSubquery` (or any other non-row-local node) is rejected loudly at
11
+ * `addRoomSource` time. It compiles Rust-side through the engine's OWN filter lowering
12
+ * (`create_predicate`), so writable evaluation can never disagree with query-filter semantics.
13
+ * NOTE: room join-key columns (Slice H) deliberately do NOT cross this boundary — they stay
14
+ * TS-side. */
15
+ export type WritableDescriptor = {
16
+ kind: "all";
17
+ } | {
18
+ kind: "none";
19
+ } | {
20
+ kind: "colEq";
21
+ col: number;
22
+ value: unknown;
23
+ } | {
24
+ kind: "predicate";
25
+ where: Condition;
26
+ };
4
27
  /** A raw staged write transaction over the wasm engine — the surface an optimistic client
5
28
  * mutator runs against (`get` reads the live state under this txn's staged overlay, so a
6
29
  * read-dependent mutator sees its own writes; OPTIMISTIC-WRITES-DESIGN.md §4.1). */
@@ -19,6 +42,17 @@ export interface WasmWriteTxn {
19
42
  queryId: number;
20
43
  events: unknown[];
21
44
  }>;
45
+ /** Like {@link commit}, additionally returning the per-change touched-source key list in staged
46
+ * order (`sources`, e.g. `"daemon"` / `"room:doc:1"`) — the provenance each staged write routed
47
+ * through, captured pre-apply (Slice E-iii-b, design Q1). `batches` is exactly {@link commit}'s
48
+ * return; the source list is additive. */
49
+ commitTracked(): {
50
+ batches: Array<{
51
+ queryId: number;
52
+ events: unknown[];
53
+ }>;
54
+ sources: string[];
55
+ };
22
56
  rollback(): void;
23
57
  }
24
58
  /** One base-table row op of a coherent server delta (the §1.3 `D`), bare cells. */
@@ -98,18 +132,80 @@ export declare class WasmBackend<S extends ColsMap> implements Backend {
98
132
  * `onCommitted` runs after `tx.commit()` returns but before `dispatch` delivers to
99
133
  * subscribers: a throw from `f` (pre-commit — engine untouched) skips it; a subscriber
100
134
  * throw re-raised by `dispatch` happens after it. It is the only point where "the commit
101
- * is applied" is knowable to a caller that must not confuse the two failure modes. */
102
- writeWith(f: (tx: WasmWriteTxn) => void, onCommitted?: () => void): void;
103
- /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds
104
- * (optimistic layer un-applied, delta folded in, `sync` re-forked) and starts
105
- * buffering. Re-invoke the still-pending mutators via {@link writeWith}, then call
106
- * {@link serverBatchEnd}. On error the optimistic state is poisoned discard the
107
- * backend and re-hydrate. */
108
- serverBatchBegin(deltas: ServerDeltaOp[]): void;
135
+ * is applied" is knowable to a caller that must not confuse the two failure modes.
136
+ *
137
+ * Returns the per-change **touched-source key list** in staged-write order (`"daemon"` /
138
+ * `"room:doc:X"`, Slice E-iii-b design Q1) additive; every current caller (`mutate`,
139
+ * `writeLocal`, `@rindle/optimistic`) ignores it. The per-source reconcile (Slice E-iii-c) zips
140
+ * it with the mutation's write-set for the per-pk hold-back source. */
141
+ writeWith(f: (tx: WasmWriteTxn) => void, onCommitted?: () => void): string[];
142
+ /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds the
143
+ * per-source rebase (optimistic layer un-applied, delta folded in, the source's baseline
144
+ * re-forked) and starts buffering. `sourceKey` names the authority the delta confirms —
145
+ * `"daemon"` today (single-domain); a `room:doc:X` key once the room feed lands. Re-invoke the
146
+ * still-pending mutators via {@link writeWith}, then call {@link serverBatchEnd}. On error the
147
+ * rebase state is poisoned — discard the backend and re-hydrate. */
148
+ serverBatchBegin(sourceKey: string, deltas: ServerDeltaOp[]): void;
109
149
  /** Close the cycle: the whole buffered stream (rewind + re-invocations) coalesces to
110
150
  * the minimal net per query (§3 — a confirmed-correct prediction delivers nothing)
111
151
  * and dispatches as ONE batch per affected query on the ordinary event stream. */
112
152
  serverBatchEnd(): void;
153
+ /** Attach a room source to `table`, compiling `writable` into the merge's per-row writable scope.
154
+ * Promotes the table Collapsed → Merged on the first room (§5.2); the room seeds empty, so no
155
+ * visible blip. Throws on an untracked (local-only) table or a duplicate room. */
156
+ addRoomSource(table: string, roomKey: string, writable: WritableDescriptor): void;
157
+ /** Detach a room source, emitting the compensating visible deltas on the ordinary event stream. */
158
+ removeRoomSource(table: string, roomKey: string): void;
159
+ /** Park an echoed `row` inertly on `sourceKey`'s slice of `table` (the confirm→echo hold-back
160
+ * window, design §7.3): keeps the row visible so the next per-source rewind of `sourceKey` does
161
+ * not flash-revert a confirmed-elsewhere write before its data echo lands. `sourceKey` is
162
+ * `"daemon"` or a `"room:doc:X"` key. */
163
+ holdBack(table: string, sourceKey: string, row: unknown[]): void;
164
+ /** Park `oldRow`'s pk as ABSENT on `sourceKey`'s slice of `table` — the §7.3 tombstone, the
165
+ * remove-sibling of {@link holdBack} (Slice G-ii): a confirmed cross-slice REMOVE whose delete
166
+ * echo has not landed on `sourceKey` must not resurrect through that source's next rewind. The
167
+ * overlay masks the pk (overlay-first) until the source's re-forked baseline lacks it (the
168
+ * delete echoed), then drops. `oldRow` is the full-width PRE-IMAGE row (same positional
169
+ * bare-cell marshaling as {@link holdBack}); it carries the pk and, later (Slice H), the cells
170
+ * writable-scope predicates evaluate on. */
171
+ holdBackAbsent(table: string, sourceKey: string, oldRow: unknown[]): void;
172
+ /** Drop one parked §7.3 hold-back at `probe`'s pk on `sourceKey`'s slice and reconcile the
173
+ * pk's visible winner — the §301 FENCE-driven drop (`301-ECHO-FENCE-DESIGN.md` §2.2), fired
174
+ * by the optimistic backend's pin registry once `sourceKey`'s stream has provably delivered
175
+ * through the pinned write's commit. Unlike the park-time seams (whose install is a no-op by
176
+ * construction) a drop is exactly the moment a masked authoritative row becomes visible, so
177
+ * the engine's compensating deltas are DELIVERED here: dispatched on the ordinary event
178
+ * stream (or folded into an open server-batch cycle's one delivery). No-op when nothing is
179
+ * parked at the pk — the rewind's state-match fallback may have raced it. */
180
+ dropHeldBack(table: string, sourceKey: string, probe: unknown[]): void;
181
+ /** The parked hold-back at `probe`'s pk on `sourceKey`'s slice, or `undefined` (§301
182
+ * introspection): `absent` names the tombstone variant; `baseline` is the source's CURRENT
183
+ * confirmed-baseline row at the pk (`undefined` when the baseline lacks it) — the pin
184
+ * registry's lazy-prune probe and the §2.5 stuck-pin tripwire input. Non-throwing on an
185
+ * unknown source (a removed room), so pruning after a whole-source removal just works. */
186
+ heldBackState(table: string, sourceKey: string, probe: unknown[]): {
187
+ absent: boolean;
188
+ baseline: unknown[] | undefined;
189
+ } | undefined;
190
+ /** Freeze a physical source — the D4 downgrade-ghost stub (wins-if-present in the merge). */
191
+ freezeSource(table: string, sourceKey: string): void;
192
+ /** The source currently serving `row`'s pk on `table` — `"daemon"`, a room key, or `undefined`
193
+ * when no source holds the pk (Slice H-i routing probe #1, design §3.2 #3). `row` is a
194
+ * full-width positional row (the pk derives from it — same marshaling as {@link holdBack});
195
+ * an unknown table throws loudly. Reads the LIVE visible state, overlay-first (§7.3): an open
196
+ * write txn's staged-but-uncommitted rows are NOT consulted (they have not routed yet), which
197
+ * is exactly the pre-commit view the §3 routing proof decides on — a present read is proven
198
+ * room-covered iff this reports the occupied room. */
199
+ provenanceOf(table: string, row: unknown[]): string | undefined;
200
+ /** Evaluate the REGISTERED writable scope of `table`'s room source `roomKey` against a
201
+ * caller-supplied full-width row (Slice H-i routing probe #2, design §3.1): a prediction-run
202
+ * write is proven room-covered iff this is `true` on the written row. Runs the SAME compiled
203
+ * predicate the merge's winner tiering uses (the {@link WritableDescriptor} registered at
204
+ * {@link addRoomSource}), so routing can never disagree with the engine; `all` ⇒ always true,
205
+ * `none` ⇒ always false, and a frozen source still answers (the predicate is static). Throws
206
+ * loudly on an unknown table, a room key not attached to the table, a wrong-width row, or
207
+ * `"daemon"` (which has no writable scope). */
208
+ writableMatches(table: string, roomKey: string, row: unknown[]): boolean;
113
209
  }
114
210
  /** Convenience: init the wasm + return a ready local {@link Store}. */
115
211
  export declare function createWasmStore<S extends ColsMap>(schema: Schema<S>): Promise<Store<S>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAmB,KAAK,EAAa,MAAM,gBAAgB,CAAC;AACnE,OAAO,KAAK,EACV,GAAG,EACH,OAAO,EACP,WAAW,EACX,OAAO,EACP,SAAS,EACT,QAAQ,EACR,OAAO,EACP,MAAM,EACP,MAAM,gBAAgB,CAAC;AAExB,cAAc,gBAAgB,CAAC;AAE/B;;;;;;;;;;eAUe;AACf,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,GACf;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,CAAC;AA4B5C;;qFAEqF;AACrF,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,GAAG,SAAS,CAAC;IACzD;;;;6DAIyD;IACzD,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,EAAE,CAAC;IAC3B,MAAM,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD;;;+CAG2C;IAC3C,aAAa,IAAI;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,OAAO,EAAE,CAAA;SAAE,CAAC,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC/F,QAAQ,IAAI,IAAI,CAAC;CAClB;AAED,mFAAmF;AACnF,MAAM,MAAM,aAAa,GACrB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE,OAAO,EAAE,CAAA;CAAE,GAC9C;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,OAAO,EAAE,CAAA;CAAE,GACjD;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,OAAO,EAAE,CAAC;IAAC,GAAG,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AAIpE;;sGAEsG;AACtG,wBAAgB,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9D;AAMD,qBAAa,WAAW,CAAC,CAAC,SAAS,OAAO,CAAE,YAAW,OAAO;IAC5D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,OAAO,CAAqD;IAKpE,OAAO,CAAC,eAAe,CAA8C;IACrE;gGAC4F;IAC5F,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAM1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkB;IAChD,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAU7B;;;;qGAIiG;IACjG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI;IAIpF;;;;uEAImE;IACnE,UAAU,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAejE;;;iGAG6F;IAC7F,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAInC,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI;IAM3C,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAInC,MAAM,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5C,OAAO,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAI/D;;gDAE4C;IAC5C,gBAAgB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,KAAK,IAAI,GAAG,IAAI;IAIjE;;;;;;;;;;oGAUgG;IAChG,OAAO,CAAC,QAAQ;IA4ChB;;;;;;;;;;;;;4EAawE;IACxE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,IAAI,GAAG,MAAM,EAAE;IAS5E;;;;;yEAKqE;IACrE,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,IAAI;IAIlE;;uFAEmF;IACnF,cAAc,IAAI,IAAI;IAYtB;;uFAEmF;IACnF,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAIjF,mGAAmG;IACnG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAItD;;;8CAG0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI;IAIhE;;;;;;iDAM6C;IAC7C,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI;IAIzE;;;;;;;kFAO8E;IAC9E,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI;IAItE;;;;+FAI2F;IAC3F,aAAa,CACX,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,EAAE,GACf;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS;IAInE,6FAA6F;IAC7F,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIpD;;;;;;2DAMuD;IACvD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,SAAS;IAI/D;;;;;;;oDAOgD;IAChD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO;CAGzE;AAED,uEAAuE;AACvE,wBAAsB,eAAe,CAAC,CAAC,SAAS,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAG7F"}
package/dist/index.js CHANGED
@@ -191,21 +191,28 @@ export class WasmBackend {
191
191
  * `onCommitted` runs after `tx.commit()` returns but before `dispatch` delivers to
192
192
  * subscribers: a throw from `f` (pre-commit — engine untouched) skips it; a subscriber
193
193
  * throw re-raised by `dispatch` happens after it. It is the only point where "the commit
194
- * is applied" is knowable to a caller that must not confuse the two failure modes. */
194
+ * is applied" is knowable to a caller that must not confuse the two failure modes.
195
+ *
196
+ * Returns the per-change **touched-source key list** in staged-write order (`"daemon"` /
197
+ * `"room:doc:X"`, Slice E-iii-b design Q1) — additive; every current caller (`mutate`,
198
+ * `writeLocal`, `@rindle/optimistic`) ignores it. The per-source reconcile (Slice E-iii-c) zips
199
+ * it with the mutation's write-set for the per-pk hold-back source. */
195
200
  writeWith(f, onCommitted) {
196
201
  const tx = this.db.write();
197
202
  f(tx);
198
- const batches = tx.commit();
203
+ const { batches, sources } = tx.commitTracked();
199
204
  onCommitted?.();
200
205
  this.dispatch(batches);
206
+ return sources;
201
207
  }
202
- /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds
203
- * (optimistic layer un-applied, delta folded in, `sync` re-forked) and starts
204
- * buffering. Re-invoke the still-pending mutators via {@link writeWith}, then call
205
- * {@link serverBatchEnd}. On error the optimistic state is poisoned discard the
206
- * backend and re-hydrate. */
207
- serverBatchBegin(deltas) {
208
- this.db.serverBatchBegin(deltas);
208
+ /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds the
209
+ * per-source rebase (optimistic layer un-applied, delta folded in, the source's baseline
210
+ * re-forked) and starts buffering. `sourceKey` names the authority the delta confirms
211
+ * `"daemon"` today (single-domain); a `room:doc:X` key once the room feed lands. Re-invoke the
212
+ * still-pending mutators via {@link writeWith}, then call {@link serverBatchEnd}. On error the
213
+ * rebase state is poisoned — discard the backend and re-hydrate. */
214
+ serverBatchBegin(sourceKey, deltas) {
215
+ this.db.serverBatchBegin(sourceKey, deltas);
209
216
  }
210
217
  /** Close the cycle: the whole buffered stream (rewind + re-invocations) coalesces to
211
218
  * the minimal net per query (§3 — a confirmed-correct prediction delivers nothing)
@@ -213,6 +220,84 @@ export class WasmBackend {
213
220
  serverBatchEnd() {
214
221
  this.dispatch(this.db.serverBatchEnd());
215
222
  }
223
+ // --- multi-source seams (Slice E-iii-b harness → Slice G-ii ABI) ------------------
224
+ //
225
+ // Thin pass-throughs to the engine's `Merge` (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5).
226
+ // `addRoomSource`'s {@link WritableDescriptor} now carries the real row-local `predicate` arm
227
+ // (a wire-Condition `where`, compiled Rust-side through the engine's own filter lowering)
228
+ // alongside the narrow `all`/`none`/`colEq` harness shapes. Single-domain behavior is untouched
229
+ // unless `addRoomSource` is called (a table stays Collapsed = today's daemon-only path).
230
+ /** Attach a room source to `table`, compiling `writable` into the merge's per-row writable scope.
231
+ * Promotes the table Collapsed → Merged on the first room (§5.2); the room seeds empty, so no
232
+ * visible blip. Throws on an untracked (local-only) table or a duplicate room. */
233
+ addRoomSource(table, roomKey, writable) {
234
+ this.db.addRoomSource(table, roomKey, writable);
235
+ }
236
+ /** Detach a room source, emitting the compensating visible deltas on the ordinary event stream. */
237
+ removeRoomSource(table, roomKey) {
238
+ this.db.removeRoomSource(table, roomKey);
239
+ }
240
+ /** Park an echoed `row` inertly on `sourceKey`'s slice of `table` (the confirm→echo hold-back
241
+ * window, design §7.3): keeps the row visible so the next per-source rewind of `sourceKey` does
242
+ * not flash-revert a confirmed-elsewhere write before its data echo lands. `sourceKey` is
243
+ * `"daemon"` or a `"room:doc:X"` key. */
244
+ holdBack(table, sourceKey, row) {
245
+ this.db.holdBack(table, sourceKey, row);
246
+ }
247
+ /** Park `oldRow`'s pk as ABSENT on `sourceKey`'s slice of `table` — the §7.3 tombstone, the
248
+ * remove-sibling of {@link holdBack} (Slice G-ii): a confirmed cross-slice REMOVE whose delete
249
+ * echo has not landed on `sourceKey` must not resurrect through that source's next rewind. The
250
+ * overlay masks the pk (overlay-first) until the source's re-forked baseline lacks it (the
251
+ * delete echoed), then drops. `oldRow` is the full-width PRE-IMAGE row (same positional
252
+ * bare-cell marshaling as {@link holdBack}); it carries the pk and, later (Slice H), the cells
253
+ * writable-scope predicates evaluate on. */
254
+ holdBackAbsent(table, sourceKey, oldRow) {
255
+ this.db.holdBackAbsent(table, sourceKey, oldRow);
256
+ }
257
+ /** Drop one parked §7.3 hold-back at `probe`'s pk on `sourceKey`'s slice and reconcile the
258
+ * pk's visible winner — the §301 FENCE-driven drop (`301-ECHO-FENCE-DESIGN.md` §2.2), fired
259
+ * by the optimistic backend's pin registry once `sourceKey`'s stream has provably delivered
260
+ * through the pinned write's commit. Unlike the park-time seams (whose install is a no-op by
261
+ * construction) a drop is exactly the moment a masked authoritative row becomes visible, so
262
+ * the engine's compensating deltas are DELIVERED here: dispatched on the ordinary event
263
+ * stream (or folded into an open server-batch cycle's one delivery). No-op when nothing is
264
+ * parked at the pk — the rewind's state-match fallback may have raced it. */
265
+ dropHeldBack(table, sourceKey, probe) {
266
+ this.dispatch(this.db.dropHeldBack(table, sourceKey, probe));
267
+ }
268
+ /** The parked hold-back at `probe`'s pk on `sourceKey`'s slice, or `undefined` (§301
269
+ * introspection): `absent` names the tombstone variant; `baseline` is the source's CURRENT
270
+ * confirmed-baseline row at the pk (`undefined` when the baseline lacks it) — the pin
271
+ * registry's lazy-prune probe and the §2.5 stuck-pin tripwire input. Non-throwing on an
272
+ * unknown source (a removed room), so pruning after a whole-source removal just works. */
273
+ heldBackState(table, sourceKey, probe) {
274
+ return this.db.heldBackState(table, sourceKey, probe);
275
+ }
276
+ /** Freeze a physical source — the D4 downgrade-ghost stub (wins-if-present in the merge). */
277
+ freezeSource(table, sourceKey) {
278
+ this.db.freezeSource(table, sourceKey);
279
+ }
280
+ /** The source currently serving `row`'s pk on `table` — `"daemon"`, a room key, or `undefined`
281
+ * when no source holds the pk (Slice H-i routing probe #1, design §3.2 #3). `row` is a
282
+ * full-width positional row (the pk derives from it — same marshaling as {@link holdBack});
283
+ * an unknown table throws loudly. Reads the LIVE visible state, overlay-first (§7.3): an open
284
+ * write txn's staged-but-uncommitted rows are NOT consulted (they have not routed yet), which
285
+ * is exactly the pre-commit view the §3 routing proof decides on — a present read is proven
286
+ * room-covered iff this reports the occupied room. */
287
+ provenanceOf(table, row) {
288
+ return this.db.provenanceOf(table, row);
289
+ }
290
+ /** Evaluate the REGISTERED writable scope of `table`'s room source `roomKey` against a
291
+ * caller-supplied full-width row (Slice H-i routing probe #2, design §3.1): a prediction-run
292
+ * write is proven room-covered iff this is `true` on the written row. Runs the SAME compiled
293
+ * predicate the merge's winner tiering uses (the {@link WritableDescriptor} registered at
294
+ * {@link addRoomSource}), so routing can never disagree with the engine; `all` ⇒ always true,
295
+ * `none` ⇒ always false, and a frozen source still answers (the predicate is static). Throws
296
+ * loudly on an unknown table, a room key not attached to the table, a wrong-width row, or
297
+ * `"daemon"` (which has no writable scope). */
298
+ writableMatches(table, roomKey, row) {
299
+ return this.db.writableMatches(table, roomKey, row);
300
+ }
216
301
  }
217
302
  /** Convenience: init the wasm + return a ready local {@link Store}. */
218
303
  export async function createWasmStore(schema) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rindle/wasm",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,7 +29,7 @@
29
29
  "README.md"
30
30
  ],
31
31
  "dependencies": {
32
- "@rindle/client": "0.4.4"
32
+ "@rindle/client": "0.5.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/node": "^22.10.0",
@@ -39,7 +39,7 @@
39
39
  "build": "tsc -p tsconfig.build.json",
40
40
  "build:wasm": "./build.sh",
41
41
  "typecheck": "tsc --noEmit",
42
- "test": "node test/bundle-size.mjs && node --conditions=@rindle/source test/e2e.mjs && node --conditions=@rindle/source test/one-start.e2e.mjs && node --conditions=@rindle/source test/repro-high.e2e.mjs && node --conditions=@rindle/source test/partial-write.e2e.mjs",
42
+ "test": "node test/bundle-size.mjs && node --conditions=@rindle/source test/e2e.mjs && node --conditions=@rindle/source test/one-start.e2e.mjs && node --conditions=@rindle/source test/repro-high.e2e.mjs && node --conditions=@rindle/source test/partial-write.e2e.mjs && node --conditions=@rindle/source test/room-source.e2e.mjs && node --conditions=@rindle/source test/predicate-agreement.e2e.mjs",
43
43
  "test:browser": "node test/browser-smoke.mjs"
44
44
  }
45
45
  }
package/pkg/rindle.d.ts CHANGED
@@ -7,13 +7,69 @@
7
7
  export class Db {
8
8
  free(): void;
9
9
  [Symbol.dispose](): void;
10
+ /**
11
+ * Attach a room source to `table`, compiling `writable` into the merge's per-row
12
+ * [`WritableScope`] ([`parse_writable_scope`]): `{ kind: "all" }` = the room may write every row
13
+ * (room wins); `{ kind: "none" }` = a context table (daemon stays authoritative);
14
+ * `{ kind: "colEq", col, value }` = writable iff `row[col] == value` (harness stand-in);
15
+ * `{ kind: "predicate", where }` = the row-local wire-`Condition` predicate (Slice G-ii),
16
+ * lowered by the engine's own [`create_predicate`] against `table`'s schema.
17
+ */
18
+ addRoomSource(table: string, room_key: string, writable: any): void;
10
19
  /**
11
20
  * Tear down a registered query: drop its sink + reclaim its pipeline (disconnect from
12
21
  * the shared sources, free its slots — [`Graph::destroy_pipeline`]). Other queries
13
22
  * and the shared sources are untouched. A no-op for an unknown id.
14
23
  */
15
24
  destroyQuery(query_id: number): void;
25
+ /**
26
+ * Drop one parked hold-back at `probe`'s pk ([`Db::drop_held_back`] — the §301 fence-driven
27
+ * drop) and **deliver** the compensating deltas: returns the same
28
+ * `[{ queryId, events: FlatChange[] }]` shape as `WriteTxn.commit` (empty inside an open
29
+ * server-batch cycle, where the events join the cycle's one delivery instead) — unlike the
30
+ * park-time seams, whose install reconcile is a no-op by construction, a fence drop is
31
+ * exactly the moment a masked authoritative row becomes visible, so it must reach the
32
+ * views. `probe` is a full-width positional row (the pk derives from it — the same
33
+ * marshalling as [`holdBack`](Self::hold_back_js)).
34
+ */
35
+ dropHeldBack(table: string, source_key: string, probe: any): any;
36
+ /**
37
+ * Freeze a physical source — the D4 downgrade-ghost stub ([`Db::freeze_source`]).
38
+ */
39
+ freezeSource(table: string, source_key: string): void;
40
+ /**
41
+ * The parked hold-back at `probe`'s pk on `source_key`'s slice, as
42
+ * `{ absent: boolean, baseline: row | undefined } | undefined`
43
+ * ([`Db::held_back_state`], §301): `undefined` when nothing is parked — including an
44
+ * unknown source (non-throwing, so the client registry prunes lazily after a state-match
45
+ * drop or a room removal); `baseline` is the source's current confirmed-baseline row at
46
+ * the pk (the §2.5 stuck-pin tripwire input). An unknown TABLE still throws (the row
47
+ * cannot even be marshalled).
48
+ */
49
+ heldBackState(table: string, source_key: string, probe: any): any;
50
+ /**
51
+ * Park an echoed `row` inertly on `source_key`'s slice of `table` ([`Db::hold_back`]).
52
+ */
53
+ holdBack(table: string, source_key: string, row: any): void;
54
+ /**
55
+ * Park `old_row`'s pk as **absent** on `source_key`'s slice of `table`
56
+ * ([`Db::hold_back_absent`]) — the §7.3 tombstone for a confirmed cross-slice REMOVE.
57
+ * `old_row` is the full pre-image row, marshalled exactly like [`holdBack`](Self::hold_back_js)'s
58
+ * `row` (a full-width positional array of bare cells).
59
+ */
60
+ holdBackAbsent(table: string, source_key: string, old_row: any): void;
16
61
  constructor();
62
+ /**
63
+ * The winning source currently serving `row`'s pk on `table`, as its wasm-boundary key
64
+ * string (`"daemon"` / the room key, [`source_key_to_string`]), or `undefined` when no
65
+ * source holds the pk ([`Db::provenance`], §3.2 #3 — Slice H-i routing probe #1). `row` is
66
+ * a full-width positional row (the pk derives from it — the same marshalling as
67
+ * [`holdBack`](Self::hold_back_js)); an unknown table throws loudly. Reads the LIVE visible
68
+ * state, **overlay-first** (§7.3): an open [`WriteTxn`]'s staged-but-uncommitted writes are
69
+ * not consulted (they have not routed yet), which is exactly the pre-commit view the §3
70
+ * routing proof decides on.
71
+ */
72
+ provenanceOf(table: string, row: any): any;
17
73
  /**
18
74
  * Register a live query from a Zero-wire AST under the caller's opaque `query_id`.
19
75
  * Lowers it into the shared graph, sinks it in a change-sink, and hydrates. Returns
@@ -38,6 +94,10 @@ export class Db {
38
94
  * (`createSchema`, N1) has a loud backstop instead of a silent one.
39
95
  */
40
96
  registerTable(table: string, schema: any, local: boolean): void;
97
+ /**
98
+ * Detach a room source, emitting the compensating visible deltas ([`Db::remove_room_source`]).
99
+ */
100
+ removeRoomSource(table: string, room_key: string): void;
41
101
  /**
42
102
  * Open a §1.3 reconcile cycle against the coherent server delta `deltas` —
43
103
  * an array of base-table row ops `{ table, type: "add"|"remove"|"edit", row,
@@ -49,11 +109,15 @@ export class Db {
49
109
  * events buffer into the cycle), then calls
50
110
  * [`server_batch_end`](Db::server_batch_end) for the one coalesced delivery.
51
111
  *
112
+ * `source_key` names the authority these `deltas` confirm — `"daemon"` at the wasm boundary
113
+ * today (single-domain); a `room:doc:X` string once the room feed lands (Slice E-iii/G). It
114
+ * selects which physical source [`Merge::rewind`] folds the delta into.
115
+ *
52
116
  * Errors if a cycle is already open, on an unknown table, or on a push failure
53
- * — after which the optimistic state is poisoned (a partial rewind may have
117
+ * — after which the per-source rebase state is poisoned (a partial rewind may have
54
118
  * applied): the only safe recovery is to discard this `Db` and re-hydrate.
55
119
  */
56
- serverBatchBegin(deltas: any): void;
120
+ serverBatchBegin(source_key: string, deltas: any): void;
57
121
  /**
58
122
  * Close the open cycle: deliver the whole buffered event stream (rewind +
59
123
  * re-invocations) per query, **in order**, as `[{ queryId, events: FlatChange[] }]`
@@ -81,6 +145,17 @@ export class Db {
81
145
  * holds).
82
146
  */
83
147
  unregisterTable(table: string): void;
148
+ /**
149
+ * Evaluate the REGISTERED writable scope of `table`'s room source `room_key` against a
150
+ * caller-supplied full-width `row` ([`Db::writable_matches`] — Slice H-i routing probe #2):
151
+ * the §3 proof's write-set rule, run through the SAME compiled predicate the merge's winner
152
+ * tiering uses (the G-ii descriptor `addRoomSource` registered), so routing can never
153
+ * disagree with the engine. `{kind:"all"}` ⇒ always `true`; `{kind:"none"}` ⇒ always
154
+ * `false`; a frozen source still answers (the predicate is static). Loud errors: unknown
155
+ * table, a room key not attached to the table, a wrong-width row, and `"daemon"` (reserved
156
+ * by [`parse_source_key`]; the daemon has no writable scope).
157
+ */
158
+ writableMatches(table: string, room_key: string, row: any): boolean;
84
159
  /**
85
160
  * Open a write transaction. Stage row ops with [`WriteTxn::add`]/`remove`/`edit`,
86
161
  * then [`WriteTxn::commit`] to apply them as one batch and get the per-query flat
@@ -166,6 +241,16 @@ export class WriteTxn {
166
241
  * `serverBatchEnd`.
167
242
  */
168
243
  commit(): any;
244
+ /**
245
+ * Like [`commit`](WriteTxn::commit), but ALSO returns the per-change **touched-source key list**
246
+ * in staged order as `{ batches, sources: string[] }` (Slice E-iii-b, design Q1). Each `sources`
247
+ * entry is the [`SourceKey`] the corresponding staged change routed through — the pk's provenance
248
+ * captured **pre-apply** (a `remove` erases the row, so provenance is unrecoverable afterward),
249
+ * `"daemon"` for a new/unknown pk or any Collapsed table. The order aligns 1:1 with the staged
250
+ * writes, so the optimistic layer can zip it with a mutation's write-set (per-pk hold-back
251
+ * source, Slice E-iii-c). `batches` is exactly what [`commit`](WriteTxn::commit) returns.
252
+ */
253
+ commitTracked(): any;
169
254
  /**
170
255
  * Stage an edit of `old` → `new_row` in `table` (both width-checked).
171
256
  *
@@ -236,13 +321,22 @@ export interface InitOutput {
236
321
  readonly __wbg_db_free: (a: number, b: number) => void;
237
322
  readonly __wbg_rindleview_free: (a: number, b: number) => void;
238
323
  readonly __wbg_writetxn_free: (a: number, b: number) => void;
324
+ readonly db_addRoomSource: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
239
325
  readonly db_destroyQuery: (a: number, b: number) => void;
326
+ readonly db_dropHeldBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
327
+ readonly db_freezeSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
328
+ readonly db_heldBackState: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
329
+ readonly db_holdBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
330
+ readonly db_holdBackAbsent: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
240
331
  readonly db_new: () => number;
332
+ readonly db_provenanceOf: (a: number, b: number, c: number, d: any) => [number, number, number];
241
333
  readonly db_query: (a: number, b: number, c: any) => [number, number, number];
242
334
  readonly db_registerTable: (a: number, b: number, c: number, d: any, e: number) => [number, number];
243
- readonly db_serverBatchBegin: (a: number, b: any) => [number, number];
335
+ readonly db_removeRoomSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
336
+ readonly db_serverBatchBegin: (a: number, b: number, c: number, d: any) => [number, number];
244
337
  readonly db_serverBatchEnd: (a: number) => [number, number, number];
245
338
  readonly db_unregisterTable: (a: number, b: number, c: number) => [number, number];
339
+ readonly db_writableMatches: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
246
340
  readonly db_write: (a: number) => number;
247
341
  readonly rindleview_build: (a: any, b: any, c: any) => [number, number, number];
248
342
  readonly rindleview_data: (a: number) => any;
@@ -253,6 +347,7 @@ export interface InitOutput {
253
347
  readonly rindleview_subscribe: (a: number, b: any) => number;
254
348
  readonly writetxn_add: (a: number, b: number, c: number, d: any) => [number, number];
255
349
  readonly writetxn_commit: (a: number) => [number, number, number];
350
+ readonly writetxn_commitTracked: (a: number) => [number, number, number];
256
351
  readonly writetxn_edit: (a: number, b: number, c: number, d: any, e: any) => [number, number];
257
352
  readonly writetxn_get: (a: number, b: number, c: number, d: any) => [number, number, number];
258
353
  readonly writetxn_query: (a: number, b: any) => [number, number, number];
package/pkg/rindle.js CHANGED
@@ -14,6 +14,27 @@ export class Db {
14
14
  const ptr = this.__destroy_into_raw();
15
15
  wasm.__wbg_db_free(ptr, 0);
16
16
  }
17
+ /**
18
+ * Attach a room source to `table`, compiling `writable` into the merge's per-row
19
+ * [`WritableScope`] ([`parse_writable_scope`]): `{ kind: "all" }` = the room may write every row
20
+ * (room wins); `{ kind: "none" }` = a context table (daemon stays authoritative);
21
+ * `{ kind: "colEq", col, value }` = writable iff `row[col] == value` (harness stand-in);
22
+ * `{ kind: "predicate", where }` = the row-local wire-`Condition` predicate (Slice G-ii),
23
+ * lowered by the engine's own [`create_predicate`] against `table`'s schema.
24
+ * @param {string} table
25
+ * @param {string} room_key
26
+ * @param {any} writable
27
+ */
28
+ addRoomSource(table, room_key, writable) {
29
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
30
+ const len0 = WASM_VECTOR_LEN;
31
+ const ptr1 = passStringToWasm0(room_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
32
+ const len1 = WASM_VECTOR_LEN;
33
+ const ret = wasm.db_addRoomSource(this.__wbg_ptr, ptr0, len0, ptr1, len1, writable);
34
+ if (ret[1]) {
35
+ throw takeFromExternrefTable0(ret[0]);
36
+ }
37
+ }
17
38
  /**
18
39
  * Tear down a registered query: drop its sink + reclaim its pipeline (disconnect from
19
40
  * the shared sources, free its slots — [`Graph::destroy_pipeline`]). Other queries
@@ -23,12 +44,133 @@ export class Db {
23
44
  destroyQuery(query_id) {
24
45
  wasm.db_destroyQuery(this.__wbg_ptr, query_id);
25
46
  }
47
+ /**
48
+ * Drop one parked hold-back at `probe`'s pk ([`Db::drop_held_back`] — the §301 fence-driven
49
+ * drop) and **deliver** the compensating deltas: returns the same
50
+ * `[{ queryId, events: FlatChange[] }]` shape as `WriteTxn.commit` (empty inside an open
51
+ * server-batch cycle, where the events join the cycle's one delivery instead) — unlike the
52
+ * park-time seams, whose install reconcile is a no-op by construction, a fence drop is
53
+ * exactly the moment a masked authoritative row becomes visible, so it must reach the
54
+ * views. `probe` is a full-width positional row (the pk derives from it — the same
55
+ * marshalling as [`holdBack`](Self::hold_back_js)).
56
+ * @param {string} table
57
+ * @param {string} source_key
58
+ * @param {any} probe
59
+ * @returns {any}
60
+ */
61
+ dropHeldBack(table, source_key, probe) {
62
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
63
+ const len0 = WASM_VECTOR_LEN;
64
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
65
+ const len1 = WASM_VECTOR_LEN;
66
+ const ret = wasm.db_dropHeldBack(this.__wbg_ptr, ptr0, len0, ptr1, len1, probe);
67
+ if (ret[2]) {
68
+ throw takeFromExternrefTable0(ret[1]);
69
+ }
70
+ return takeFromExternrefTable0(ret[0]);
71
+ }
72
+ /**
73
+ * Freeze a physical source — the D4 downgrade-ghost stub ([`Db::freeze_source`]).
74
+ * @param {string} table
75
+ * @param {string} source_key
76
+ */
77
+ freezeSource(table, source_key) {
78
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
79
+ const len0 = WASM_VECTOR_LEN;
80
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
81
+ const len1 = WASM_VECTOR_LEN;
82
+ const ret = wasm.db_freezeSource(this.__wbg_ptr, ptr0, len0, ptr1, len1);
83
+ if (ret[1]) {
84
+ throw takeFromExternrefTable0(ret[0]);
85
+ }
86
+ }
87
+ /**
88
+ * The parked hold-back at `probe`'s pk on `source_key`'s slice, as
89
+ * `{ absent: boolean, baseline: row | undefined } | undefined`
90
+ * ([`Db::held_back_state`], §301): `undefined` when nothing is parked — including an
91
+ * unknown source (non-throwing, so the client registry prunes lazily after a state-match
92
+ * drop or a room removal); `baseline` is the source's current confirmed-baseline row at
93
+ * the pk (the §2.5 stuck-pin tripwire input). An unknown TABLE still throws (the row
94
+ * cannot even be marshalled).
95
+ * @param {string} table
96
+ * @param {string} source_key
97
+ * @param {any} probe
98
+ * @returns {any}
99
+ */
100
+ heldBackState(table, source_key, probe) {
101
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
102
+ const len0 = WASM_VECTOR_LEN;
103
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
104
+ const len1 = WASM_VECTOR_LEN;
105
+ const ret = wasm.db_heldBackState(this.__wbg_ptr, ptr0, len0, ptr1, len1, probe);
106
+ if (ret[2]) {
107
+ throw takeFromExternrefTable0(ret[1]);
108
+ }
109
+ return takeFromExternrefTable0(ret[0]);
110
+ }
111
+ /**
112
+ * Park an echoed `row` inertly on `source_key`'s slice of `table` ([`Db::hold_back`]).
113
+ * @param {string} table
114
+ * @param {string} source_key
115
+ * @param {any} row
116
+ */
117
+ holdBack(table, source_key, row) {
118
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
119
+ const len0 = WASM_VECTOR_LEN;
120
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
121
+ const len1 = WASM_VECTOR_LEN;
122
+ const ret = wasm.db_holdBack(this.__wbg_ptr, ptr0, len0, ptr1, len1, row);
123
+ if (ret[1]) {
124
+ throw takeFromExternrefTable0(ret[0]);
125
+ }
126
+ }
127
+ /**
128
+ * Park `old_row`'s pk as **absent** on `source_key`'s slice of `table`
129
+ * ([`Db::hold_back_absent`]) — the §7.3 tombstone for a confirmed cross-slice REMOVE.
130
+ * `old_row` is the full pre-image row, marshalled exactly like [`holdBack`](Self::hold_back_js)'s
131
+ * `row` (a full-width positional array of bare cells).
132
+ * @param {string} table
133
+ * @param {string} source_key
134
+ * @param {any} old_row
135
+ */
136
+ holdBackAbsent(table, source_key, old_row) {
137
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
138
+ const len0 = WASM_VECTOR_LEN;
139
+ const ptr1 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
140
+ const len1 = WASM_VECTOR_LEN;
141
+ const ret = wasm.db_holdBackAbsent(this.__wbg_ptr, ptr0, len0, ptr1, len1, old_row);
142
+ if (ret[1]) {
143
+ throw takeFromExternrefTable0(ret[0]);
144
+ }
145
+ }
26
146
  constructor() {
27
147
  const ret = wasm.db_new();
28
148
  this.__wbg_ptr = ret;
29
149
  DbFinalization.register(this, this.__wbg_ptr, this);
30
150
  return this;
31
151
  }
152
+ /**
153
+ * The winning source currently serving `row`'s pk on `table`, as its wasm-boundary key
154
+ * string (`"daemon"` / the room key, [`source_key_to_string`]), or `undefined` when no
155
+ * source holds the pk ([`Db::provenance`], §3.2 #3 — Slice H-i routing probe #1). `row` is
156
+ * a full-width positional row (the pk derives from it — the same marshalling as
157
+ * [`holdBack`](Self::hold_back_js)); an unknown table throws loudly. Reads the LIVE visible
158
+ * state, **overlay-first** (§7.3): an open [`WriteTxn`]'s staged-but-uncommitted writes are
159
+ * not consulted (they have not routed yet), which is exactly the pre-commit view the §3
160
+ * routing proof decides on.
161
+ * @param {string} table
162
+ * @param {any} row
163
+ * @returns {any}
164
+ */
165
+ provenanceOf(table, row) {
166
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
167
+ const len0 = WASM_VECTOR_LEN;
168
+ const ret = wasm.db_provenanceOf(this.__wbg_ptr, ptr0, len0, row);
169
+ if (ret[2]) {
170
+ throw takeFromExternrefTable0(ret[1]);
171
+ }
172
+ return takeFromExternrefTable0(ret[0]);
173
+ }
32
174
  /**
33
175
  * Register a live query from a Zero-wire AST under the caller's opaque `query_id`.
34
176
  * Lowers it into the shared graph, sinks it in a change-sink, and hydrates. Returns
@@ -72,6 +214,21 @@ export class Db {
72
214
  throw takeFromExternrefTable0(ret[0]);
73
215
  }
74
216
  }
217
+ /**
218
+ * Detach a room source, emitting the compensating visible deltas ([`Db::remove_room_source`]).
219
+ * @param {string} table
220
+ * @param {string} room_key
221
+ */
222
+ removeRoomSource(table, room_key) {
223
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
224
+ const len0 = WASM_VECTOR_LEN;
225
+ const ptr1 = passStringToWasm0(room_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
226
+ const len1 = WASM_VECTOR_LEN;
227
+ const ret = wasm.db_removeRoomSource(this.__wbg_ptr, ptr0, len0, ptr1, len1);
228
+ if (ret[1]) {
229
+ throw takeFromExternrefTable0(ret[0]);
230
+ }
231
+ }
75
232
  /**
76
233
  * Open a §1.3 reconcile cycle against the coherent server delta `deltas` —
77
234
  * an array of base-table row ops `{ table, type: "add"|"remove"|"edit", row,
@@ -83,13 +240,20 @@ export class Db {
83
240
  * events buffer into the cycle), then calls
84
241
  * [`server_batch_end`](Db::server_batch_end) for the one coalesced delivery.
85
242
  *
243
+ * `source_key` names the authority these `deltas` confirm — `"daemon"` at the wasm boundary
244
+ * today (single-domain); a `room:doc:X` string once the room feed lands (Slice E-iii/G). It
245
+ * selects which physical source [`Merge::rewind`] folds the delta into.
246
+ *
86
247
  * Errors if a cycle is already open, on an unknown table, or on a push failure
87
- * — after which the optimistic state is poisoned (a partial rewind may have
248
+ * — after which the per-source rebase state is poisoned (a partial rewind may have
88
249
  * applied): the only safe recovery is to discard this `Db` and re-hydrate.
250
+ * @param {string} source_key
89
251
  * @param {any} deltas
90
252
  */
91
- serverBatchBegin(deltas) {
92
- const ret = wasm.db_serverBatchBegin(this.__wbg_ptr, deltas);
253
+ serverBatchBegin(source_key, deltas) {
254
+ const ptr0 = passStringToWasm0(source_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
255
+ const len0 = WASM_VECTOR_LEN;
256
+ const ret = wasm.db_serverBatchBegin(this.__wbg_ptr, ptr0, len0, deltas);
93
257
  if (ret[1]) {
94
258
  throw takeFromExternrefTable0(ret[0]);
95
259
  }
@@ -136,6 +300,31 @@ export class Db {
136
300
  throw takeFromExternrefTable0(ret[0]);
137
301
  }
138
302
  }
303
+ /**
304
+ * Evaluate the REGISTERED writable scope of `table`'s room source `room_key` against a
305
+ * caller-supplied full-width `row` ([`Db::writable_matches`] — Slice H-i routing probe #2):
306
+ * the §3 proof's write-set rule, run through the SAME compiled predicate the merge's winner
307
+ * tiering uses (the G-ii descriptor `addRoomSource` registered), so routing can never
308
+ * disagree with the engine. `{kind:"all"}` ⇒ always `true`; `{kind:"none"}` ⇒ always
309
+ * `false`; a frozen source still answers (the predicate is static). Loud errors: unknown
310
+ * table, a room key not attached to the table, a wrong-width row, and `"daemon"` (reserved
311
+ * by [`parse_source_key`]; the daemon has no writable scope).
312
+ * @param {string} table
313
+ * @param {string} room_key
314
+ * @param {any} row
315
+ * @returns {boolean}
316
+ */
317
+ writableMatches(table, room_key, row) {
318
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
319
+ const len0 = WASM_VECTOR_LEN;
320
+ const ptr1 = passStringToWasm0(room_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
321
+ const len1 = WASM_VECTOR_LEN;
322
+ const ret = wasm.db_writableMatches(this.__wbg_ptr, ptr0, len0, ptr1, len1, row);
323
+ if (ret[2]) {
324
+ throw takeFromExternrefTable0(ret[1]);
325
+ }
326
+ return ret[0] !== 0;
327
+ }
139
328
  /**
140
329
  * Open a write transaction. Stage row ops with [`WriteTxn::add`]/`remove`/`edit`,
141
330
  * then [`WriteTxn::commit`] to apply them as one batch and get the per-query flat
@@ -309,6 +498,24 @@ export class WriteTxn {
309
498
  }
310
499
  return takeFromExternrefTable0(ret[0]);
311
500
  }
501
+ /**
502
+ * Like [`commit`](WriteTxn::commit), but ALSO returns the per-change **touched-source key list**
503
+ * in staged order as `{ batches, sources: string[] }` (Slice E-iii-b, design Q1). Each `sources`
504
+ * entry is the [`SourceKey`] the corresponding staged change routed through — the pk's provenance
505
+ * captured **pre-apply** (a `remove` erases the row, so provenance is unrecoverable afterward),
506
+ * `"daemon"` for a new/unknown pk or any Collapsed table. The order aligns 1:1 with the staged
507
+ * writes, so the optimistic layer can zip it with a mutation's write-set (per-pk hold-back
508
+ * source, Slice E-iii-c). `batches` is exactly what [`commit`](WriteTxn::commit) returns.
509
+ * @returns {any}
510
+ */
511
+ commitTracked() {
512
+ const ptr = this.__destroy_into_raw();
513
+ const ret = wasm.writetxn_commitTracked(ptr);
514
+ if (ret[2]) {
515
+ throw takeFromExternrefTable0(ret[1]);
516
+ }
517
+ return takeFromExternrefTable0(ret[0]);
518
+ }
312
519
  /**
313
520
  * Stage an edit of `old` → `new_row` in `table` (both width-checked).
314
521
  *
Binary file
@@ -5,13 +5,22 @@ export const __rindle_wasm_start: () => void;
5
5
  export const __wbg_db_free: (a: number, b: number) => void;
6
6
  export const __wbg_rindleview_free: (a: number, b: number) => void;
7
7
  export const __wbg_writetxn_free: (a: number, b: number) => void;
8
+ export const db_addRoomSource: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
8
9
  export const db_destroyQuery: (a: number, b: number) => void;
10
+ export const db_dropHeldBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
11
+ export const db_freezeSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
12
+ export const db_heldBackState: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
13
+ export const db_holdBack: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
14
+ export const db_holdBackAbsent: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number];
9
15
  export const db_new: () => number;
16
+ export const db_provenanceOf: (a: number, b: number, c: number, d: any) => [number, number, number];
10
17
  export const db_query: (a: number, b: number, c: any) => [number, number, number];
11
18
  export const db_registerTable: (a: number, b: number, c: number, d: any, e: number) => [number, number];
12
- export const db_serverBatchBegin: (a: number, b: any) => [number, number];
19
+ export const db_removeRoomSource: (a: number, b: number, c: number, d: number, e: number) => [number, number];
20
+ export const db_serverBatchBegin: (a: number, b: number, c: number, d: any) => [number, number];
13
21
  export const db_serverBatchEnd: (a: number) => [number, number, number];
14
22
  export const db_unregisterTable: (a: number, b: number, c: number) => [number, number];
23
+ export const db_writableMatches: (a: number, b: number, c: number, d: number, e: number, f: any) => [number, number, number];
15
24
  export const db_write: (a: number) => number;
16
25
  export const rindleview_build: (a: any, b: any, c: any) => [number, number, number];
17
26
  export const rindleview_data: (a: number) => any;
@@ -22,6 +31,7 @@ export const rindleview_setResultType: (a: number, b: number, c: number) => void
22
31
  export const rindleview_subscribe: (a: number, b: any) => number;
23
32
  export const writetxn_add: (a: number, b: number, c: number, d: any) => [number, number];
24
33
  export const writetxn_commit: (a: number) => [number, number, number];
34
+ export const writetxn_commitTracked: (a: number) => [number, number, number];
25
35
  export const writetxn_edit: (a: number, b: number, c: number, d: any, e: any) => [number, number];
26
36
  export const writetxn_get: (a: number, b: number, c: number, d: any) => [number, number, number];
27
37
  export const writetxn_query: (a: number, b: any) => [number, number, number];
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import type {
13
13
  Backend,
14
14
  ChangeEvent,
15
15
  ColsMap,
16
+ Condition,
16
17
  Mutation,
17
18
  QueryId,
18
19
  Schema,
@@ -20,6 +21,23 @@ import type {
20
21
 
21
22
  export * from "@rindle/client";
22
23
 
24
+ /** A serializable writable-scope descriptor: how the merge decides, per row, whether an attached
25
+ * room may write it (the room copy then wins, RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5.2).
26
+ * `"all"` = the room owns every row it holds; `"none"` = a context table the daemon stays
27
+ * authoritative for; `"colEq"` = writable iff `row[col] === value` (harness stand-in). The REAL
28
+ * shape (Slice G) is `"predicate"`: `where` is the SAME wire {@link Condition} JSON a query AST
29
+ * carries, restricted to what evaluates against a single row — `simple` column-vs-literal leaves
30
+ * under `and`/`or`; a `correlatedSubquery` (or any other non-row-local node) is rejected loudly at
31
+ * `addRoomSource` time. It compiles Rust-side through the engine's OWN filter lowering
32
+ * (`create_predicate`), so writable evaluation can never disagree with query-filter semantics.
33
+ * NOTE: room join-key columns (Slice H) deliberately do NOT cross this boundary — they stay
34
+ * TS-side. */
35
+ export type WritableDescriptor =
36
+ | { kind: "all" }
37
+ | { kind: "none" }
38
+ | { kind: "colEq"; col: number; value: unknown }
39
+ | { kind: "predicate"; where: Condition };
40
+
23
41
  // The wasm Db surface this backend uses (kept local so it doesn't depend on the generated .d.ts).
24
42
  interface WasmDb {
25
43
  registerTable(table: string, spec: { columns: string[]; primaryKey: number[] }, local: boolean): void;
@@ -27,8 +45,23 @@ interface WasmDb {
27
45
  query(queryId: number, ast: Ast): { comparatorVersion: number; schema: unknown; snapshot: unknown[] };
28
46
  destroyQuery(queryId: number): void;
29
47
  write(): WasmWriteTxn;
30
- serverBatchBegin(deltas: ServerDeltaOp[]): void;
48
+ serverBatchBegin(sourceKey: string, deltas: ServerDeltaOp[]): void;
31
49
  serverBatchEnd(): Array<{ queryId: number; events: unknown[] }>;
50
+ // Multi-source seams (Slice E-iii-b harness → Slice G-ii ABI — see WritableDescriptor).
51
+ addRoomSource(table: string, roomKey: string, writable: WritableDescriptor): void;
52
+ removeRoomSource(table: string, roomKey: string): void;
53
+ holdBack(table: string, sourceKey: string, row: unknown[]): void;
54
+ holdBackAbsent(table: string, sourceKey: string, oldRow: unknown[]): void;
55
+ dropHeldBack(table: string, sourceKey: string, probe: unknown[]): unknown;
56
+ heldBackState(
57
+ table: string,
58
+ sourceKey: string,
59
+ probe: unknown[],
60
+ ): { absent: boolean; baseline: unknown[] | undefined } | undefined;
61
+ freezeSource(table: string, sourceKey: string): void;
62
+ // Slice H-i routing probes (design §3.1/§3.2 — see the WasmBackend mirrors below).
63
+ provenanceOf(table: string, row: unknown[]): string | undefined;
64
+ writableMatches(table: string, roomKey: string, row: unknown[]): boolean;
32
65
  }
33
66
 
34
67
  /** A raw staged write transaction over the wasm engine — the surface an optimistic client
@@ -46,6 +79,11 @@ export interface WasmWriteTxn {
46
79
  * shape to a `view.data` row), in the query's order. */
47
80
  query(ast: Ast): unknown[];
48
81
  commit(): Array<{ queryId: number; events: unknown[] }>;
82
+ /** Like {@link commit}, additionally returning the per-change touched-source key list in staged
83
+ * order (`sources`, e.g. `"daemon"` / `"room:doc:1"`) — the provenance each staged write routed
84
+ * through, captured pre-apply (Slice E-iii-b, design Q1). `batches` is exactly {@link commit}'s
85
+ * return; the source list is additive. */
86
+ commitTracked(): { batches: Array<{ queryId: number; events: unknown[] }>; sources: string[] };
49
87
  rollback(): void;
50
88
  }
51
89
 
@@ -242,22 +280,29 @@ export class WasmBackend<S extends ColsMap> implements Backend {
242
280
  * `onCommitted` runs after `tx.commit()` returns but before `dispatch` delivers to
243
281
  * subscribers: a throw from `f` (pre-commit — engine untouched) skips it; a subscriber
244
282
  * throw re-raised by `dispatch` happens after it. It is the only point where "the commit
245
- * is applied" is knowable to a caller that must not confuse the two failure modes. */
246
- writeWith(f: (tx: WasmWriteTxn) => void, onCommitted?: () => void): void {
283
+ * is applied" is knowable to a caller that must not confuse the two failure modes.
284
+ *
285
+ * Returns the per-change **touched-source key list** in staged-write order (`"daemon"` /
286
+ * `"room:doc:X"`, Slice E-iii-b design Q1) — additive; every current caller (`mutate`,
287
+ * `writeLocal`, `@rindle/optimistic`) ignores it. The per-source reconcile (Slice E-iii-c) zips
288
+ * it with the mutation's write-set for the per-pk hold-back source. */
289
+ writeWith(f: (tx: WasmWriteTxn) => void, onCommitted?: () => void): string[] {
247
290
  const tx = this.db.write();
248
291
  f(tx);
249
- const batches = tx.commit() as BatchSet;
292
+ const { batches, sources } = tx.commitTracked() as { batches: BatchSet; sources: string[] };
250
293
  onCommitted?.();
251
294
  this.dispatch(batches);
295
+ return sources;
252
296
  }
253
297
 
254
- /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds
255
- * (optimistic layer un-applied, delta folded in, `sync` re-forked) and starts
256
- * buffering. Re-invoke the still-pending mutators via {@link writeWith}, then call
257
- * {@link serverBatchEnd}. On error the optimistic state is poisoned discard the
258
- * backend and re-hydrate. */
259
- serverBatchBegin(deltas: ServerDeltaOp[]): void {
260
- this.db.serverBatchBegin(deltas);
298
+ /** Open a §1.3 reconcile cycle against the coherent server delta: the engine rewinds the
299
+ * per-source rebase (optimistic layer un-applied, delta folded in, the source's baseline
300
+ * re-forked) and starts buffering. `sourceKey` names the authority the delta confirms
301
+ * `"daemon"` today (single-domain); a `room:doc:X` key once the room feed lands. Re-invoke the
302
+ * still-pending mutators via {@link writeWith}, then call {@link serverBatchEnd}. On error the
303
+ * rebase state is poisoned — discard the backend and re-hydrate. */
304
+ serverBatchBegin(sourceKey: string, deltas: ServerDeltaOp[]): void {
305
+ this.db.serverBatchBegin(sourceKey, deltas);
261
306
  }
262
307
 
263
308
  /** Close the cycle: the whole buffered stream (rewind + re-invocations) coalesces to
@@ -266,6 +311,98 @@ export class WasmBackend<S extends ColsMap> implements Backend {
266
311
  serverBatchEnd(): void {
267
312
  this.dispatch(this.db.serverBatchEnd() as BatchSet);
268
313
  }
314
+
315
+ // --- multi-source seams (Slice E-iii-b harness → Slice G-ii ABI) ------------------
316
+ //
317
+ // Thin pass-throughs to the engine's `Merge` (RINDLE-REALTIME-QUERY-ENABLEMENT-DESIGN.md §5).
318
+ // `addRoomSource`'s {@link WritableDescriptor} now carries the real row-local `predicate` arm
319
+ // (a wire-Condition `where`, compiled Rust-side through the engine's own filter lowering)
320
+ // alongside the narrow `all`/`none`/`colEq` harness shapes. Single-domain behavior is untouched
321
+ // unless `addRoomSource` is called (a table stays Collapsed = today's daemon-only path).
322
+
323
+ /** Attach a room source to `table`, compiling `writable` into the merge's per-row writable scope.
324
+ * Promotes the table Collapsed → Merged on the first room (§5.2); the room seeds empty, so no
325
+ * visible blip. Throws on an untracked (local-only) table or a duplicate room. */
326
+ addRoomSource(table: string, roomKey: string, writable: WritableDescriptor): void {
327
+ this.db.addRoomSource(table, roomKey, writable);
328
+ }
329
+
330
+ /** Detach a room source, emitting the compensating visible deltas on the ordinary event stream. */
331
+ removeRoomSource(table: string, roomKey: string): void {
332
+ this.db.removeRoomSource(table, roomKey);
333
+ }
334
+
335
+ /** Park an echoed `row` inertly on `sourceKey`'s slice of `table` (the confirm→echo hold-back
336
+ * window, design §7.3): keeps the row visible so the next per-source rewind of `sourceKey` does
337
+ * not flash-revert a confirmed-elsewhere write before its data echo lands. `sourceKey` is
338
+ * `"daemon"` or a `"room:doc:X"` key. */
339
+ holdBack(table: string, sourceKey: string, row: unknown[]): void {
340
+ this.db.holdBack(table, sourceKey, row);
341
+ }
342
+
343
+ /** Park `oldRow`'s pk as ABSENT on `sourceKey`'s slice of `table` — the §7.3 tombstone, the
344
+ * remove-sibling of {@link holdBack} (Slice G-ii): a confirmed cross-slice REMOVE whose delete
345
+ * echo has not landed on `sourceKey` must not resurrect through that source's next rewind. The
346
+ * overlay masks the pk (overlay-first) until the source's re-forked baseline lacks it (the
347
+ * delete echoed), then drops. `oldRow` is the full-width PRE-IMAGE row (same positional
348
+ * bare-cell marshaling as {@link holdBack}); it carries the pk and, later (Slice H), the cells
349
+ * writable-scope predicates evaluate on. */
350
+ holdBackAbsent(table: string, sourceKey: string, oldRow: unknown[]): void {
351
+ this.db.holdBackAbsent(table, sourceKey, oldRow);
352
+ }
353
+
354
+ /** Drop one parked §7.3 hold-back at `probe`'s pk on `sourceKey`'s slice and reconcile the
355
+ * pk's visible winner — the §301 FENCE-driven drop (`301-ECHO-FENCE-DESIGN.md` §2.2), fired
356
+ * by the optimistic backend's pin registry once `sourceKey`'s stream has provably delivered
357
+ * through the pinned write's commit. Unlike the park-time seams (whose install is a no-op by
358
+ * construction) a drop is exactly the moment a masked authoritative row becomes visible, so
359
+ * the engine's compensating deltas are DELIVERED here: dispatched on the ordinary event
360
+ * stream (or folded into an open server-batch cycle's one delivery). No-op when nothing is
361
+ * parked at the pk — the rewind's state-match fallback may have raced it. */
362
+ dropHeldBack(table: string, sourceKey: string, probe: unknown[]): void {
363
+ this.dispatch(this.db.dropHeldBack(table, sourceKey, probe) as BatchSet);
364
+ }
365
+
366
+ /** The parked hold-back at `probe`'s pk on `sourceKey`'s slice, or `undefined` (§301
367
+ * introspection): `absent` names the tombstone variant; `baseline` is the source's CURRENT
368
+ * confirmed-baseline row at the pk (`undefined` when the baseline lacks it) — the pin
369
+ * registry's lazy-prune probe and the §2.5 stuck-pin tripwire input. Non-throwing on an
370
+ * unknown source (a removed room), so pruning after a whole-source removal just works. */
371
+ heldBackState(
372
+ table: string,
373
+ sourceKey: string,
374
+ probe: unknown[],
375
+ ): { absent: boolean; baseline: unknown[] | undefined } | undefined {
376
+ return this.db.heldBackState(table, sourceKey, probe);
377
+ }
378
+
379
+ /** Freeze a physical source — the D4 downgrade-ghost stub (wins-if-present in the merge). */
380
+ freezeSource(table: string, sourceKey: string): void {
381
+ this.db.freezeSource(table, sourceKey);
382
+ }
383
+
384
+ /** The source currently serving `row`'s pk on `table` — `"daemon"`, a room key, or `undefined`
385
+ * when no source holds the pk (Slice H-i routing probe #1, design §3.2 #3). `row` is a
386
+ * full-width positional row (the pk derives from it — same marshaling as {@link holdBack});
387
+ * an unknown table throws loudly. Reads the LIVE visible state, overlay-first (§7.3): an open
388
+ * write txn's staged-but-uncommitted rows are NOT consulted (they have not routed yet), which
389
+ * is exactly the pre-commit view the §3 routing proof decides on — a present read is proven
390
+ * room-covered iff this reports the occupied room. */
391
+ provenanceOf(table: string, row: unknown[]): string | undefined {
392
+ return this.db.provenanceOf(table, row);
393
+ }
394
+
395
+ /** Evaluate the REGISTERED writable scope of `table`'s room source `roomKey` against a
396
+ * caller-supplied full-width row (Slice H-i routing probe #2, design §3.1): a prediction-run
397
+ * write is proven room-covered iff this is `true` on the written row. Runs the SAME compiled
398
+ * predicate the merge's winner tiering uses (the {@link WritableDescriptor} registered at
399
+ * {@link addRoomSource}), so routing can never disagree with the engine; `all` ⇒ always true,
400
+ * `none` ⇒ always false, and a frozen source still answers (the predicate is static). Throws
401
+ * loudly on an unknown table, a room key not attached to the table, a wrong-width row, or
402
+ * `"daemon"` (which has no writable scope). */
403
+ writableMatches(table: string, roomKey: string, row: unknown[]): boolean {
404
+ return this.db.writableMatches(table, roomKey, row);
405
+ }
269
406
  }
270
407
 
271
408
  /** Convenience: init the wasm + return a ready local {@link Store}. */