Merge commit '5932ad749f0df01d4321cc961f57a488ab637dde' into fix/we1a-abort-refuses

# Conflicts:
#	context/test-evidence/command.md
#	crates/improvise-command/src/ui/app.rs
This commit is contained in:
Edward Langley
2026-09-23 08:51:42 -07:00
44 changed files with 1236 additions and 203 deletions
+119
View File
@@ -5793,6 +5793,69 @@ on `Food/Jan/Profit`, where there is nothing to clear, and
`Clothing/Jan/Profit = 5` survives. Injected alone into a full workspace run,
the rival fails this test and nothing else.
### a_wire_write_to_a_collapsed_fold_is_rejected_and_stores_nothing
improvise-ciw8, through a real `Host` frame. Q2 is collapsed, and a hand-rolled
client sends `SetCell` (then, separately, `ClearCell`) for `{Month=Q2, _Measure=Cost}`
with a caught-up basis. At `ea119bd9` both were answered `Accepted`, and the
`SetCell` stored `Month=Q2, _Measure=Cost = 7`, the non-item coordinate
improvise-gw18 exists to prevent. The test asserts `Rejected(MutationRefused)`
and an unchanged document for both. Rival: the aggregate refusal disabled. This
test and the None-axis test fail. The control is
`a_wire_write_to_a_stored_cell_beside_a_fold_is_accepted`, so a host that refused
every wire write cannot pass.
### a_wire_write_under_a_formula_is_rejected_and_stores_nothing
improvise-ciw8's `Derived` arm: `SetCell` on Feb × Profit, where
`Profit = Revenue - Cost`. At `ea119bd9` it was accepted and stored a value under
the formula. Rival: the formula refusal disabled. Only this test fails.
### a_wire_write_to_a_none_axis_aggregate_is_rejected_and_stores_nothing
improvise-ciw8's other `Aggregate`. With `Product` on `Axis::None`, the grid's
key `{Region=South, _Measure=Revenue}` is a partial key that no stored record
has, so it resolves only through the layout, where `cell_id` says `Aggregate`.
At `ea119bd9` it was accepted and stored the partial key. It fails under the
same rival as the fold test.
### a_wire_write_to_a_record_that_never_existed_is_rejected
The evidence for keeping `model_effects`' `vec![]` arm rather than turning it
into a refusal (`context/code/command.md`, "A wire write reaches only a cell the
host projects as a Record"). A lone write naming a record that never existed is
answered `RecordGone` before anything runs, both when a caught-up basis claims
it and when a hand-rolled basis omits it. The second half was expected to be
`ChainBroken`, and the test said otherwise: the create exemption covers only
commands after the first in a frame. That surprise is why it is asserted rather
than argued.
### an_empty_cell_beside_a_collapsed_measure_group_is_writable
improvise-sjdb, a regression PR #217 introduced and its review found. `_Measure`
is on Column with Revenue and Cost in a collapsed group `Money` and `Units`
ungrouped, and the Feb row is empty. The host projects Feb × Units as a
`Record`. A caught-up `SetCell` on it was answered `Rejected(MutationRefused)`
at `bc060f67`. It was accepted before #217. `aggregate_refusal` found the
unstored record by `RecordHash` alone, which drops `_Measure`, so it judged the
record at the first column on Feb's row, the `Money` fold, which `cell_id` calls
`Aggregate`. The setup asserts that the fold comes before `Units` on the row and
that the target projects as `Record`, so a fixture where the first column is
harmless cannot pass. The fix looks the cell up by its whole key. The rival is
the old lookup by record, which is this test's red commit, `6e021699`.
### a_stored_record_the_hosts_view_aggregates_is_writable
improvise-sjdb, from the #217 review. South/Revenue is stored, then `Product` is
added on `Axis::None` in the host's view, so the host projects South's position
as `Aggregate`. A stored record is a record wherever it is drawn, because
`RecordHash` is document-scoped (gotcha 44). A records-mode client, or one on
another view, may write it. The setup asserts the host really does project
`Aggregate` there, so the test cannot pass on a view that draws South plainly.
Rival: stored records sent through `cell_id` like unstored ones. That rival
fails this test and nothing else in `improvise-command`. Before this test,
nothing in the suite saw it.
### a_fresh_sessions_slice_is_the_hosts_startup_slice
improvise-v8ab. A fresh session's slice is built twice: `App::with_storage`
@@ -5817,6 +5880,33 @@ that is deliberate. Its helpers seed a client *after* mutating the host (mode,
buffers, cursor), so a `Snapshot` would drop exactly the state those tests
stage. This test is what makes the copy faithful at startup.
### ui::effect::tests::recomputing_formulas_leaves_the_model_fingerprint_alone
improvise-pc3b, found by the review of PR #224 at `e62a289`. That PR moved the
effect-classification fingerprints from `serde_json` to `Debug`. The derived
`Debug` for `Model` prints `formula_cache`, a `HashMap` that `recompute_formulas`
clears and refills, so it prints in hash order. The reviewer measured a correct
`ViewOnly` effect (`RebuildLayout`) changing the fingerprint in 90 of 200 runs
once formulas were present. `test_app` has none, so the suite was green over a
latent flake.
The test builds `formula_app` (two measures across a 2×2 grid, plus
`Profit = Revenue - Cost` and `Margin = Profit / Revenue`) 32 times. Each build
is a fresh `App`, so each gets fresh hash states. Each time, it asserts that
`RebuildLayout` leaves `model_fingerprint` unchanged. One flip in about two makes
32 independent tries fail near-certainly.
**Red before the fix**: the two renderings differed only in `formula_cache`'s
entry order. The fix is a hand-written `Debug` for `Model` that renders
`formula_cache` and `measure_agg` in key order.
**Rival, run:** keep the hand-written `Debug` but print `&self.formula_cache`
unsorted. This test fails. `view_only_effects_leave_shared_state_alone` now runs
over `formula_app` too, so every `ViewOnly` case is checked against a populated
cache. The two earlier injections (a `SetStatus` that writes the model, and one
that writes a view's `number_format`) still fail it.
### a_document_opening_on_a_records_view_opens_the_host_in_records_normal
improvise-y5eo. `App::with_storage` set `view_state.mode` to `Normal` and never
@@ -5959,6 +6049,35 @@ flag. This test fails alone. Every other test stays green, which was the gap.
Once improvise-fy9o re-seeds the reopened editor, a stale `touched` would make
every untouched Tab or Enter write its seed.
#### improvise-i3o5: four declines that `set_status` hid from every sweep
`every_refusal_site_refuses_its_failure_and_only_its_failure` gained four cases.
PR #213's review found them by reading, because the sweeps behind this table
revert `refuse(` calls and cannot see a refusal written as `set_status`. Each
exited 0 headless and would not reject a frame:
- `EditOrDrill`'s collapsed-group arm (`folded_quarter`, cursor on Q2's fold at
row 2). The succeeding twin is on Feb at row 1. Its two sibling arms already
refused.
- `paste_into`'s aggregate decline. The spelled key `Row/A Col/A` omits the
`Hidden` category on None. The twin spells all three coordinates.
- `paste_into_record`'s coordinate-column decline: `_Index/1 _Dim/0` is Region.
The twin, `_Dim/2`, is a measure.
- `paste_into_record`'s inside-a-drill decline. The same measure key, under a
frozen drill. The twin is the same key with no drill.
**Red at `e406edc`**, in the order the table reaches the cases. Each failed with
its own text and `refused=false`, and each went green only when its own site was
converted. The collapsed-group case failed first. Then, one conversion at a
time, the three paste cases failed with the aggregate, coordinate-column and
inside-a-drill text.
**Per-site revert:** `/tmp/refuse-fu/sweep.py`, not committed, restricted to the
four sites. It reverts each `effect::refuse(` to `effect::set_status(` alone and
runs `cargo test --workspace --no-fail-fast`. Four of four fail this test, and
nothing else fails. None of the four needs an abort: each is the only effect its
command emits, and `p`/`i` are single bindings, not sequences.
## `ui/host.rs` — the abort arm of `Host::mutating_frame` (improvise-we1a)
### every_failing_model_effect_that_aborts_also_refuses