Skip to content

Content temporarily unavailable

Stale reacquired Query ownership oracle review

Reviewed source: branch investigate-finite-stale-reacquisition, based on origin/main at 31dad2dd3. The first runs used acaaf4c0a. The counts, RED/GREEN table, and mutant table were last rerun on the commit that follows 67987ce10, which extends the reacquired lease table and records the open cells; that tree was measured before it was committed. The independent reviews read the acaaf4c0a tree.

Contract and defect

PR #2198 (db0ac815e) made a released Query keep an empty queryToRows entry until its release publishes, so metadata that has not published the release cannot restore it as an owner. The result handler's already-applied guard and the on-demand direct-write skip also read entry presence as "the active observer lease has applied a result". When a stale Query was reacquired while its release was held, the refetch-start notification repeated the cached result, the guard read the release's entry as applied, and the reacquired lease owned nothing. The release then removed the row, and a failed refetch never restored it.

Laws checked, each stated beside its model in packages/query-db-collection/tests/ownership-lifecycle.oracle.test.ts:

  1. A reacquired observer lease owns the Query's latest successful data from reacquisition until its own release. A successful refetch replaces that result; a failed one keeps it. staleTime decides only whether a refetch starts.
  2. Published owners never name a released lease, including when another lease claims the row while the release is held.
  3. An accepted on-demand direct write patches the cache of an active Query whose lease has not applied a result.
  4. An accepted direct write survives a refetch start of an empty current result.
  5. A release whose durable commit fails is terminal for its sync run, and a restart begins from clean ownership.

Authority: the file's opening ownership and direct-write laws, the existing reacquisition history, TanStack Query's stale-mount and failed-refetch rules, the DbClient hydration history, the glossary's cleanup definition, and the AGENTS.md fail-fast rules.

Repair

hasAppliedOwnership reports an entry only when it is not a release's entry. removeQueryOwnership marks its empty entry in a WeakSet. The guard and the on-demand direct-write skip use the helper. The result-application rollback snapshot keeps the entry by reference (CR-1 below). The production diff is 15 lines added and 6 removed in src/query.ts.

RED and GREEN

Witnessmain 31dad2dd3Repair
Reacquired lease table (42 cells: 7 refetch histories × 0–2 peers × 1–2 rows)20 fail. Example: published: [] and settled: [] where the model gives ["shared"]. With peers, shared is lost only at peerReleased, while a two-row result loses solo at publication42 pass
Published owners table (8 cells)8 pass8 pass
Reacquired cache patch behind deferDataRefreshFails: cache ["Shared"], model ["Manual"]Passes
Empty-result direct insert (eager, on-demand)2 pass2 pass
Failed release commit (3 cells)3 pass3 pass
Reacquired commit fails after a held releasePassesPasses

The oracle file passes 585 of 585 tests with the repair and 564 on main. The Query Collection package passes 1460 tests with 2 skipped, and the type check reports no errors. Three repeated runs of the new tables gave identical results.

Mutant results

Each mutant was applied to the repaired source, run against the whole oracle file, and reverted. Counts are for the final oracle file.

MutantOutcomeKilling witness
Guard reads entry presence (the defect)Assertion failure, 20 testsReacquired lease table
Release entry not markedAssertion failure, 21 testsReacquired lease table, cache patch
Direct-write skip reads entry presenceAssertion failure, 1 testCache patch (Query cache only; public rows agree)
Applied means non-empty entryAssertion failure, 2 testsEmpty-result direct insert: on-demand loses the insert at its receipt; eager keeps Manual after Newer
Delete entry at releaseAssertion failure, 4 testsPublished owners while-held: owner count 2 instead of 1; without a peer the hydrated row leaks
Delete the released entry when the Query is reacquiredAssertion failure, 2 testsPublished owners while-held, reacquired: the reacquired lease has no cached result and its fetch fails, so b copies a back from unpublished metadata
Replace the released entry with an unmarked empty Set at reacquisitionAssertion failure, 21 testsReacquired lease table, cache patch
Mark the reacquired lease's own application entry as releasedAssertion failure, 5 testsPersisted direct-write refetch-start histories and the direct cache patch bound
Reuse the released Set object for the next applicationAssertion failure, 25 testsReacquisition history and reacquired lease table: the reused entry keeps its mark
Guard removed (false &&)Assertion failure, 4 testsPersisted direct-write refetch-start histories
Hostile storage that swallows a failed commitAssertion failure, 2 cells and 1 testBoth failed-commit witnesses
Cleanup skips entries without an observerSurvives the oracle file; assertion failure, 1 test, in the package suitetests/query.test.ts "should clear retained ownership during explicit collection cleanup": a retained Query's retention marker survives cleanup

The last four rows came from a later independent review. It reported two survivors without a distinguishing history; the concrete versions above were written from its descriptions. Deleting the released entry at reacquisition survived every test until the reacquired published-owner histories were added; it fails them and passes nothing else differently.

CodeRabbit review of 8ff1db49f

Review 5600814169 had one inline finding and a summary. Ledger:

IDClaimVerdictAction
CR-1The rollback snapshot in result application clones the entry, so a failed application that restores it drops a release's markTrue at the code level. No public consequence reached: in persisted histories the release retires its entry before the reacquired application snapshots (instrumented with the release commit gated and ungated), and in non-persisted histories the held commit rejects only by cancellation, which the restore guards skipFixed: the snapshot keeps the entry by reference, because application replaces the entry rather than mutating it. No witness distinguishes this; reverting it passes all 557 tests
CR-2Add a history with reacquisition during a held release, a commit failure before publication, and a failed refetch; the row must remain until the reacquired lease is releasedThe history runs; its expected result conflicts with the fail-loud law. The release is durable and the reacquired result is not, so the row leaves and the Collection enters errorAdded "fails loudly when a reacquired lease's commit fails after a held release"; a storage adapter that swallows the failure fails it
CR-3Docstring coverage 0% for touched functionsTrue; the touched closure helpers use line comments like their neighboursNo change, per AGENTS.md's rule to match surrounding comment density
CR-4CodeRabbit's ESLint tool timed outA tool note, not a code claim; the repository Lint check passed on 8ff1db49fNo change
CR-5Title, description, and issue checks passedInformationalNo change

ORC outcomes

  • ORC-001: Each table states its law, authority, and limits before mechanics.
  • ORC-002: The models keep lease results and owner sets. They do not import queryToRows, the guard, or sync transactions.
  • ORC-003: Prose beside each model, grammar, driver, and comparison. The prose was updated with each grammar change.
  • ORC-004: Fixed enumerations, not generated properties. Each axis has a stated contribution: staleTime starts the refetch; outcome and placement separate keeping, replacing, and losing the row; peer hides a lost result until peerReleased; claims reaches the unpublished-metadata premise. Excluded: eager reacquisition, whose premise is unreachable (probed: cleanup removes the Query from the cache, so a restart fetches); persisted retention, which keeps released rows by design.
  • ORC-005: Public loadSubset, unloadSubset, direct writes, DbClient dehydrate and hydrate, cleanup, and preload. Observations are public rows, stored rows, Collection status, rejection messages, dehydrated owner counts, and QueryClient.getQueriesData. No missing-information hypothesis was identified.
  • ORC-006: The mutant table above. Every kill is an assertion failure at the named comparison.
  • ORC-007: Not applicable; no generated property.
  • ORC-008: The reacquisition model keeps one result per active lease; the peer axis needs the union, shown by the peer cells that differ only at peerReleased. The owner model keeps a set; the owner count distinguishes peer histories that public rows do not.
  • ORC-009: Glossary terms observer lease, acquisition release, publication, accepted sync transaction, and cleanup. a, b, c, and blocker are declared local names.
  • ORC-010: Each history registers cleanup that releases its holds and Query clients; comparisons are single structured assertions.
  • ORC-011: Not applied. The named shared-fault risk (an entry read as applied) is separated by the empty-result and cache-patch witnesses, which observe different boundaries; no further formulation was identified.
  • ORC-012: This record.
  • ORC-013: The reusable law "an empty entry is applied unless it belongs to a release" has a premise witness (reacquired lease table) and a nearby boundary witness (empty-result direct insert) that rejects the non-empty rule.
  • ORC-014: The controlled storage adapter supplies the commit failure. A receiving probe on Node SQLite aborted the release's delete with a SQLite trigger. The Collection entered error and storage kept the row, but the public Collection dropped it, while a peer's row stayed. The controlled adapter keeps the row public, whether it rejects at once or after a delay. The real SQLite adapter drops it on every sync-present configuration tried: managed, resolved for sync-present, and adapter-only without a coordinator. The controlled premise does not hold on the real host, so the cross-boundary claim is contradicted, not established. The divergence belongs to the SQLite persistence owner; see Open cells.

Bug-class boundary

Contract: Query ownership after release and reacquisition. Histories: an on-demand lease with a one- or two-row result and a held release, a stale or fresh reacquisition, zero to two peers, an optional refresh barrier, a direct write, and a failed release commit. Path: Query Collection over a real QueryClient and a controlled persistence adapter. Observations: public rows, stored rows, owners, status, and Query cache at the named checkpoints.

Open cells:

  • CR-1, unreached. Core rejects a committed, held sync transaction only in state.cleanup(), which runs after sync cleanup has removed the observers, so the restore guard returns. The remaining route is a duplicate-key replay inside the reacquired application's own open transaction, which no history here constructs.
  • SQLite durability failure. On the Node SQLite adapter, a sync transaction (here, a release) whose durable step fails still removes the row from the public Collection, while storage keeps it. This contradicts the glossary's accepted-sync-transaction rule, which accepts a wrapping sync's transaction only after its durable step. It belongs to the SQLite persistence owner and is outside this Query DB change; tracked in #2210.