Base: f6857fee9. This record covers the working-tree change that adds Model D
to packages/db/tests/query/includes-temporal-oracle.test.ts.
A user reported (TanStack/db#2113) that useLiveSuspenseQuery over a progressive Electric
collection suspends until the full sync completes, even when the query's rows
are present. The cause was in core. allRequiredSourcesReady() exempted an
on-demand source from its Collection readiness only on the lazy side of a join.
A progressive source reports syncMode: 'on-demand' but stays loading until
its full sync, so a query rooted on it waited for the whole dataset.
Law (ARCHITECTURE.md, initial readiness and law 10): initial-query readiness
requires every reachable demand to be covered by a settled request and every
source that serves no subset demand to be Collection-ready. An on-demand
source's own Collection readiness does not gate it. An Effect with
skipInitial ends suppression at the same point.
Authority: ARCHITECTURE.md law 10 already stated readiness as demand coverage.
The TanStack DB 0.5 post
defines progressive mode: "First query loads immediately (on-demand)" while the
collection "syncs full dataset in background". The source readiness gate came
from the earlier allCollectionsReady() rule and had no separate authority.
The same review found a related Effect defect. An Effect with skipInitial over
an on-demand source that is already ready did not wait for its initial subset
load, so the subset rows arrived as enter events.
- Contract: initial-query readiness and Effect initial suppression.
- Histories: every legal order of delivery, settlement, source readiness,
and one later change for a source whose Collection readiness lags its
settled subsets (Model D grammar).
- Production paths: a live-query Collection preload and createEffect
over direct and lazy-join on-demand roots and two eager roots, one with
syncMode: 'eager' and one with the default. The live query
also covers a single-result root (the reported findOne trace), an ordered
window, and an include child.
- Observations: isReady(), preload settlement, visible rows, Effect
events, and acquisitions at each step checkpoint.
Original witness: the issue's standalone script passes with the fix and fails
on the original build with queryReady: false, preloadResolved: false. Its
shape is the find-one cell. Live query direct: [deliver, settle, source-ready] fails at
checkpoint 2 on the original code with ready: false, preloadSettled: false
while its row is visible. That is the reported behavior.
Mutants, each run against Model D. Every outcome is an assertion failure at the
named checkpoint:
| Mutant | Rejecting cells | Checkpoint |
|---|
| Live query: lazy-only exemption (original) | 4 direct, 4 find-one, 4 ordered | 2–3 |
| Live query: ignore source readiness | 6 eager-root, 6 eager-default | 0 |
| Effect: suppress until source readiness | direct, join with a mid-sync change | 3 |
| Effect: ignore source readiness | 4 eager-root, 4 eager-default | 1 |
| Effect: on-demand exempt, no initial participant | 24 direct/join | 1–2 |
| Live query: exempt every source not set eager | 6 eager-default | 0 |
| Effect: no participant for lazy demand | 12 join | 1–2 |
| Effect: no participant for the root initial load | 12 direct | 1–2 |
The last three come from the second review round. Before the eager-default
shape existed, the "not set eager" mutant passed Model D, because its eager
fixture always set syncMode explicitly; only the deferred-acquisition oracle
rejected it. Three more mutants survive and are equivalent within the
contract:
- Exempting an on-demand source only while it is loading differs only for an
idle, errored, or cleaned-up source. An errored or cleaned-up source puts the
live query in error first. A preload or subscriber starts an idle source's
sync run, and a deferring query has its own readiness rule (law 15).
- Tracking initial loads without skipInitial cannot differ, because
initialLoadComplete starts true without skipInitial and never resets.
The production guard was redundant and is removed.
- Treating a rejected initial load as success cannot differ, because the
rejection calls onSourceError, which disposes the Effect.
Receiving witnesses (ORC-014):
- electric-live-query.test.ts, "a live query is ready once its subset
loads": a real Electric progressive source supplies the premise "subset
settled, Collection loading". It fails on the original code by assertion.
- "a skipInitial effect over an on-demand source suppresses its subset rows":
real Electric on-demand. It fails on the original code with [1].
- "a skipInitial effect suppresses only its subset rows" (progressive) passes
on the original code. Electric buffers progressive changes until the full
sync, so it cannot supply a change between settlement and readiness. It
rejects the no-participant mutant.
- useLiveSuspenseQuery.test.tsx, "releases with useLiveQuery isReady when a
lagging source's subset settles": fails on the original code.
- ORC-001: The law, its authority, and its limits are in the Model D prose
and the architecture document.
- ORC-002: expectedLiveQueryObservation and expectedEffectObservation
compute the result from four model facts and the source mode. They import no
production classifier.
- ORC-003: The prose beside the model, grammar, driver, and projection
explains each one. The opening law list adds law 7.
- ORC-004 and ORC-007: Not applicable. The grammar is a fixed, exhaustive
enumeration (108 cells), not a generated property. Exclusion: settle before
deliver would contradict applied settlement, so it is not generated.
- ORC-005: Real Collections, createLiveQueryCollection, preload(), and
createEffect. The observations are public.
- ORC-006: See the mutant table.
- ORC-008: Each fact is needed. delivered changes rows, settled changes
on-demand readiness, sourceReady changes eager readiness, and
writtenAfterReadiness changes Effect events. Each is shown by a mutant kill
above.
- ORC-009: "Lagging source" is a model label for an on-demand source whose
markReady() comes after its settled subsets. source-ready maps to
markReady(), and settle maps to resolving every acquisition the consumer
issued.
- ORC-010: Cleanup uses finishTemporalCleanup, which keeps the primary
mismatch and each cleanup failure.
- ORC-011: Not applicable. No reviewer named a fault that the model and
production could share.
- ORC-013: The eager root and the mid-sync change are the nearby witnesses
that reject the two wrong boundaries.
- ORC-014: See the receiving witnesses.
Four fresh reviewers attacked this change with the oracle-review skill. The
enforcement and law reviewers found no defects, but they gave
source-inspection verdicts and did not execute the mutants or tensions they
claimed to check. The mutant reviewer reported every mutant killed by all 29
React tests, including Effect mutants that the React file does not exercise.
That is a setup failure, not a comparison result, so the author reran its
mutant ideas against Model D. That run produced the second-round rows above.
A /code-review pass found two regressions in the first Effect repair and two
in Electric that the new readiness rule exposed:
- Effect hold latch. The first repair put initial loads on the
ordered-repair gate, whose failure flag never clears. A truncate during the
initial load, or a lazy-join rekey before the first settle, aborts the
pending acquisition without disposing the Effect, so the Effect went silent
for good. Model D's Effect lane now crosses each history with a truncate
or rekey, and its fixture rejects aborted acquisitions. RED: 30 disturbed
cells failed at the first write after readiness. The repair gives initial
loads their own hold: an aborted load releases it, and a truncate replacement
takes it over. Two narrower repairs are rejected. Keeping the shared latch
but passing the truncate replacement fails 10 rekey cells. Releasing without
the takeover fails 80 truncate cells.
- Electric reset settlement. Outside its buffered initial sync, a
progressive load installed no rows yet resolved at once. During a
must-refetch before the first ready, the query became ready and empty. The
phase matrix in electric-live-query.test.ts fails on the original Electric
code in both reset phases and passes on the original core rule. The repair
settles such a load when the collection is first ready. A first version that
also waited during a resumed stream failed six existing Electric oracle tests
that pin the complete-resume contract, and was narrowed to resets only.
The resumed-stream phase is the neighboring witness.
A second /code-review pass (head 2812be07f) made ten findings. Outcomes:
- Electric progressive loads now wait on the established snapshot. The
separate reset waiter resolved on any abort, leaked its listener, and did
not follow the swap's applied receipt. Progressive streams now reuse the
fullSnapshotReady deferred. A must-refetch renews it, the up-to-date
commit resolves it with its receipt, a stream abort rejects it, and a
complete resume starts it resolved. The listener and abort findings are
fixed by removing that code path. For the receipt window, neither a
persisting transaction nor a held durable write made the swap's row
invisible after up-to-date. The repair covers that window by construction,
and the persisted test asserts the law ("ready implies the row is visible").
A witness that reaches the window is still missing.
- Refuted: early release of the Effect's hold. An initial load's result
rejects only on cleanup, which disposes the Effect first (law 14), on demand
retirement, or on unsubscribe. Re-acquisition after detachment forwards the
replay's completion.
- Law wording. The law said "every source that serves no subset demand",
but the code checks for eager sources. The law now names eager sources.
- Model independence. Model D's mode rule mirrors production's
classification. Each on-demand cell now compares its consumer with a
metamorphic twin over a source that is ready from its start. Under the
original rule, the twin is ready while the lagging consumer is not, so the
twin alone rejects that design.
- Naming and duplication. The Effect's source check is now
sourcesPermitInitialReadiness, and both Effect holds share one replacement
handoff while keeping their own failure policies.
- Open design decision. A progressive source drops txids until its full
sync. A mutation on a ready progressive query can therefore wait for that
sync in awaitTxId. useLiveQuery already rendered rows in this window
before this change; Suspense now does too.
A third high-effort review (head 19354ffce) made ten findings:
- Fixed: progressive loads after an onError retry. A stream error
rejected the shared snapshot deferred, and only full-log on-demand streams
renewed it on retry. Every later progressive load rejected with the old
error. RED: a new Electric test fails on this branch and passes on main.
Progressive streams now take the same retry renewal.
- Fixed: disturbed live-query and queryOnce lanes. Model D now crosses
every on-demand consumer with a truncate or a parent rekey. This found a
defect that already exists on main: a truncate of a lazy source during
the initial load aborted its pending demand, and the live query entered
error. 80 cells failed at the first checkpoint after the truncate. The
lazy demand now follows the truncate replacement, as the Effect hold does.
- Fixed by construction: a failed initial load no longer reads the
Effect's subscription before it exists, a disposed Effect takes no new
hold, and Electric's progressive snapshot getter is required, without
dead fallbacks.
- Cleanup: the Effect class JSDoc sits on the class again, and the new
helper and test use no any.
- Not changed: a lazy demand settlement can schedule two graph runs. The
second run is idempotent, and avoiding it needs another parameter on the
hold.
- Tracked elsewhere: awaitTxId during the full sync (#2120) and the
narrower source-capability design (#2118).
The law predicts behavior for other readiness consumers. Each prediction now
has a witness that fails on the original rule:
- queryOnce resolves when the subset settles (Model D's query-once lane:
the original rule fails 4 cells).
- A live-query observer hands a hydration seed off at subset settlement
(live-query-observer.test.ts).
- Network-first initial rendering cannot combine with an on-demand source.
The persisted-readiness oracle rejects that configuration, so the new rule
cannot bypass the network-first gate.
- A ready progressive query's rows update only at the full sync's swap
(electric-live-query.test.ts, a pinned limit).
The author checked when each on-demand adapter calls markReady() compared
with its subset loads:
- query-db: markReady() waits for startup retention maintenance, and
createQueryFromOpts holds every subset load until that maintenance
finishes. A settled subset therefore implies the maintenance completed.
- PowerSync on-demand: calls markReady() while sync starts, so its
readiness does not lag.
- TrailBase on-demand: load does not wait for recordApi.subscribe, and
markReady() follows the subscription. A live query can now become ready
before the change listener is attached. The visible rows are unchanged. The
fetch-versus-listen gap that the adapter's comment describes already applied
to core subset loads; readiness no longer hides it.
- RxDB: has no on-demand mode.
After three review rounds kept finding defects, the author audited every
producer and consumer of the readiness signal. The author designed the
grammar from the boundary matrix, checked it with TLA+, and listed every
outcome of every async hold.
- Grammar. progressive-source-readiness-tla/ holds a bounded TLA+
model of the frozen grammar, its trace table, the boundary matrix, and the
exclusions. Four safe configurations pass. Nine weakened configurations
each produce the expected counterexample: the original rule, ignoring
eager readiness, both Effect hold defects, the lazy-truncate failure,
settling during a reset without rows, a retry without renewal,
recomputed readiness, and a late window continuation. One encoding
failure (lazy demand missing after a snapshot settle) was corrected, with a
receipt.
- Consumers. The shared framework conformance suite now has a lagging
on-demand source and the readiness law for a list query, a findOne query,
and the infinite query. The original rule fails each new scenario in every
driver (13 failures, all assertions). Solid's Suspense keeps its own
witness, because the shared suite has no Suspense surface.
- Fixed: toArrayWhenReady() and stateWhenReady(). They resolved as
soon as the Collection held a row, so a live query's read could resolve
before readiness. They now resolve through preload(). Model D's two new
read lanes start the read after deliver. The original shortcut fails 236
cells, all at that checkpoint.
- Producers. Witnesses under a real live query show that query-db,
PowerSync, TrailBase, and the SQLite persisted wrapper settle a load only
with its rows visible, reject real failures, and install no rows for a
released demand. These adapters call markReady() early, so the witnesses
check applied settlement, not the readiness rule. PowerSync resolves an
aborted acquisition instead of rejecting it; the generation check ignores
it.
- TrailBase widened. An it.fails witness shows that a query can now be
first ready before the change subscription starts. The original rule
passes it.
- Async holds. Six holds by eight outcomes: 12 new tests, 8 of 11 mutants
killed, and 3 equivalent survivors. A progressive load that is waiting when
a stream error arrives rejects, even if onError retries; a test pins
this.
TrailBase on-demand: a change committed between a subset fetch and the
change subscription can be missed. The owner is
packages/trailbase-db-collection/tests/ORACLE.md. This predates the change.
Effect skipInitial over an ordered window or an include child has no lane.
Vue, Svelte, and Angular have no Suspense surface in the shared suite.
A persisted follower tab's load may settle before the leader's rows apply
in that tab. Only the two-tab OPFS e2e suite can show this; it has not run.
Electron IPC, two on-demand roots, and include children apart from lazy
joins are not exercised.
Whether a load that was waiting at a retried stream error is a real failure
is a product decision.
The Electric e2e progressive suite has no run receipt (see the coverage map).
A rejected acquisition or a source error while a source lags is outside
Model D.
Before the full sync, an Electric progressive query's rows do not receive
live changes. The source contract owns that behavior, not this law.