fix(incremental): pushdown takes the source alias, refuses cell edits, shares Eager's reads and pins stem order and refresh snapshots (improvise-rnak)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (dbsp-sources)
2026-10-10 19:06:11 -07:00
committed by Edward Langley
parent 00cc83281e
commit 53868bb4d6
8 changed files with 359 additions and 36 deletions
+24 -1
View File
@@ -458,7 +458,30 @@ change log, no stems. These are the readings taken.
same thing.
5. **`define(edit)` takes a formula or an aggregation edit and refreshes.** A cell edit is the source's:
`Model::set_cell` refuses it in a debug build.
6. **What is and is not proven.** The tests run against `Remote` (`tests/support/remote.rs`), a scanning
6. **What the differential proves, and what it does not.** `value`, `raw_fold`, `covered` and `total` are
the same four functions for `Eager` and `Pushdown` (`reads.rs`; they were copies, and are shared now),
so a Pushdown-against-Eager differential cannot see a mistake in them. `Pushdown::members` repeats
`Eager::group_holds` in a different form (a span list against a predicate), and that repetition
is compared. What the differential proves is the **source's** semantics: that `summarize`, `count`
and `distinct_without` give, for every select the evaluator and the views make, the answers a
`DataStore` gives. The evaluator over a source is proven equal to the evaluator over the owned
store in `improvise-core` (`over_tests`). Nothing else is claimed.
7. **A refresh wants one snapshot.** `refresh` asks `distinct_without` and then folds, so a source that
changes between the queries can serve them from different revisions. The passes heal what they
can: a write between two folds is read again by the next pass, and the cache ends equal to the new
state's values (`writes_between_two_queries_of_a_refresh_are_healed_by_the_fixed_point`). A stem
that appears after the stem list was taken has no cell until the next refresh
(`a_stem_that_appears_between_the_stem_list_and_the_folds_waits_for_the_next_refresh`). A real source
should serve a refresh from one read transaction; nothing here can ask it to.
8. **The order of the stems is part of the contract.** The passes of the fixed point visit stems in the
order `distinct_without` lists them, and a document whose stems read each other settles in as many
passes as its dependency chain runs against that order, with a bound of sixteen. A chain of
eighteen stems settles in one pass listed along the chain and not at all listed against it
(`the_order_stems_are_listed_in_decides_a_result_and_a_source_must_keep_it`). No fixture before it
had a result that depended on the order, and a source listing stems sorted passed everything.
The first-seen order is `DataStore`'s insertion order; a source with no order of its own must
fix one.
9. **What is and is not proven.** The tests run against `Remote` (`tests/support/remote.rs`), a scanning
in-memory table with counters, that shares no evaluator with `Eager` and answers queries over rows.
Without a real SQL engine only the **contract** is proven: that a source answering these six
queries as specified gives `Eager`'s answers, and what each read costs in queries. Nothing here
@@ -924,3 +924,36 @@ The same for `ClearCell`.
Review of #358: two backends over one feed; one write; `a.sync(); b.sync()`. The first sees it, the
second does not. This pins the behaviour as documented rather than as a guarantee: a drain is destructive.
### the_order_stems_are_listed_in_decides_a_result_and_a_source_must_keep_it
Review of #360: stem order was invisible to every differential (a sorted `distinct_without` went
uncaught). Eighteen products are stored in chain order with names that sort the other way, and one nested
`IF` formula reads the product before it. Along the chain it settles in one pass (the last link is 18);
sorted it needs one pass per link and the bound of sixteen leaves it `circular`. The test asserts both
values, that a sound source passes the differential and that a source listing the stems sorted is a
divergence. Rival: `DataStore::distinct_without` sorting fails it (the oracle then gives `circular`, so
the first assertion fails). Before it, a source sorting its stems passed every differential; only the core stems test (which compares two stores) saw a sort.
### writes_between_two_queries_of_a_refresh_are_healed_by_the_fixed_point
Review of #360: a source that changes between the queries of one refresh. Another client's writes land
right after the first fold; the cached Profit of Product A ends at 53, the new state's value, and not at
13 (the old revenue less the new cost). The documented guard is one snapshot per refresh. Rival:
none; this pins a behaviour that the fixed point gives and the note says may not be relied on.
### a_stem_that_appears_between_the_stem_list_and_the_folds_waits_for_the_next_refresh
The stem list is taken, then Product C is written: it has no formula cell until the next refresh,
which has it (5).
### a_cell_edit_on_a_pushdown_is_refused
Review of #360: `define` applied a cell edit to the model, whose guard was a `debug_assert`; now an
`assert!` of its own, with its own message. Rival: the assert removed fails it (the model's message
differs).
### a_pushdown_can_be_built_over_the_exported_source_alias
`Pushdown::new` took a generic `Arc<Q>` that could not take a `SharedSource`; the test builds one from
the alias and did not compile against the generic signature.