Merge pull request #208 from fiddlerwoaroof/fix/b9f2-width-guard

Determinism law: the width guard measures the drills it compared (improvise-b9f2)
This commit is contained in:
Ed Langley
2026-09-23 07:56:04 -07:00
committed by GitHub
3 changed files with 112 additions and 53 deletions
+35 -12
View File
@@ -3379,18 +3379,41 @@ one run in eight (measured: 46 of 400). So the arguments now include **every
item of every regular category as a one-coordinate drill**, read off the parsed
document. A one-coordinate drill spans every other category whole, so it matches
the whole slice of records under that item, not one record picked by name. In
the demo that is four records and eight cells for each of the eight drills. The
test
also asserts that the widest candidate it actually passes matches at least four
records. That guard catches a demo with too little data, and it catches the
derived drills being dropped from the argument list. It does **not** catch the
comparison losing its wide drills some other way, because it measures what the
argument lists match, not what the built commands ordered (improvise-b9f2). The
review showed this with a rival: a `drill-into-cell` parser that refuses fewer
than two arguments sends every one-coordinate drill to the one-record cursor
fallback. The guard still reports four records, and with the store's sort
removed the law passed 38 of 300 runs. With the store's sort removed and the
parser intact, the law fails 400 of 400 runs.
the demo that is four records and eight cells for each of the eight drills. With
the store's sort removed, the law fails 400 of 400 runs.
**The width guard measures what the comparison compared (improvise-b9f2,
improvise-btyw).** The test asserts that some `drill-into-cell` the comparison
actually built and compared froze at least four records. For each one it applies
those very effects to one scratch `App` and counts the records in its
`drill_state`. It counts them only when their `Debug` text, which carries their
order, appears in the text the comparison compared. The comparison reads `Debug`
while the scratch `App` reads the applied state, and those are two
representations. The review showed they can disagree: give `StartDrill` a
`Debug` that prints only a row count, and the frozen records are still four
while the compared text no longer holds their order. With the store's sort
removed the law then passed 300 of 300 runs, with the guard green. Requiring
the ordered records in the compared text ties the guard to what the comparison
could actually see. A first
version measured what the *argument lists* match in the store instead. That was
a parallel derivation, and the review beat it with a `drill-into-cell` parser
that refuses fewer than two arguments. Every one-coordinate drill then went to
the one-record cursor fallback, the argument lists still matched four records,
and with the store's sort removed the law passed 38 of 300 runs.
Counting frozen records is not enough by itself. A drill over no coordinates
(any candidate with no `Cat/Item` argument, such as `["1", "1"]`) freezes
**every** record, and it reads them through `iter_cells` in insertion order,
never through the narrowing index where gotcha 18 lives. Under the parser rival
that whole-store drill still froze 16 records, and a guard counting it stayed
silent. So only a drill that **narrowed** the store counts: one that froze fewer
records than the store holds. With the parser rival in place, the guard fires on
every run ("froze 1 records"), and it fires on every run under the `Debug` rival
too ("froze 0 records").
What the guard still cannot see is a narrowing drill whose records are frozen in
an order that does not come from the store. It checks that the law had a wide
drill to compare, not why the drill's order would differ.
**What it cannot see.** Only commands and arguments the fixture reaches. A
command whose nondeterminism needs some other state, such as records mode, a
+35 -8
View File
@@ -5509,14 +5509,41 @@ rivals it catches:
still pin the store directly.
- **The derived drills dropped from the argument list** (the
`all.extend(one_coordinate_drills(model))` line commented out): the test
fails before comparing anything, with "the widest drill orders 1 records".
That is the only way of losing the wide drills the guard can see. It measures
what the argument lists match, not what the built commands ordered, so it can
pass while the comparison has lost its wide drills (improvise-b9f2). Review
rival: `drill-into-cell`'s parser changed to refuse fewer than two arguments,
which sends every one-coordinate drill to the one-record cursor fallback. The
guard stays silent and, with gotcha 18 re-injected, the law passed 38 of 300
runs.
fails with "the widest narrowing drill compared froze 1 records".
- **`drill-into-cell`'s parser changed to refuse fewer than two arguments**
(improvise-b9f2): every one-coordinate drill then goes to the one-record cursor
fallback. The first width guard measured what the argument lists match, so it
stayed silent. With gotcha 18 re-injected the law passed 38 of 300 runs in
review, and 31 of 300 here at `cfcc9bff`. The guard now applies the compared
drills' own effects to a scratch `App` and counts the records they froze.
Against this rival it fails on every run with "froze 1 records".
- A first version of the fix counted all frozen records and **still passed**.
A candidate with no `Cat/Item` argument drills the whole store (16 records),
and it reads them in insertion order through `iter_cells`, which the
injection cannot reach. So the guard counts only drills that froze fewer
records than the store holds.
- Measured at `7dfa90f1`, test binary in a loop:
| Faults | Law passes | Guard fired |
|---|---|---|
| gotcha 18 + parser rival | **0 of 300** | all 300 |
| gotcha 18 alone | **0 of 300** | 0 (every failure a real divergence) |
| neither | 300 of 300 | 0 |
- **`StartDrill`'s `Debug` changed to print only its row count**
(`StartDrill(4 rows)`, improvise-btyw): the law compares effects by their
`Debug` text, and this rival takes the record order out of that text, while
the frozen `drill_state` still holds four records. At `7dfa90f1`, with gotcha
18 re-injected, the law **passed 200 of 200** runs and the guard stayed green.
The guard now counts a drill only when its frozen records, as `Debug` prints
them, appear in the text the comparison compared. The same measurements after
the repair, 200 runs each:
| Faults | Law passes | Guard fired |
|---|---|---|
| gotcha 18 + `Debug` rival | **0 of 200** | all 200 ("froze 0 records") |
| gotcha 18 + parser rival | **0 of 200** | all 200 |
| gotcha 18 alone | **0 of 200** | 0 (every failure a real divergence) |
| neither | 200 of 200 | 0 |
- **A stale exemption**: `("quit", "stale")` listed in `OUTSIDE_THE_CONTEXT`.
It fails as "listed … but did not".
@@ -1,14 +1,14 @@
//! A command is a function of its context: two contexts built independently from
//! equal data give equal effects — `context/code/command.md`, improvise-hzjo.
use std::collections::{BTreeSet, HashSet};
use std::collections::BTreeSet;
use super::core::{Cmd, CmdContext};
use super::grid::DrillIntoCell;
use super::registry::tests::candidate_args;
use crate::command::key::{KeyCode, KeyModifiers};
use crate::model::Model;
use crate::model::cell::{CellKey, CellValue};
use crate::model::record::record_of;
use crate::ui::app::App;
use crate::ui::effect::{self, Effect};
@@ -61,27 +61,23 @@ fn one_coordinate_drills(model: &Model) -> Vec<Vec<String>> {
drills
}
/// How many distinct records the widest all-`Cat/Item` candidate matches.
fn widest_drill(model: &Model, candidates: &[Vec<String>]) -> usize {
let coordinates = |args: &Vec<String>| -> Option<Vec<(String, String)>> {
args.iter()
.map(|a| {
a.split_once('/')
.map(|(c, i)| (c.to_string(), i.to_string()))
})
.collect()
};
candidates
.iter()
.filter(|args| !args.is_empty())
.filter_map(coordinates)
.map(|partial| {
let cells = model.data.matching_cells(&partial);
let records: HashSet<CellKey> = cells.iter().map(|(k, _)| record_of(k)).collect();
records.len()
})
.max()
.unwrap_or(0)
/// Records a compared drill froze, counted only if it narrowed the store and its
/// ordered records are in `compared`, the text the law compared (improvise-btyw).
fn records_narrowed(scratch: &mut App, effects: Vec<Box<dyn Effect>>, compared: &str) -> usize {
let store = scratch
.model_state
.workbook
.model
.data
.live_records()
.count();
scratch.apply_effects(effects);
let frozen = scratch
.drill_state
.as_ref()
.filter(|drill| compared.contains(&format!("{:?}", drill.records)))
.map_or(0, |drill| drill.records.len());
if frozen < store { frozen } else { 0 }
}
/// The registry's candidates, plus names the demo really has: a drill or a fit
@@ -108,9 +104,9 @@ fn args_the_document_names(model: &Model) -> Vec<Vec<String>> {
all
}
fn effects_of(name: &str, args: &[String], ctx: &CmdContext) -> Option<String> {
fn effects_of(name: &str, args: &[String], ctx: &CmdContext) -> Option<Vec<Box<dyn Effect>>> {
let cmd = ctx.registry.interactive(name, args, ctx).ok()?;
Some(format!("{:?}", cmd.execute(ctx)))
Some(cmd.execute(ctx))
}
/// What running every registered command under `a` and under each of `others` found.
@@ -120,20 +116,34 @@ struct Comparison {
shown: Vec<String>,
never_built: Vec<&'static str>,
compared: usize,
widest_drill: usize,
}
fn compare(a: &CmdContext, others: &[CmdContext]) -> Comparison {
let mut found = Comparison::default();
let candidates = args_the_document_names(a.model);
let drill = DrillIntoCell {
key: CellKey::new(vec![]),
};
let mut scratch = fixture();
for name in a.registry.names().collect::<Vec<_>>() {
let before = found.compared;
for args in &candidates {
let Some(left) = effects_of(name, args, a) else {
let Some(effects) = effects_of(name, args, a) else {
continue;
};
let left = format!("{effects:?}");
if name == drill.name() {
found.widest_drill =
found
.widest_drill
.max(records_narrowed(&mut scratch, effects, &left));
}
found.compared += 1;
for b in others {
let right = effects_of(name, args, b).unwrap_or_default();
let right = effects_of(name, args, b)
.map(|e| format!("{e:?}"))
.unwrap_or_default();
if left != right {
found.diverged.insert(name);
found
@@ -157,13 +167,12 @@ fn every_command_gives_equal_effects_in_independently_built_equal_contexts() {
.iter()
.map(|app| app.cmd_context(KeyCode::Null, KeyModifiers::NONE))
.collect();
let model = contexts[0].model;
let widest = widest_drill(model, &args_the_document_names(model));
assert!(
widest >= 4,
"the widest drill orders {widest} records, so the store's order is nearly a coin toss"
);
let found = compare(&contexts[0], &contexts[1..]);
assert!(
found.widest_drill >= 4,
"the widest narrowing drill compared froze {} records, so the store's order is nearly a coin toss",
found.widest_drill
);
assert!(
found.never_built.is_empty(),
"no candidate args build these in the fixture, so the law never ran them: {:?}",