TanStack

Content temporarily unavailable

Issue #1891 asynchronous cleanup oracle review

Reviewed semantic head: 21ed924352e91e47f0643de6d2542051ddd12d20

Scope: the cleanup-settlement repair in packages/db/tests/collection-cleanup-restart-oracle.test.ts and the adjacent focused refinements in packages/db-sqlite-persistence-core/tests/persisted.test.ts and packages/powersync-db-collection/tests/load-hooks.test.ts. This documentation closeout does not change the reviewed semantic tree.

Claim and limits

The public Collection.cleanup() contract, the CleanupFn contract, and the contributing glossary authorize the core law. Cleanup invalidates the old sync run and starts local teardown synchronously. If that run owns adapter cleanup work, the work settles before the Collection publishes cleaned-up. The terminal status event admits a new sync run. A continuation attached to the public cleanup Promise runs later.

Concurrent callers receive one Promise. A rejected adapter cleanup still ends the old sync run once, publishes cleaned-up, and rejects each waiter with the adapter error as the SyncCleanupError cause. It does not retry the retired callback. A later sync run remains admissible. Cleanup treats an incidental non-Promise return from a contextual-void callback as synchronous. It awaits a runtime Promise-like return even when the callback is statically typed to return void.

The core owner is a partial oracle. It does not establish full demand or replay histories, provider transport shutdown, persistence-wrapper behavior, or general row-publication laws. The adjacent persistence and PowerSync tests refine the same settlement boundary for their wrappers. They do not expand the core owner's row or transport claims.

The five oracle responsibilities are visible as follows:

  • Contract: the oracle opening and this record state the cleanup and restart law, authority, observation cuts, and limits.
  • Model: cleanup is a closed admission interval. Same-stack invalidation starts it. Adapter settlement and terminal status publication end it. The terminal event admits restart before public Promise continuations run.
  • History grammar: the core matrix crosses abort versus release reentry, nested cleanup, and one versus two restart attempts. Focused cases add fulfillment, rejection, runtime return shape, concurrent waiters, and event versus awaited restart.
  • Production driver: the tests call the public Collection entry points. The adjacent refinements drive the real persisted wrapper and a real PowerSync Node SQLite database through public Collections and live queries.
  • Refinement check: the tests compare exact status, event and continuation order, Promise identity and outcome, error cause, callback counts, ownership, subscriber counts, unhandled-error channels, and later restart behavior at named gates.

ORC-001 through ORC-011

RequirementOutcome
ORC-001: contract authority and limitsPass. Collection.cleanup(), the CleanupFn documentation, and the glossary's cleanup and restart terms authorize the expected result. The oracle opening and this record exclude full demand/replay, provider transport, wrapper-wide persistence, and general publication claims.
ORC-002: independent judgmentPass. The expected order comes from the public contract: adapter settlement, terminal status publication, then public Promise continuation. Held gates and sentinel errors supply the expected observations without importing lifecycle transitions, cleanup classifiers, or adapter settlement helpers from production.
ORC-003: distinguishable responsibilitiesPass. The responsibilities are listed above. The executable owner keeps the contract and model in its opening, the bounded scenarios matrix beside the focused histories, public Collection drivers in each test, and exact refinement assertions at controlled checkpoints.
ORC-004: generated-history grammar controlsNot applicable. The repair makes no generated-history coverage claim. Its matrix is bounded enumeration, and the adapter refinements are fixed controlled histories.
ORC-005: production path and observationPass. Core tests call public cleanup, start, preload, and status subscription entry points. Persistence tests use persistedCollectionOptions, same-resource replacement, and real coordinator ownership. PowerSync tests use a real Node SQLite database, source Collection, and live queries. Gates prove that public cleanup remains pending; exact status, error cause, call count, ownership, and global error-channel assertions preserve the promised violations.
ORC-006: checker calibrationPass. The R4 ordering mutant resolved the public cleanup Promise before scheduling terminal status. The gated core test reached the intended checkpoint and failed its exact order assertion with the public continuation before the cleaned-up event. This was an assertion failure, not a timeout, setup failure, or unreached path. The mutant was fully restored. Pre-fix witnesses also produced early core restart, duplicate persistence ownership, skipped teardown, early PowerSync settlement, and unhandled adapter rejection at their intended checkpoints.
ORC-007: fixed and random campaigns with replayNot applicable. The repaired owner and adjacent refinements contain no important generated property.
ORC-008: stateful-model minimalityNot applicable. The repair does not add, remove, combine, or split state in a stateful reference model. The bounded relation still distinguishes pending cleanup, terminal cleanup, and a later sync run because restart legality and public settlement distinguish those cuts.
ORC-009: vocabulary mappingPass. The model-only phrase cleanup admission interval maps to the time from same-stack sync-run invalidation through terminal cleaned-up publication. Sync run, cleanup, restart, logical owner, physical acquisition, acquisition lease, and Promise settlement retain their glossary meanings. Adapter cleanup settlement and public cleanup Promise continuation are separate observations.
ORC-010: failure fidelity and cleanupPass. The tests do not shrink or normalize traces. Exact sentinel identity, SyncCleanupError.cause, ordered observations, and unhandled-error arrays preserve the primary violation. Persistence uses cleanupPersistedOracle; PowerSync uses withTestCleanup. Both attempt every cleanup and retain secondary diagnostics. Core gates are released in finally, and retired callbacks are not retried.
ORC-011: independent second formulationPass. The named shared-fault hypothesis is that a wrapper starts cleanup but discards a returned Promise. The core in-memory driver checks the public lifecycle relation. Persistence independently checks same-resource replacement, retired remote-owner fencing, and all-task settlement after a synchronous runtime failure. PowerSync independently checks eager provider disposal and already-acquired on-demand hooks through real SQLite. These paths share only the cleanup settlement and once-only release law; row ordering, projection, duplicates, and empty results are not part of this cleanup claim.

ORC-012 is satisfied by this versioned record. It identifies the exact reviewed semantic head, records every ORC-001 through ORC-011 outcome, and is linked from the coverage map.

Calibration and semantic-head receipts

The R4 mutant changed only terminal ordering. The driver had already attached a continuation and then released the adapter gate. Production reached adapter settlement and both public observations. The exact comparison rejected the wrong sequence adapter -> public continuation -> cleaned-up event; the required sequence is adapter -> cleaned-up event -> public continuation.

Additional RED/GREEN controls reached these missing laws:

  • An incidental numeric return from a contextual-void cleanup incorrectly delayed core restart. Runtime Promise-like classification repaired it.
  • A persisted source retired during synchronous entry later registered remote ownership. A same-ID replacement exposed the duplicate owner.
  • A synchronous persisted runtime-teardown failure settled public cleanup before held source cleanup and skipped later release attempts.
  • Eager PowerSync cleanup settled before held trigger disposal. The rejection lane escaped through the global error channel.
  • Active on-demand PowerSync cleanup settled before held hook cleanup, swallowed a synchronous throw, and failed to await every reentrant release.

At the reviewed head, the core focused owner campaign passed 349 tests with no type errors. Persistence passed 292 tests with one existing todo and passed its type check. PowerSync passed 154 tests and its type, lint, format, and package build checks.

Known limits and tracked follow-up

PowerSync cleanup now awaits eager trigger disposal and every already-acquired on-demand hook. Two on-demand histories remain outside this repair:

  • an onLoadSubset hook that is pending when cleanup starts and returns its cleanup callback only after retirement;
  • provider trigger disposal created by that pending on-demand acquisition.

A held on-demand trigger disposer proved that public cleanup can settle before disposal. A candidate that delayed terminal cleaned-up until that work settled made the disposer checks pass, but violated the existing staged-subset baseline law: a held dependent preload could fulfill before source-cleanup publication. The candidate was removed. This is a confirmed design gap, not green evidence.

The durable destination is one core cleanup-start/intermediate signal distinct from terminal cleaned-up. Adapters can use that signal to retire and await pending or late hooks and on-demand trigger disposal without allowing a staged dependent preload to fulfill during cleanup. That destination must be designed with the pending/late on-demand hook history; this review does not claim it is implemented.

Dual-failure reconciliation

Reviewed semantic head: 228b68f7

A later review found two sibling cleanup paths that awaited every teardown but reported only one rejection. The persisted wrapper preferred source cleanup and dropped runtime teardown. Eager PowerSync preferred the load-hook cleanup and dropped trigger disposal. The PowerSync loss was deterministic once both resources existed; callback completion order did not change the winner.

Controlled pre-fix probes reached both production paths. The persisted probe observed only the exact source error. The PowerSync probe observed only the exact load-hook error in both controlled completion orders. The repaired paths keep source cleanup and trigger disposal primary, respectively. Each now uses an AggregateError to retain the secondary diagnostic. Lone failures keep their prior identity.

The coverage gap was a missing dual-rejection history. Existing tests exercised each cleanup source separately. The persisted-history owner now crosses source and runtime rejection in one public cleanup. The PowerSync load-hook refinement crosses hook and trigger rejection in both completion orders. Both tests assert the public SyncCleanupError, aggregate cause, ordered error identities, once-only callbacks, and terminal status where applicable.

This reconciliation adds focused histories, not a generated-history or new state-model claim. ORC-001, ORC-002, ORC-005, ORC-006, and ORC-010 are satisfied by the established cleanup contract, independent sentinel errors, public Collection drivers, recorded pre-fix assertion failures, exact error identity, and bounded resource cleanup. ORC-003, ORC-004, ORC-007, ORC-008, ORC-009, and ORC-011 are not triggered by these focused refinements. This versioned section satisfies ORC-012 for the reconciliation.

Design-grammar and loss-audit closeout

Reviewed semantic head: ccef1e2ea6f6ea04740a27d7de6efde3424ecca6

This closeout adds the Query DB cleanup helper and public Query-backed Collection witness to the reviewed scope. It also re-audits the core cleanup oracle, persisted wrapper, and PowerSync wrapper after the failure-retention repairs. A later documentation-only receipt commit does not change this semantic tree.

Intended law and minimal grammar

The retained model has five primitives:

  1. An active sync run owns cleanup obligations.
  2. Same-stack cleanup invalidation creates one shared retirement ticket.
  3. An ordered obligation ledger waits for every acquired cleanup obligation.
  4. A deterministic failure algebra preserves every failure, its role, and its ownership order; a lone failure keeps its identity.
  5. The public observation trace is invalidation, obligation settlement, terminal cleaned-up publication, public retirement Promise continuation, and only then an ordinary replacement run.

The wrappers refine that grammar rather than adding unrelated Cartesian axes:

RefinementPrimary roleLater retained roles
Coreadapter cleanuplocal teardown
Persistencesource cleanupruntime releases in release order
PowerSync eagertrigger disposalload-hook cleanup
PowerSync on-demandfirst acquired cleanup obligationlater obligations in ledger order
Query DBadapter cleanupQueryClient unmount

A contextual-void callback may return an incidental non-Promise value; that is synchronous completion. A native Promise or structural thenable is an obligation and must settle. Fulfillment and rejection both close the old run; rejection remains observable after terminal publication. Completion timing never chooses the primary failure.

Loss-audit retention decisions

The audit retained the distinctions that can change a promised observation:

  • active run, retirement pending, terminal publication, public Promise settlement, and replacement run remain separate cuts;
  • zero, one, and multiple obligations remain distinguishable;
  • incidental values, native Promises, and structural thenables remain separate runtime return classes;
  • synchronous throw, asynchronous rejection, and multiple failures remain separate settlement shapes;
  • abort and release reentry, nested cleanup, and repeated restart attempts remain explicit grammar coordinates;
  • one and multiple wrapper-owned resources remain separate because the latter exposes dropped diagnostics and order-dependent precedence;
  • wrapper priority is a named refinement mapping, not an implicit scheduling accident.

The audit deliberately did not import demand replay, row publication, provider transport, or adapter-specific startup into the cleanup grammar. A pending PowerSync hook discovered after cleanup starts belongs to the cleanup-start signal design in PR #1897. Those behaviors may share code paths, but no cleanup observation here can judge their full contracts.

Grammar reconstruction, ablation, range, and exclusion

Reconstruction. Every known valid cleanup witness in scope maps to the five primitives: synchronous and asynchronous cleanup, structural thenables, fulfillment and rejection, concurrent and nested cleanup callers, terminal event and awaited restart, and each wrapper's resource roles. No known witness requires a sixth lifecycle state or a wrapper-specific copy of the model.

Ablation. Each declared coordinate has a named contribution. Removing abort or release loses one reentry boundary. Removing nested cleanup loses the shared-ticket check. Removing the second attempt loses repeated early-admission pressure. Collapsing return classes recreates the truthy-number or hidden- thenable bugs. Collapsing settlement shapes loses rejection ordering and lone- error identity. Collapsing ownership count loses later failures. Removing a wrapper row loses its public resource mapping and precedence law. The executable 2 × 2 × 2 admission calibration proves eight unique cells and one execution coordinate per declared cell.

Range. The bounded admission axes are abort or release, nested cleanup on or off, and one or two restart attempts. Focused margins cover zero/one/many obligations, incidental/native/structural returns, synchronous/asynchronous failure, both completion orders, and one/two wrapper failures. No independently supplied held-out marginal case was available after construction, so formal external range validation remains untested. A later range evaluation should supply a behavior-preserving witness without changing the grammar first; the model succeeds only if that witness reconstructs from the existing primitives and the checker reaches its named public checkpoint.

Exclusion and negative controls. The grammar rejects restart before terminal publication, duplicated physical release, a dropped secondary failure, treating a truthy non-Promise as a Promise, and ownership published by a retired run. Pre-fix production reached and failed the intended checkpoints: Query DB threw TypeError: result.then is not a function; persistence exposed only the first runtime release error; PowerSync exposed only the first acquired hook rejection in both controlled completion orders. These were assertion failures, not timeouts, setup failures, or unreached paths.

ORC-001 through ORC-012 reconciliation

RequirementOutcome at the reviewed semantic head
ORC-001: contract authority and limitsPass. The public cleanup contract and glossary authorize settlement, terminal publication, and restart. The oracle opening and this record exclude replay, publication, provider transport, and late-acquisition design.
ORC-002: independent judgmentPass. Held gates, structural thenables, sentinel identities, ordered public observations, and a role table derive expectations without importing production cleanup classifiers or failure selectors.
ORC-003: distinguishable responsibilitiesPass. Contract and model are in the oracle opening; named axes form the grammar; public Collections and wrappers are the drivers; exact status, order, identity, count, and ownership assertions are the refinement checks.
ORC-004: generated-history grammar controlsNot triggered because this remains bounded enumeration, not a generated-property claim. Reconstruction, ablation, range, and exclusion are nevertheless recorded above, and unique-cell calibration is executable.
ORC-005: production path and observationPass. Core, persistence, and PowerSync drive public Collections. Query DB directly checks helper composition and separately proves the real public wrapper mounts and unmounts QueryClient exactly once. A supported public seam for injecting arbitrary async internal Query cleanup does not exist; no private seam was added.
ORC-006: checker calibrationPass. The prior R4 ordering mutant remains killed. New pre-fix controls separately killed truthy non-Promise classification and both same-role failure-dropping designs at their intended assertions.
ORC-007: fixed/random campaigns and replayNot triggered. No important randomized property was added or claimed.
ORC-008: stateful-model minimalityNot triggered by a state-model shape change. The audit still retained five observable cuts because restart legality and Promise ordering distinguish each adjacent pair.
ORC-009: vocabulary mappingPass. Active sync run, cleanup, restart, logical owner, physical acquisition, and Promise settlement retain glossary meanings. Retirement ticket, obligation ledger, and terminal publication are model descriptions mapped above to the public cleanup Promise, acquired cleanup work, and the cleaned-up status event.
ORC-010: failure fidelity and cleanupPass. Core and PowerSync hostile harness controls retain a primary mismatch plus every cleanup failure. Persistence now calibrates both modes: it aggregates cleanup failures when there is no primary and records all secondary diagnostics without replacing an existing primary. All actions are attempted.
ORC-011: independent second formulationPass within scope. The core public lifecycle uses a structural thenable; Query's direct helper isolates wrapper composition while the public QueryClient witness proves real mount/unmount wiring; persisted ownership replacement and real PowerSync SQLite hooks independently test wrapper refinement. Query's public witness does not claim arbitrary async adapter injection.
ORC-012: review evidencePass. This exact-head record accounts for ORC-001 through ORC-011, including non-triggered requirements and the unresolved held-out-range limitation.

Exact-head receipts

Environment: Node v24.19.0, pnpm 11.1.0, Vitest 3.2.4, Darwin arm64.

At baseline head efa90005248c7274795e489aa08e494914215483, the new RED controls produced one Query failure, one persistence failure, and two PowerSync failures. At semantic head ccef1e2ea6f6ea04740a27d7de6efde3424ecca6:

  • the core cleanup oracle passed 25 of 25 tests;
  • Query cleanup composition passed 8 of 8 tests, including the public wrapper witness;
  • the selected persisted cleanup and harness histories passed 8 of 8 tests with 240 unrelated tests skipped by the name filter;
  • the PowerSync load-hook file passed 25 of 25 tests;
  • TypeScript checks passed for DB, SQLite persistence, and PowerSync;
  • Query DB's package build, including declaration generation, passed. Its broad TypeScript configuration also traverses DB collection E2E suites and failed only because @tanstack/electric-db-collection was not resolvable in this checkout; no changed Query file produced a diagnostic;
  • DB, SQLite persistence, and PowerSync package builds also passed, and the documentation link check found no broken links;
  • targeted ESLint reported zero errors. Its 22 warnings are pre-existing require-await warnings in the persisted test file outside the changed lines;
  • Prettier and git diff --check passed.