From 0279f5a87b9dc719fb9f09aea5ab8947d1734fa6 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 9 Oct 2026 11:55:54 -0600 Subject: [PATCH 1/7] test(db-ivm): pin what min and max promise after a delete groupBy consolidates by hash, which merges -0 with 0 and equal Dates. After a delete, min and max return a hash-equal value, not necessarily the remaining instance. The query compiler keeps its own exact inputs for that reason; this test records the contract it relies on. Co-authored-by: Isaac --- .../db-ivm/tests/operators/groupBy.test.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index 63067bc1b4..29c8212e94 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -626,6 +626,55 @@ describe(`Operators`, () => { // These are readable replay witnesses. The generated groupBy law lives in // incrementalization-law-oracle.property.test.ts. + // Contract for callers: groupBy consolidates values by their hash, and the + // hash treats -0 as 0 and equal Dates as one value. So after a delete, + // min and max return a value hash-equal to a remaining member, but not + // necessarily the remaining instance: here they return the deleted -0 and + // the deleted Date. A caller that needs the exact remaining value, such as + // the query compiler's group representatives, must keep its own exact + // inputs. Distinct values are not affected. + test(`min and max after a delete return a hash-equal value, not a particular instance`, () => { + const extremes = (first: unknown, second: unknown) => { + const graph = new D2() + const input = graph.newInput<{ g: string; id: number; v: any }>() + let latest: { lo: unknown; hi: unknown } | undefined + input.pipe( + groupBy((row) => ({ g: row.g }), { + lo: min((row) => row.v), + hi: max((row) => row.v), + }), + output((message) => { + for (const [[, row], multiplicity] of message.getInner()) + if (multiplicity > 0) latest = row as typeof latest + }), + ) + graph.finalize() + const deleted = { g: `x`, id: 1, v: first } + input.sendData( + new MultiSet([ + [deleted, 1], + [{ g: `x`, id: 2, v: second }, 1], + ]), + ) + graph.run() + input.sendData(new MultiSet([[deleted, -1]])) + graph.run() + return latest! + } + + const zero = extremes(-0, 0) + expect(serializeValue(zero.lo)).toBe(serializeValue(0)) + expect(serializeValue(zero.hi)).toBe(serializeValue(0)) + + const kept = new Date(5) + const date = extremes(new Date(5), kept) + expect((date.lo as Date).getTime()).toBe(kept.getTime()) + expect((date.hi as Date).getTime()).toBe(kept.getTime()) + + const distinct = extremes(1, 2) + expect(distinct).toMatchObject({ lo: 2, hi: 2 }) + }) + test(`min and max reduce keep 0, 0n, and empty string as extremes`, () => { const minNum = min() const maxNum = max() From 4894372ad2cef063f6fdd7beb79211bc7e239012 Mon Sep 17 00:00:00 2001 From: Isaac Date: Fri, 9 Oct 2026 14:16:10 -0600 Subject: [PATCH 2/7] test(db-ivm): check the delete's output and record the min/max contract The contract test now clears its result before the delete and requires the delete to publish the group again, checks that a distinct remaining Date is returned as is, types its values, and no longer sits under the replay-witness header. The coverage map records the contract and why the query compiler keeps its own exact inputs. Co-authored-by: Isaac --- docs/contributing/oracle-coverage.md | 2 +- .../db-ivm/tests/operators/groupBy.test.ts | 38 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index f7128fb41e..aa96756bd1 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -241,7 +241,7 @@ comment and the current API/architecture contract before extending its model. | Surface | Primary executable owners | Independent judgment and important limit | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). | +| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact inputs. | | Includes and publication | [cross-formulation](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts), [temporal](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-temporal-oracle.test.ts), [Collection includes](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-collection-oracle.property.test.ts), [architecture and complete suite map](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/ARCHITECTURE.md#executable-contracts) | Per-parent/flat-join/partition relations, callback-time rows, nested values, and route histories. Fragmented lazy-demand consolidation covers delta growth and successful, rejected/retried, and obsolete replacements at the Collection request boundary; it observes exact key unions and abort checkpoints. A compiled-includes fixture makes adapter unload evict owned child rows and proves a rejected union cannot remove established public child rows. Retry after that terminal live-query failure remains covered at the acquisition boundary; no automatic in-place recovery is claimed. Observe raw promised order; fresh queries do not establish continuous publication safety. | | Collection lifecycle | [mutation startup](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-mutation-startup-oracle.test.ts), [change-event history](https://github.com/TanStack/db/blob/main/packages/db/tests/change-event-history-oracle.test.ts), [sync publication reentrancy](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-sync-reentrancy-oracle.test.ts), [history](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-history-oracle.property.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-publication-oracle.property.test.ts), [replay](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-replay-oracle.property.test.ts), [effect disposal](https://github.com/TanStack/db/blob/main/packages/db/tests/effect-disposal-oracle.test.ts), [PR #1902 review](oracle-reviews/pr-1902-change-event-history.md), [batch-order decision review](oracle-reviews/change-event-batch-order.md), [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md) | Core Collection `insert`/`update`/`delete` admission while `startSync:false` is idle; complete public-row/change-message agreement across bounded one- and two-key histories plus replayable four-key campaigns, where every message must be valid for a strict consumer (insert only for an absent key, update or delete only for a present one; [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md)); a two-key deferred sync history checks each key's causal insert/update/delete trace while allowing any cross-key interleaving. Reversing all deferred messages fails that check; reversing each sync transaction's distinct-key messages passes. Queued duplicate admission and cancellation, ownership and phase histories, exact caller/error/publication evidence, and late completion and restart have separate witnesses. Generated deferred histories with cancellation or reentrant callbacks need a witness in the change-event history owner before claiming general batch-shape coverage. Query write utilities and effect self-dependent disposal remain separate contracts. During a truncate, a ready callback can write to any key after a prior optimistic request. Each subscriber, with or without initial state or a filter, must receive valid messages after the Collection is ready and end at the Collection's rows. A throwing ready callback during sync entry surfaces once the sync function returns, whether it calls markReady or commits a truncate ([round 3 follow-ups](oracle-reviews/2026-10-05-round-3-followups.md)). Callback rollbacks, non-optimistic callback writes, and several writing callbacks remain open ([ready-callback truncate review](oracle-reviews/2026-10-05-truncate-ready-callback-delete.md)). | | Paced mutations | [virtual-clock oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/paced-mutations-oracle.test.ts) | `createPacedMutations` with queue front/back options, bounded waiting capacity at zero and one, cleanup draining in FIFO/LIFO order after a held write, cleanup timing at the configured wait for immediate writes, admitted writes after separate Collection cleanup, debounce and throttle schedules with explicit and omitted options, and first-leading throttle execution at epoch zero. Finite histories compare optimistic rows, returned transaction identity and receipt outcome, execution order and virtual-clock time. Held writes verify queue serialization. Leading-only throttle and debounce witnesses reject skipped calls immediately with their named dropped-call errors, including omitted edges, both edges disabled, and same-row rollback while a prior write is held. Capacity witnesses cover overflow rejection, distinct-key optimistic rollback, same-key admitted-write survival, and in-flight versus waiting admission. Cleanup witnesses check repeated queue cleanup, eventual admitted receipt settlement, direct queue strategy rejection of new callbacks, public post-cleanup rollback with `QueueDisposedError`, pending debounce transactions that wait until the last call's quiet edge after separate Collection cleanup, and a trailing throttle timer that runs at its regular edge after separate Collection cleanup. Deferred custom queue and batch callbacks check compatibility with `void` and `false` execute results, respectively. Frozen options verify factory non-mutation; a mutable option witness checks that debounce uses its construction-time trailing setting. Throwing `onMutate` histories check leading-edge reuse before any row change, failed-row restoration when a pending transaction already has admitted work, eligible leading writes behind a held handler, and throttle pending merges at the throw, start, receipt, and later-timer cuts. New debounce/throttle admission after cleanup, manual commit of a strategy-owned transaction, longer throwing/reentrant bursts, failed persistence, broader capacities and schedules remain outside this owner. | diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index 29c8212e94..2a8845ff7a 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -624,19 +624,23 @@ describe(`Operators`, () => { expect(latestMessage.getInner()).toEqual(expectedResult) }) - // These are readable replay witnesses. The generated groupBy law lives in - // incrementalization-law-oracle.property.test.ts. - // Contract for callers: groupBy consolidates values by their hash, and the - // hash treats -0 as 0 and equal Dates as one value. So after a delete, - // min and max return a value hash-equal to a remaining member, but not - // necessarily the remaining instance: here they return the deleted -0 and - // the deleted Date. A caller that needs the exact remaining value, such as - // the query compiler's group representatives, must keep its own exact - // inputs. Distinct values are not affected. + // Contract for callers, not a replay witness: the generated groupBy law in + // incrementalization-law-oracle.property.test.ts compares JSON values and + // does not cover -0 or Date identity. groupBy consolidates values by their + // hash, which treats -0 as 0 and equal Dates as one value. So after a + // delete, min and max return a value hash-equal to a remaining member, but + // not necessarily the remaining instance; the group is retracted and + // inserted again with that value. A caller that needs the exact remaining + // value, such as the query compiler's group representatives, must keep its + // own exact inputs. A distinct remaining value is returned as is. test(`min and max after a delete return a hash-equal value, not a particular instance`, () => { - const extremes = (first: unknown, second: unknown) => { + const extremes = (first: number | Date, second: number | Date) => { const graph = new D2() - const input = graph.newInput<{ g: string; id: number; v: any }>() + const input = graph.newInput<{ + g: string + id: number + v: number | Date + }>() let latest: { lo: unknown; hi: unknown } | undefined input.pipe( groupBy((row) => ({ g: row.g }), { @@ -657,8 +661,11 @@ describe(`Operators`, () => { ]), ) graph.run() + // Only the output of the delete counts. + latest = undefined input.sendData(new MultiSet([[deleted, -1]])) graph.run() + expect(latest, `the delete publishes the group again`).toBeDefined() return latest! } @@ -671,10 +678,15 @@ describe(`Operators`, () => { expect((date.lo as Date).getTime()).toBe(kept.getTime()) expect((date.hi as Date).getTime()).toBe(kept.getTime()) - const distinct = extremes(1, 2) - expect(distinct).toMatchObject({ lo: 2, hi: 2 }) + expect(extremes(1, 2)).toMatchObject({ lo: 2, hi: 2 }) + const distinct = new Date(6) + const distinctDates = extremes(new Date(5), distinct) + expect(distinctDates.lo).toBe(distinct) + expect(distinctDates.hi).toBe(distinct) }) + // These are readable replay witnesses. The generated groupBy law lives in + // incrementalization-law-oracle.property.test.ts. test(`min and max reduce keep 0, 0n, and empty string as extremes`, () => { const minNum = min() const maxNum = max() From 3e9d435b684f1607e8cffd2dd63234f13e80c7dd Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 10 Oct 2026 09:42:16 -0600 Subject: [PATCH 3/7] test(db-ivm): pin min/max identity after a delete in both directions The review showed the contract test could not fail on what it documented: it checked only a hash-equal bound with serializeValue. It now checks that bound with the hash the Index merges by, in both delete orders, and separately pins the current behavior that the deleted instance comes back, so a db-ivm change alerts the compiler owner. The group's row is read from the consolidated output, so republication is not part of the contract. The prose says the merge is over the whole aggregate input tuple. The compiler's exact-input comment points to the test, and the coverage map names the value-identity policy that would generalize it. Co-authored-by: Isaac --- docs/contributing/oracle-coverage.md | 2 +- .../db-ivm/tests/operators/groupBy.test.ts | 61 ++++++++++++------- packages/db/src/query/compiler/group-by.ts | 3 +- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index aa96756bd1..6fe188f222 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -241,7 +241,7 @@ comment and the current API/architecture contract before extending its model. | Surface | Primary executable owners | Independent judgment and important limit | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact inputs. | +| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact inputs. It pins the current behavior too: the deleted instance comes back, in either direction. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | | Includes and publication | [cross-formulation](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts), [temporal](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-temporal-oracle.test.ts), [Collection includes](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-collection-oracle.property.test.ts), [architecture and complete suite map](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/ARCHITECTURE.md#executable-contracts) | Per-parent/flat-join/partition relations, callback-time rows, nested values, and route histories. Fragmented lazy-demand consolidation covers delta growth and successful, rejected/retried, and obsolete replacements at the Collection request boundary; it observes exact key unions and abort checkpoints. A compiled-includes fixture makes adapter unload evict owned child rows and proves a rejected union cannot remove established public child rows. Retry after that terminal live-query failure remains covered at the acquisition boundary; no automatic in-place recovery is claimed. Observe raw promised order; fresh queries do not establish continuous publication safety. | | Collection lifecycle | [mutation startup](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-mutation-startup-oracle.test.ts), [change-event history](https://github.com/TanStack/db/blob/main/packages/db/tests/change-event-history-oracle.test.ts), [sync publication reentrancy](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-sync-reentrancy-oracle.test.ts), [history](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-history-oracle.property.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-publication-oracle.property.test.ts), [replay](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-replay-oracle.property.test.ts), [effect disposal](https://github.com/TanStack/db/blob/main/packages/db/tests/effect-disposal-oracle.test.ts), [PR #1902 review](oracle-reviews/pr-1902-change-event-history.md), [batch-order decision review](oracle-reviews/change-event-batch-order.md), [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md) | Core Collection `insert`/`update`/`delete` admission while `startSync:false` is idle; complete public-row/change-message agreement across bounded one- and two-key histories plus replayable four-key campaigns, where every message must be valid for a strict consumer (insert only for an absent key, update or delete only for a present one; [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md)); a two-key deferred sync history checks each key's causal insert/update/delete trace while allowing any cross-key interleaving. Reversing all deferred messages fails that check; reversing each sync transaction's distinct-key messages passes. Queued duplicate admission and cancellation, ownership and phase histories, exact caller/error/publication evidence, and late completion and restart have separate witnesses. Generated deferred histories with cancellation or reentrant callbacks need a witness in the change-event history owner before claiming general batch-shape coverage. Query write utilities and effect self-dependent disposal remain separate contracts. During a truncate, a ready callback can write to any key after a prior optimistic request. Each subscriber, with or without initial state or a filter, must receive valid messages after the Collection is ready and end at the Collection's rows. A throwing ready callback during sync entry surfaces once the sync function returns, whether it calls markReady or commits a truncate ([round 3 follow-ups](oracle-reviews/2026-10-05-round-3-followups.md)). Callback rollbacks, non-optimistic callback writes, and several writing callbacks remain open ([ready-callback truncate review](oracle-reviews/2026-10-05-truncate-ready-callback-delete.md)). | | Paced mutations | [virtual-clock oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/paced-mutations-oracle.test.ts) | `createPacedMutations` with queue front/back options, bounded waiting capacity at zero and one, cleanup draining in FIFO/LIFO order after a held write, cleanup timing at the configured wait for immediate writes, admitted writes after separate Collection cleanup, debounce and throttle schedules with explicit and omitted options, and first-leading throttle execution at epoch zero. Finite histories compare optimistic rows, returned transaction identity and receipt outcome, execution order and virtual-clock time. Held writes verify queue serialization. Leading-only throttle and debounce witnesses reject skipped calls immediately with their named dropped-call errors, including omitted edges, both edges disabled, and same-row rollback while a prior write is held. Capacity witnesses cover overflow rejection, distinct-key optimistic rollback, same-key admitted-write survival, and in-flight versus waiting admission. Cleanup witnesses check repeated queue cleanup, eventual admitted receipt settlement, direct queue strategy rejection of new callbacks, public post-cleanup rollback with `QueueDisposedError`, pending debounce transactions that wait until the last call's quiet edge after separate Collection cleanup, and a trailing throttle timer that runs at its regular edge after separate Collection cleanup. Deferred custom queue and batch callbacks check compatibility with `void` and `false` execute results, respectively. Frozen options verify factory non-mutation; a mutable option witness checks that debounce uses its construction-time trailing setting. Throwing `onMutate` histories check leading-edge reuse before any row change, failed-row restoration when a pending transaction already has admitted work, eligible leading writes behind a held handler, and throttle pending merges at the throw, start, receipt, and later-timer cuts. New debounce/throttle admission after cleanup, manual commit of a strategy-owned transaction, longer throwing/reentrant bursts, failed persistence, broader capacities and schedules remain outside this owner. | diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index 2a8845ff7a..105a1deaa2 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -13,6 +13,7 @@ import { } from '../../src/operators/groupBy.js' import { output } from '../../src/operators/index.js' import { serializeValue } from '../../src/utils.js' +import { hash } from '../../src/hashing/hash.js' describe(`Operators`, () => { describe(`GroupBy operation`, () => { @@ -626,14 +627,19 @@ describe(`Operators`, () => { // Contract for callers, not a replay witness: the generated groupBy law in // incrementalization-law-oracle.property.test.ts compares JSON values and - // does not cover -0 or Date identity. groupBy consolidates values by their - // hash, which treats -0 as 0 and equal Dates as one value. So after a - // delete, min and max return a value hash-equal to a remaining member, but - // not necessarily the remaining instance; the group is retracted and - // inserted again with that value. A caller that needs the exact remaining - // value, such as the query compiler's group representatives, must keep its - // own exact inputs. A distinct remaining value is returned as is. + // cannot see -0 or Date identity. groupBy's Index merges rows whose whole + // tuple of pre-mapped aggregate inputs hashes equal, and the hash treats -0 + // as 0 and equal Dates as one value. So after a delete, min and max return + // a value hash-equal to a remaining member's, and not necessarily that + // member's instance: when nothing else in the tuple differs, the instance + // that comes back can be the one just deleted, in either direction. An + // aggregate that differs per row (such as the query compiler's exact + // inputs) keeps the rows apart. A caller that needs the exact remaining + // value must therefore keep its own exact inputs, as the compiler's group + // representatives do. A distinct remaining value is returned as is. test(`min and max after a delete return a hash-equal value, not a particular instance`, () => { + // The group's row after the delete, read from the consolidated output: + // whether a hash-equal row is published again is not part of this. const extremes = (first: number | Date, second: number | Date) => { const graph = new D2() const input = graph.newInput<{ @@ -641,7 +647,7 @@ describe(`Operators`, () => { id: number v: number | Date }>() - let latest: { lo: unknown; hi: unknown } | undefined + let current: { lo: unknown; hi: unknown } | undefined input.pipe( groupBy((row) => ({ g: row.g }), { lo: min((row) => row.v), @@ -649,7 +655,7 @@ describe(`Operators`, () => { }), output((message) => { for (const [[, row], multiplicity] of message.getInner()) - if (multiplicity > 0) latest = row as typeof latest + if (multiplicity > 0) current = row as typeof current }), ) graph.finalize() @@ -661,23 +667,34 @@ describe(`Operators`, () => { ]), ) graph.run() - // Only the output of the delete counts. - latest = undefined input.sendData(new MultiSet([[deleted, -1]])) graph.run() - expect(latest, `the delete publishes the group again`).toBeDefined() - return latest! + expect(current, `the group has a row`).toBeDefined() + return current! } - const zero = extremes(-0, 0) - expect(serializeValue(zero.lo)).toBe(serializeValue(0)) - expect(serializeValue(zero.hi)).toBe(serializeValue(0)) - - const kept = new Date(5) - const date = extremes(new Date(5), kept) - expect((date.lo as Date).getTime()).toBe(kept.getTime()) - expect((date.hi as Date).getTime()).toBe(kept.getTime()) - + // The contract: hash-equal, by the hash the Index merges with. + for (const [first, second] of [ + [-0, 0], + [0, -0], + ] as const) { + const zero = extremes(first, second) + expect(hash(zero.lo)).toBe(hash(second)) + expect(hash(zero.hi)).toBe(hash(second)) + } + const deletedDate = new Date(5) + const keptDate = new Date(5) + const date = extremes(deletedDate, keptDate) + expect(hash(date.lo)).toBe(hash(keptDate)) + + // Current behavior, pinned so a db-ivm change alerts the compiler owner: + // the deleted value comes back, not the remaining one. + expect(Object.is(extremes(-0, 0).lo, -0)).toBe(true) + expect(Object.is(extremes(0, -0).lo, 0)).toBe(true) + expect(date.lo).toBe(deletedDate) + expect(date.hi).toBe(deletedDate) + + // A distinct remaining value is returned as is. expect(extremes(1, 2)).toMatchObject({ lo: 2, hi: 2 }) const distinct = new Date(6) const distinctDates = extremes(new Date(5), distinct) diff --git a/packages/db/src/query/compiler/group-by.ts b/packages/db/src/query/compiler/group-by.ts index 1d672acd8f..2d2417c801 100644 --- a/packages/db/src/query/compiler/group-by.ts +++ b/packages/db/src/query/compiler/group-by.ts @@ -430,7 +430,8 @@ export function processGroupBy( } } - // D2 consolidates contributions whose hashes match, and the hash treats -0 + // D2 consolidates contributions whose hashes match (db-ivm records this in + // tests/operators/groupBy.test.ts), and the hash treats -0 // as 0 and equal Dates as one value. A min or max returns one of its // inputs, so each contribution carries the exact key of the value it // compares, as a group value does. A primitive's key is its exact value, so From 9fd81b10a8b8e0b140cd34c2f494ae08a70f0b07 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sat, 10 Oct 2026 14:40:43 -0600 Subject: [PATCH 4/7] test(db-ivm): check min and max after a delete in both insertion orders Split the contract from the pinned current behavior, read the group's row by integrating output multiplicities, check the max's hash for Dates, and show that a per-row aggregate input returns the remaining instance. Co-authored-by: Isaac --- docs/contributing/oracle-coverage.md | 2 +- .../db-ivm/tests/operators/groupBy.test.ts | 179 +++++++++++------- packages/db/src/query/compiler/group-by.ts | 2 +- 3 files changed, 114 insertions(+), 69 deletions(-) diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index 6fe188f222..f44cd223ce 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -241,7 +241,7 @@ comment and the current API/architecture contract before extending its model. | Surface | Primary executable owners | Independent judgment and important limit | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact inputs. It pins the current behavior too: the deleted instance comes back, in either direction. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | +| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact-value keys. It checks both insertion orders, reads the group's row by integrating output multiplicities, and shows that an aggregate input that differs per row returns the remaining instance. It pins the current behavior separately: when the deleted row was added first, its instance comes back, for -0/0 in either direction and for an equal Date. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | | Includes and publication | [cross-formulation](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts), [temporal](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-temporal-oracle.test.ts), [Collection includes](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-collection-oracle.property.test.ts), [architecture and complete suite map](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/ARCHITECTURE.md#executable-contracts) | Per-parent/flat-join/partition relations, callback-time rows, nested values, and route histories. Fragmented lazy-demand consolidation covers delta growth and successful, rejected/retried, and obsolete replacements at the Collection request boundary; it observes exact key unions and abort checkpoints. A compiled-includes fixture makes adapter unload evict owned child rows and proves a rejected union cannot remove established public child rows. Retry after that terminal live-query failure remains covered at the acquisition boundary; no automatic in-place recovery is claimed. Observe raw promised order; fresh queries do not establish continuous publication safety. | | Collection lifecycle | [mutation startup](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-mutation-startup-oracle.test.ts), [change-event history](https://github.com/TanStack/db/blob/main/packages/db/tests/change-event-history-oracle.test.ts), [sync publication reentrancy](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-sync-reentrancy-oracle.test.ts), [history](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-history-oracle.property.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-publication-oracle.property.test.ts), [replay](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-replay-oracle.property.test.ts), [effect disposal](https://github.com/TanStack/db/blob/main/packages/db/tests/effect-disposal-oracle.test.ts), [PR #1902 review](oracle-reviews/pr-1902-change-event-history.md), [batch-order decision review](oracle-reviews/change-event-batch-order.md), [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md) | Core Collection `insert`/`update`/`delete` admission while `startSync:false` is idle; complete public-row/change-message agreement across bounded one- and two-key histories plus replayable four-key campaigns, where every message must be valid for a strict consumer (insert only for an absent key, update or delete only for a present one; [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md)); a two-key deferred sync history checks each key's causal insert/update/delete trace while allowing any cross-key interleaving. Reversing all deferred messages fails that check; reversing each sync transaction's distinct-key messages passes. Queued duplicate admission and cancellation, ownership and phase histories, exact caller/error/publication evidence, and late completion and restart have separate witnesses. Generated deferred histories with cancellation or reentrant callbacks need a witness in the change-event history owner before claiming general batch-shape coverage. Query write utilities and effect self-dependent disposal remain separate contracts. During a truncate, a ready callback can write to any key after a prior optimistic request. Each subscriber, with or without initial state or a filter, must receive valid messages after the Collection is ready and end at the Collection's rows. A throwing ready callback during sync entry surfaces once the sync function returns, whether it calls markReady or commits a truncate ([round 3 follow-ups](oracle-reviews/2026-10-05-round-3-followups.md)). Callback rollbacks, non-optimistic callback writes, and several writing callbacks remain open ([ready-callback truncate review](oracle-reviews/2026-10-05-truncate-ready-callback-delete.md)). | | Paced mutations | [virtual-clock oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/paced-mutations-oracle.test.ts) | `createPacedMutations` with queue front/back options, bounded waiting capacity at zero and one, cleanup draining in FIFO/LIFO order after a held write, cleanup timing at the configured wait for immediate writes, admitted writes after separate Collection cleanup, debounce and throttle schedules with explicit and omitted options, and first-leading throttle execution at epoch zero. Finite histories compare optimistic rows, returned transaction identity and receipt outcome, execution order and virtual-clock time. Held writes verify queue serialization. Leading-only throttle and debounce witnesses reject skipped calls immediately with their named dropped-call errors, including omitted edges, both edges disabled, and same-row rollback while a prior write is held. Capacity witnesses cover overflow rejection, distinct-key optimistic rollback, same-key admitted-write survival, and in-flight versus waiting admission. Cleanup witnesses check repeated queue cleanup, eventual admitted receipt settlement, direct queue strategy rejection of new callbacks, public post-cleanup rollback with `QueueDisposedError`, pending debounce transactions that wait until the last call's quiet edge after separate Collection cleanup, and a trailing throttle timer that runs at its regular edge after separate Collection cleanup. Deferred custom queue and batch callbacks check compatibility with `void` and `false` execute results, respectively. Frozen options verify factory non-mutation; a mutable option witness checks that debounce uses its construction-time trailing setting. Throwing `onMutate` histories check leading-edge reuse before any row change, failed-row restoration when a pending transaction already has admitted work, eligible leading writes behind a held handler, and throttle pending merges at the throw, start, receipt, and later-timer cuts. New debounce/throttle admission after cleanup, manual commit of a strategy-owned transaction, longer throwing/reentrant bursts, failed persistence, broader capacities and schedules remain outside this owner. | diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index 105a1deaa2..5948f2e0fd 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -627,79 +627,124 @@ describe(`Operators`, () => { // Contract for callers, not a replay witness: the generated groupBy law in // incrementalization-law-oracle.property.test.ts compares JSON values and - // cannot see -0 or Date identity. groupBy's Index merges rows whose whole - // tuple of pre-mapped aggregate inputs hashes equal, and the hash treats -0 - // as 0 and equal Dates as one value. So after a delete, min and max return - // a value hash-equal to a remaining member's, and not necessarily that - // member's instance: when nothing else in the tuple differs, the instance - // that comes back can be the one just deleted, in either direction. An - // aggregate that differs per row (such as the query compiler's exact - // inputs) keeps the rows apart. A caller that needs the exact remaining - // value must therefore keep its own exact inputs, as the compiler's group - // representatives do. A distinct remaining value is returned as is. - test(`min and max after a delete return a hash-equal value, not a particular instance`, () => { - // The group's row after the delete, read from the consolidated output: - // whether a hash-equal row is published again is not part of this. - const extremes = (first: number | Date, second: number | Date) => { - const graph = new D2() - const input = graph.newInput<{ - g: string - id: number - v: number | Date - }>() - let current: { lo: unknown; hi: unknown } | undefined - input.pipe( - groupBy((row) => ({ g: row.g }), { - lo: min((row) => row.v), - hi: max((row) => row.v), - }), - output((message) => { - for (const [[, row], multiplicity] of message.getInner()) - if (multiplicity > 0) current = row as typeof current - }), - ) - graph.finalize() - const deleted = { g: `x`, id: 1, v: first } - input.sendData( - new MultiSet([ - [deleted, 1], - [{ g: `x`, id: 2, v: second }, 1], - ]), - ) - graph.run() - input.sendData(new MultiSet([[deleted, -1]])) - graph.run() - expect(current, `the group has a row`).toBeDefined() - return current! - } - - // The contract: hash-equal, by the hash the Index merges with. - for (const [first, second] of [ - [-0, 0], - [0, -0], - ] as const) { - const zero = extremes(first, second) - expect(hash(zero.lo)).toBe(hash(second)) - expect(hash(zero.hi)).toBe(hash(second)) + // cannot see -0 or Date identity. groupBy's reduce keeps a group's + // contributions in an Index keyed by the hash of each row's record of + // pre-mapped aggregate inputs. Rows whose records hash equal share one + // entry, which keeps one instance and sums their multiplicities. The hash + // treats -0 as 0 and equal Dates as one value. So after a delete, min and + // max return a value hash-equal to a remaining member's, not necessarily + // that member's instance. An aggregate input that differs per row, such as + // the query compiler's exact-value keys, keeps the records apart. A caller + // that needs the exact remaining value must keep its own exact inputs, as + // the compiler's group representatives do. A distinct remaining value is + // returned as is. + // + // The group's row after the delete is integrated from the output's + // multiplicities by hash, keeping the instance of the latest insertion: a + // retraction is a new object. Whether a hash-equal row is published again + // is not part of this. + const extremes = ( + deletedValue: number | Date, + keptValue: number | Date, + options: { deletedFirst?: boolean; perRowInput?: boolean } = {}, + ) => { + const { deletedFirst = true, perRowInput = false } = options + const graph = new D2() + const input = graph.newInput<{ + g: string + id: number + v: number | Date + }>() + const rows = new Map() + input.pipe( + groupBy((row) => ({ g: row.g }), { + lo: min((row) => row.v), + hi: max((row) => row.v), + ...(perRowInput ? { id: max((row) => row.id) } : {}), + }), + output((message) => { + for (const [[, row], multiplicity] of message.getInner()) { + const entry = rows.get(hash(row)) ?? { row, count: 0 } + entry.count += multiplicity + if (multiplicity > 0) entry.row = row + rows.set(hash(row), entry) + } + }), + ) + graph.finalize() + const deleted = { g: `x`, id: 1, v: deletedValue } + const kept = { g: `x`, id: 2, v: keptValue } + input.sendData( + new MultiSet( + deletedFirst + ? [ + [deleted, 1], + [kept, 1], + ] + : [ + [kept, 1], + [deleted, 1], + ], + ), + ) + graph.run() + input.sendData(new MultiSet([[deleted, -1]])) + graph.run() + const present = [...rows.values()].filter((entry) => entry.count !== 0) + expect( + present.map((entry) => entry.count), + `one row for the group`, + ).toEqual([1]) + return present[0]!.row as { lo: unknown; hi: unknown } + } + + test(`min and max after a delete return a value hash-equal to the remaining one`, () => { + for (const deletedFirst of [true, false]) { + for (const [deleted, kept] of [ + [-0, 0], + [0, -0], + ] as const) { + const zero = extremes(deleted, kept, { deletedFirst }) + expect(hash(zero.lo)).toBe(hash(kept)) + expect(hash(zero.hi)).toBe(hash(kept)) + } + const keptDate = new Date(5) + const date = extremes(new Date(5), keptDate, { deletedFirst }) + expect(hash(date.lo)).toBe(hash(keptDate)) + expect(hash(date.hi)).toBe(hash(keptDate)) + + // A distinct remaining value is returned as is. + expect(extremes(1, 2, { deletedFirst })).toMatchObject({ lo: 2, hi: 2 }) + const distinct = new Date(6) + const distinctDates = extremes(new Date(5), distinct, { deletedFirst }) + expect(distinctDates.lo).toBe(distinct) + expect(distinctDates.hi).toBe(distinct) + + // An input that differs per row keeps the records apart, so the + // remaining instance comes back. + const exactDate = new Date(5) + const exact = extremes(new Date(5), exactDate, { + deletedFirst, + perRowInput: true, + }) + expect(exact.lo).toBe(exactDate) + expect(exact.hi).toBe(exactDate) + expect( + Object.is(extremes(-0, 0, { deletedFirst, perRowInput: true }).lo, 0), + ).toBe(true) } - const deletedDate = new Date(5) - const keptDate = new Date(5) - const date = extremes(deletedDate, keptDate) - expect(hash(date.lo)).toBe(hash(keptDate)) + }) - // Current behavior, pinned so a db-ivm change alerts the compiler owner: - // the deleted value comes back, not the remaining one. + test(`min and max can return the deleted instance of a hash-equal value`, () => { + // Current behavior, pinned so a db-ivm change alerts the compiler owner. + // When the deleted row was added first, its instance is the one the + // shared entry keeps. expect(Object.is(extremes(-0, 0).lo, -0)).toBe(true) expect(Object.is(extremes(0, -0).lo, 0)).toBe(true) + const deletedDate = new Date(5) + const date = extremes(deletedDate, new Date(5)) expect(date.lo).toBe(deletedDate) expect(date.hi).toBe(deletedDate) - - // A distinct remaining value is returned as is. - expect(extremes(1, 2)).toMatchObject({ lo: 2, hi: 2 }) - const distinct = new Date(6) - const distinctDates = extremes(new Date(5), distinct) - expect(distinctDates.lo).toBe(distinct) - expect(distinctDates.hi).toBe(distinct) }) // These are readable replay witnesses. The generated groupBy law lives in diff --git a/packages/db/src/query/compiler/group-by.ts b/packages/db/src/query/compiler/group-by.ts index 2d2417c801..4d124ddbf5 100644 --- a/packages/db/src/query/compiler/group-by.ts +++ b/packages/db/src/query/compiler/group-by.ts @@ -431,7 +431,7 @@ export function processGroupBy( } // D2 consolidates contributions whose hashes match (db-ivm records this in - // tests/operators/groupBy.test.ts), and the hash treats -0 + // packages/db-ivm/tests/operators/groupBy.test.ts), and the hash treats -0 // as 0 and equal Dates as one value. A min or max returns one of its // inputs, so each contribution carries the exact key of the value it // compares, as a group value does. A primitive's key is its exact value, so From d86af31d22cf7eac11e206aef8f1474d2fe3b5e1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 11 Oct 2026 08:25:24 -0600 Subject: [PATCH 5/7] test(db-ivm): pin the deleted min/max instance in both insertion orders The deleted instance comes back whichever row was added first: a shared hash entry keeps the instance it was last given, and the delete's retraction is last. The pinned test now covers both orders, the prose names that mechanism and states which contract cases a wrong value can fail, and the hash import uses the hashing entry point. Co-authored-by: Isaac --- docs/contributing/oracle-coverage.md | 2 +- .../db-ivm/tests/operators/groupBy.test.ts | 29 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index f44cd223ce..94e405fcb0 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -241,7 +241,7 @@ comment and the current API/architecture contract before extending its model. | Surface | Primary executable owners | Independent judgment and important limit | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact-value keys. It checks both insertion orders, reads the group's row by integrating output multiplicities, and shows that an aggregate input that differs per row returns the remaining instance. It pins the current behavior separately: when the deleted row was added first, its instance comes back, for -0/0 in either direction and for an equal Date. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | +| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact-value keys. It checks both insertion orders, reads the group's row by integrating output multiplicities, and shows that an aggregate input that differs per row returns the remaining instance. It pins the current behavior separately: the delete's retraction is the last record a shared hash entry gets, so the deleted instance comes back in either insertion order, for -0/0 in either direction and for an equal Date. For -0/0 and equal Dates the contract assertions only show hash equality, which is all the contract promises. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | | Includes and publication | [cross-formulation](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts), [temporal](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-temporal-oracle.test.ts), [Collection includes](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-collection-oracle.property.test.ts), [architecture and complete suite map](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/ARCHITECTURE.md#executable-contracts) | Per-parent/flat-join/partition relations, callback-time rows, nested values, and route histories. Fragmented lazy-demand consolidation covers delta growth and successful, rejected/retried, and obsolete replacements at the Collection request boundary; it observes exact key unions and abort checkpoints. A compiled-includes fixture makes adapter unload evict owned child rows and proves a rejected union cannot remove established public child rows. Retry after that terminal live-query failure remains covered at the acquisition boundary; no automatic in-place recovery is claimed. Observe raw promised order; fresh queries do not establish continuous publication safety. | | Collection lifecycle | [mutation startup](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-mutation-startup-oracle.test.ts), [change-event history](https://github.com/TanStack/db/blob/main/packages/db/tests/change-event-history-oracle.test.ts), [sync publication reentrancy](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-sync-reentrancy-oracle.test.ts), [history](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-history-oracle.property.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-publication-oracle.property.test.ts), [replay](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-replay-oracle.property.test.ts), [effect disposal](https://github.com/TanStack/db/blob/main/packages/db/tests/effect-disposal-oracle.test.ts), [PR #1902 review](oracle-reviews/pr-1902-change-event-history.md), [batch-order decision review](oracle-reviews/change-event-batch-order.md), [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md) | Core Collection `insert`/`update`/`delete` admission while `startSync:false` is idle; complete public-row/change-message agreement across bounded one- and two-key histories plus replayable four-key campaigns, where every message must be valid for a strict consumer (insert only for an absent key, update or delete only for a present one; [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md)); a two-key deferred sync history checks each key's causal insert/update/delete trace while allowing any cross-key interleaving. Reversing all deferred messages fails that check; reversing each sync transaction's distinct-key messages passes. Queued duplicate admission and cancellation, ownership and phase histories, exact caller/error/publication evidence, and late completion and restart have separate witnesses. Generated deferred histories with cancellation or reentrant callbacks need a witness in the change-event history owner before claiming general batch-shape coverage. Query write utilities and effect self-dependent disposal remain separate contracts. During a truncate, a ready callback can write to any key after a prior optimistic request. Each subscriber, with or without initial state or a filter, must receive valid messages after the Collection is ready and end at the Collection's rows. A throwing ready callback during sync entry surfaces once the sync function returns, whether it calls markReady or commits a truncate ([round 3 follow-ups](oracle-reviews/2026-10-05-round-3-followups.md)). Callback rollbacks, non-optimistic callback writes, and several writing callbacks remain open ([ready-callback truncate review](oracle-reviews/2026-10-05-truncate-ready-callback-delete.md)). | | Paced mutations | [virtual-clock oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/paced-mutations-oracle.test.ts) | `createPacedMutations` with queue front/back options, bounded waiting capacity at zero and one, cleanup draining in FIFO/LIFO order after a held write, cleanup timing at the configured wait for immediate writes, admitted writes after separate Collection cleanup, debounce and throttle schedules with explicit and omitted options, and first-leading throttle execution at epoch zero. Finite histories compare optimistic rows, returned transaction identity and receipt outcome, execution order and virtual-clock time. Held writes verify queue serialization. Leading-only throttle and debounce witnesses reject skipped calls immediately with their named dropped-call errors, including omitted edges, both edges disabled, and same-row rollback while a prior write is held. Capacity witnesses cover overflow rejection, distinct-key optimistic rollback, same-key admitted-write survival, and in-flight versus waiting admission. Cleanup witnesses check repeated queue cleanup, eventual admitted receipt settlement, direct queue strategy rejection of new callbacks, public post-cleanup rollback with `QueueDisposedError`, pending debounce transactions that wait until the last call's quiet edge after separate Collection cleanup, and a trailing throttle timer that runs at its regular edge after separate Collection cleanup. Deferred custom queue and batch callbacks check compatibility with `void` and `false` execute results, respectively. Frozen options verify factory non-mutation; a mutable option witness checks that debounce uses its construction-time trailing setting. Throwing `onMutate` histories check leading-edge reuse before any row change, failed-row restoration when a pending transaction already has admitted work, eligible leading writes behind a held handler, and throttle pending merges at the throw, start, receipt, and later-timer cuts. New debounce/throttle admission after cleanup, manual commit of a strategy-owned transaction, longer throwing/reentrant bursts, failed persistence, broader capacities and schedules remain outside this owner. | diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index 5948f2e0fd..75c147a50a 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -13,7 +13,7 @@ import { } from '../../src/operators/groupBy.js' import { output } from '../../src/operators/index.js' import { serializeValue } from '../../src/utils.js' -import { hash } from '../../src/hashing/hash.js' +import { hash } from '../../src/hashing/index.js' describe(`Operators`, () => { describe(`GroupBy operation`, () => { @@ -630,14 +630,18 @@ describe(`Operators`, () => { // cannot see -0 or Date identity. groupBy's reduce keeps a group's // contributions in an Index keyed by the hash of each row's record of // pre-mapped aggregate inputs. Rows whose records hash equal share one - // entry, which keeps one instance and sums their multiplicities. The hash + // entry, which sums their multiplicities and keeps the instance it was + // last given, a retraction's included. The hash // treats -0 as 0 and equal Dates as one value. So after a delete, min and // max return a value hash-equal to a remaining member's, not necessarily // that member's instance. An aggregate input that differs per row, such as // the query compiler's exact-value keys, keeps the records apart. A caller // that needs the exact remaining value must keep its own exact inputs, as // the compiler's group representatives do. A distinct remaining value is - // returned as is. + // returned as is. For -0/0 and equal Dates, hash equality is all the + // contract promises, so those cases only show the result stays in the + // remaining value's class; the distinct and per-row cases are the ones a + // wrong value fails. // // The group's row after the delete is integrated from the output's // multiplicities by hash, keeping the instance of the latest insertion: a @@ -737,14 +741,17 @@ describe(`Operators`, () => { test(`min and max can return the deleted instance of a hash-equal value`, () => { // Current behavior, pinned so a db-ivm change alerts the compiler owner. - // When the deleted row was added first, its instance is the one the - // shared entry keeps. - expect(Object.is(extremes(-0, 0).lo, -0)).toBe(true) - expect(Object.is(extremes(0, -0).lo, 0)).toBe(true) - const deletedDate = new Date(5) - const date = extremes(deletedDate, new Date(5)) - expect(date.lo).toBe(deletedDate) - expect(date.hi).toBe(deletedDate) + // The delete's retraction is the last record the shared entry gets, so + // its instance, the deleted value, is the one that comes back, in either + // insertion order. + for (const deletedFirst of [true, false]) { + expect(Object.is(extremes(-0, 0, { deletedFirst }).lo, -0)).toBe(true) + expect(Object.is(extremes(0, -0, { deletedFirst }).lo, 0)).toBe(true) + const deletedDate = new Date(5) + const date = extremes(deletedDate, new Date(5), { deletedFirst }) + expect(date.lo).toBe(deletedDate) + expect(date.hi).toBe(deletedDate) + } }) // These are readable replay witnesses. The generated groupBy law lives in From 770f94f9a113041668955fd68ac3a06908fa970f Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 11 Oct 2026 10:41:20 -0600 Subject: [PATCH 6/7] test(db-ivm): check min/max after deleting the maximum and both zero directions Mirror cases now delete the maximum, and the per-row cases check -0/0 in both directions for min and max. The contract comment states the positive multiplicity condition and what the compiler's exact-value keys separate, and the compiler comment names the contract instead of a test path. Co-authored-by: Isaac --- docs/contributing/oracle-coverage.md | 2 +- .../db-ivm/tests/operators/groupBy.test.ts | 31 ++++++++++++++----- packages/db/src/query/compiler/group-by.ts | 5 +-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/contributing/oracle-coverage.md b/docs/contributing/oracle-coverage.md index 94e405fcb0..b9305be18d 100644 --- a/docs/contributing/oracle-coverage.md +++ b/docs/contributing/oracle-coverage.md @@ -241,7 +241,7 @@ comment and the current API/architecture contract before extending its model. | Surface | Primary executable owners | Independent judgment and important limit | | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact-value keys. It checks both insertion orders, reads the group's row by integrating output multiplicities, and shows that an aggregate input that differs per row returns the remaining instance. It pins the current behavior separately: the delete's retraction is the last record a shared hash entry gets, so the deleted instance comes back in either insertion order, for -0/0 in either direction and for an equal Date. For -0/0 and equal Dates the contract assertions only show hash equality, which is all the contract promises. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | +| Ordered relations and BTree | [top-K relation oracle](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topk-relation-oracle.test.ts), [fractional-index window moves](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/operators/topKWithFractionalIndex-oracle.test.ts), [BTree/Map](https://github.com/TanStack/db/blob/main/packages/db/tests/btree-map-oracle.test.ts), [incrementalization laws](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/incrementalization-law-oracle.property.test.ts) | Independent ordered relations and cumulative signed output. Top-K consolidation compares same-key values without hashing, including cyclic replacements and fresh transient cancellation. The fractional-index test checks an empty output message for a window move after all rows retract; it does not prove downstream lazy acquisition. Other hash-based operators retain hashing's declared domain. Algebra does not specify client readiness. The BTree/Map oracle checks point operations, neighbor pairs, and full, partial, boundary, exclusive-high, and empty range scans for integer keys and node sizes 4–8. It does not check balance or space, and it assumes consistent comparators ([code-weight BTree review](oracle-reviews/code-weight-btree-trim.md)). The generated groupBy law compares JSON values; a pinned contract test in `packages/db-ivm/tests/operators/groupBy.test.ts` records that after a delete, `min` and `max` return a value hash-equal to a remaining member, not necessarily the remaining instance (`-0` for `0`, an equal Date), which is why the query compiler keeps its own exact-value keys. It checks both insertion orders, reads the group's row by integrating output multiplicities, deletes the minimum and the maximum, and shows that an aggregate input that tells the rows apart returns the remaining instance, for -0/0 in both directions and for both `min` and `max`. The contract holds while every member has positive multiplicity. It pins the current behavior separately: the delete's retraction is the last record a shared hash entry gets, so the deleted instance comes back in either insertion order, for -0/0 in either direction and for an equal Date. For -0/0 and equal Dates the contract assertions only show hash equality, which is all the contract promises. Open: the incrementalization law's value policy compares JSON and clones by `structuredClone`, so no generated history sees -0 or Date identity; a value-identity policy with -0/0 and equal-time Dates would generalize this pinned contract. | | Includes and publication | [cross-formulation](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-cross-formulation-oracle.property.test.ts), [temporal](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-temporal-oracle.test.ts), [Collection includes](https://github.com/TanStack/db/blob/main/packages/db/tests/query/includes-collection-oracle.property.test.ts), [architecture and complete suite map](https://github.com/TanStack/db/blob/main/packages/db/src/query/live/ARCHITECTURE.md#executable-contracts) | Per-parent/flat-join/partition relations, callback-time rows, nested values, and route histories. Fragmented lazy-demand consolidation covers delta growth and successful, rejected/retried, and obsolete replacements at the Collection request boundary; it observes exact key unions and abort checkpoints. A compiled-includes fixture makes adapter unload evict owned child rows and proves a rejected union cannot remove established public child rows. Retry after that terminal live-query failure remains covered at the acquisition boundary; no automatic in-place recovery is claimed. Observe raw promised order; fresh queries do not establish continuous publication safety. | | Collection lifecycle | [mutation startup](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-mutation-startup-oracle.test.ts), [change-event history](https://github.com/TanStack/db/blob/main/packages/db/tests/change-event-history-oracle.test.ts), [sync publication reentrancy](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-sync-reentrancy-oracle.test.ts), [history](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-history-oracle.property.test.ts), [publication](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-lifecycle-publication-oracle.property.test.ts), [replay](https://github.com/TanStack/db/blob/main/packages/db/tests/collection-subscription-replay-oracle.property.test.ts), [effect disposal](https://github.com/TanStack/db/blob/main/packages/db/tests/effect-disposal-oracle.test.ts), [PR #1902 review](oracle-reviews/pr-1902-change-event-history.md), [batch-order decision review](oracle-reviews/change-event-batch-order.md), [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md) | Core Collection `insert`/`update`/`delete` admission while `startSync:false` is idle; complete public-row/change-message agreement across bounded one- and two-key histories plus replayable four-key campaigns, where every message must be valid for a strict consumer (insert only for an absent key, update or delete only for a present one; [absent-key insert review](oracle-reviews/2026-10-02-insert-for-absent-key.md)); a two-key deferred sync history checks each key's causal insert/update/delete trace while allowing any cross-key interleaving. Reversing all deferred messages fails that check; reversing each sync transaction's distinct-key messages passes. Queued duplicate admission and cancellation, ownership and phase histories, exact caller/error/publication evidence, and late completion and restart have separate witnesses. Generated deferred histories with cancellation or reentrant callbacks need a witness in the change-event history owner before claiming general batch-shape coverage. Query write utilities and effect self-dependent disposal remain separate contracts. During a truncate, a ready callback can write to any key after a prior optimistic request. Each subscriber, with or without initial state or a filter, must receive valid messages after the Collection is ready and end at the Collection's rows. A throwing ready callback during sync entry surfaces once the sync function returns, whether it calls markReady or commits a truncate ([round 3 follow-ups](oracle-reviews/2026-10-05-round-3-followups.md)). Callback rollbacks, non-optimistic callback writes, and several writing callbacks remain open ([ready-callback truncate review](oracle-reviews/2026-10-05-truncate-ready-callback-delete.md)). | | Paced mutations | [virtual-clock oracle](https://github.com/TanStack/db/blob/main/packages/db/tests/paced-mutations-oracle.test.ts) | `createPacedMutations` with queue front/back options, bounded waiting capacity at zero and one, cleanup draining in FIFO/LIFO order after a held write, cleanup timing at the configured wait for immediate writes, admitted writes after separate Collection cleanup, debounce and throttle schedules with explicit and omitted options, and first-leading throttle execution at epoch zero. Finite histories compare optimistic rows, returned transaction identity and receipt outcome, execution order and virtual-clock time. Held writes verify queue serialization. Leading-only throttle and debounce witnesses reject skipped calls immediately with their named dropped-call errors, including omitted edges, both edges disabled, and same-row rollback while a prior write is held. Capacity witnesses cover overflow rejection, distinct-key optimistic rollback, same-key admitted-write survival, and in-flight versus waiting admission. Cleanup witnesses check repeated queue cleanup, eventual admitted receipt settlement, direct queue strategy rejection of new callbacks, public post-cleanup rollback with `QueueDisposedError`, pending debounce transactions that wait until the last call's quiet edge after separate Collection cleanup, and a trailing throttle timer that runs at its regular edge after separate Collection cleanup. Deferred custom queue and batch callbacks check compatibility with `void` and `false` execute results, respectively. Frozen options verify factory non-mutation; a mutable option witness checks that debounce uses its construction-time trailing setting. Throwing `onMutate` histories check leading-edge reuse before any row change, failed-row restoration when a pending transaction already has admitted work, eligible leading writes behind a held handler, and throttle pending merges at the throw, start, receipt, and later-timer cuts. New debounce/throttle admission after cleanup, manual commit of a strategy-owned transaction, longer throwing/reentrant bursts, failed persistence, broader capacities and schedules remain outside this owner. | diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index 75c147a50a..af563c6c96 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -634,10 +634,11 @@ describe(`Operators`, () => { // last given, a retraction's included. The hash // treats -0 as 0 and equal Dates as one value. So after a delete, min and // max return a value hash-equal to a remaining member's, not necessarily - // that member's instance. An aggregate input that differs per row, such as - // the query compiler's exact-value keys, keeps the records apart. A caller - // that needs the exact remaining value must keep its own exact inputs, as - // the compiler's group representatives do. A distinct remaining value is + // that member's instance. This holds while every member has positive + // multiplicity: min and max do not look at multiplicities. An aggregate + // input that tells the rows apart keeps their records apart. The query + // compiler adds one for min and max: an exact-value key that separates -0 + // from 0 and gives each object its row key. A distinct remaining value is // returned as is. For -0/0 and equal Dates, hash equality is all the // contract promises, so those cases only show the result stays in the // remaining value's class; the distinct and per-row cases are the ones a @@ -717,12 +718,18 @@ describe(`Operators`, () => { expect(hash(date.lo)).toBe(hash(keptDate)) expect(hash(date.hi)).toBe(hash(keptDate)) - // A distinct remaining value is returned as is. + // A distinct remaining value is returned as is, whether the delete + // removes the minimum or the maximum. expect(extremes(1, 2, { deletedFirst })).toMatchObject({ lo: 2, hi: 2 }) + expect(extremes(2, 1, { deletedFirst })).toMatchObject({ lo: 1, hi: 1 }) const distinct = new Date(6) const distinctDates = extremes(new Date(5), distinct, { deletedFirst }) expect(distinctDates.lo).toBe(distinct) expect(distinctDates.hi).toBe(distinct) + const earlier = new Date(5) + const earlierDates = extremes(new Date(6), earlier, { deletedFirst }) + expect(earlierDates.lo).toBe(earlier) + expect(earlierDates.hi).toBe(earlier) // An input that differs per row keeps the records apart, so the // remaining instance comes back. @@ -733,9 +740,17 @@ describe(`Operators`, () => { }) expect(exact.lo).toBe(exactDate) expect(exact.hi).toBe(exactDate) - expect( - Object.is(extremes(-0, 0, { deletedFirst, perRowInput: true }).lo, 0), - ).toBe(true) + for (const [deleted, kept] of [ + [-0, 0], + [0, -0], + ] as const) { + const zero = extremes(deleted, kept, { + deletedFirst, + perRowInput: true, + }) + expect(Object.is(zero.lo, kept)).toBe(true) + expect(Object.is(zero.hi, kept)).toBe(true) + } } }) diff --git a/packages/db/src/query/compiler/group-by.ts b/packages/db/src/query/compiler/group-by.ts index 4d124ddbf5..bd3fef5cb0 100644 --- a/packages/db/src/query/compiler/group-by.ts +++ b/packages/db/src/query/compiler/group-by.ts @@ -430,8 +430,9 @@ export function processGroupBy( } } - // D2 consolidates contributions whose hashes match (db-ivm records this in - // packages/db-ivm/tests/operators/groupBy.test.ts), and the hash treats -0 + // D2 consolidates contributions whose hashes match, keeping the instance it + // was last given (db-ivm's groupBy min/max contract test records this), + // and the hash treats -0 // as 0 and equal Dates as one value. A min or max returns one of its // inputs, so each contribution carries the exact key of the value it // compares, as a group value does. A primitive's key is its exact value, so From a8108d51213ee7c3ceb6f02c3c6a5d01630ff52e Mon Sep 17 00:00:00 2001 From: Isaac Date: Sun, 11 Oct 2026 18:23:37 -0600 Subject: [PATCH 7/7] test(db-ivm): pin max as well as min for the deleted zero instance The compiler comment now calls the last-given instance pinned current behavior, not part of the contract. Co-authored-by: Isaac --- .../db-ivm/tests/operators/groupBy.test.ts | 9 ++++++-- packages/db/src/query/compiler/group-by.ts | 21 +++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/db-ivm/tests/operators/groupBy.test.ts b/packages/db-ivm/tests/operators/groupBy.test.ts index af563c6c96..037ab7f8cf 100644 --- a/packages/db-ivm/tests/operators/groupBy.test.ts +++ b/packages/db-ivm/tests/operators/groupBy.test.ts @@ -760,8 +760,13 @@ describe(`Operators`, () => { // its instance, the deleted value, is the one that comes back, in either // insertion order. for (const deletedFirst of [true, false]) { - expect(Object.is(extremes(-0, 0, { deletedFirst }).lo, -0)).toBe(true) - expect(Object.is(extremes(0, -0, { deletedFirst }).lo, 0)).toBe(true) + for (const deleted of [-0, 0]) { + const zero = extremes(deleted, deleted === 0 ? -0 : 0, { + deletedFirst, + }) + expect(Object.is(zero.lo, deleted)).toBe(true) + expect(Object.is(zero.hi, deleted)).toBe(true) + } const deletedDate = new Date(5) const date = extremes(deletedDate, new Date(5), { deletedFirst }) expect(date.lo).toBe(deletedDate) diff --git a/packages/db/src/query/compiler/group-by.ts b/packages/db/src/query/compiler/group-by.ts index bd3fef5cb0..92830888eb 100644 --- a/packages/db/src/query/compiler/group-by.ts +++ b/packages/db/src/query/compiler/group-by.ts @@ -430,17 +430,16 @@ export function processGroupBy( } } - // D2 consolidates contributions whose hashes match, keeping the instance it - // was last given (db-ivm's groupBy min/max contract test records this), - // and the hash treats -0 - // as 0 and equal Dates as one value. A min or max returns one of its - // inputs, so each contribution carries the exact key of the value it - // compares, as a group value does. A primitive's key is its exact value, so - // equal primitives still consolidate. An object's key holds its row key, so - // a retraction cancels its insert even when the argument builds a new - // instance each time, and a merged contribution never keeps a deleted - // row's instance. A sum, avg, or count only adds its coerced numbers, so - // merging equal inputs cannot change its result. + // D2 consolidates contributions whose hashes match, and the hash treats -0 as 0 + // and equal Dates as one value. Today it keeps the instance it was last given; + // db-ivm's groupBy tests pin that as current behavior, not as a promise. A min + // or max returns one of its inputs, so each contribution carries the exact key + // of the value it compares, as a group value does. A primitive's key is its + // exact value, so equal primitives still consolidate. An object's key holds its + // row key, so a retraction cancels its insert even when the argument builds a + // new instance each time, and a merged contribution never keeps a deleted row's + // instance. A sum, avg, or count only adds its coerced numbers, so merging + // equal inputs cannot change its result. const exactInputs: Array<(entry: [string, NamespacedRow]) => unknown> = [] const addAggregate = (alias: string, aggExpr: Aggregate) => { aggregates[alias] = getAggregateFunction(aggExpr)