Merge pull request #232 from fiddlerwoaroof/collapse-fu/d7aj

improvise-d7aj: pin the fold memo to the whole key, and the diamond (stacked on #203)
This commit is contained in:
Ed Langley
2026-09-23 08:35:17 -07:00
committed by GitHub
2 changed files with 64 additions and 0 deletions
+27
View File
@@ -1481,6 +1481,33 @@ because the depth bound the fold path used before is gone. Without the marker
the evaluation recurses until the stack runs out, which is why the marker is
there rather than being an optimisation.
### one_formula_at_two_fold_keys_is_evaluated_at_each
improvise-d7aj, found by the adversarial re-review of PR #203 at `b3379d1a`. The
fold memo in `fold_formula_once` is keyed by the whole `CellKey`, and nothing
pinned that: both tests above keep one key context throughout, so a memo keyed
by the formula alone passed the whole workspace. A reference to an item of
**another** category moves the key while `_Measure` names the same formula.
Stores A (Jan 3, Feb 4) and B (Jan 100, Feb 200), `G = Revenue * 2`,
`F = IF(Revenue > 50, G, G + B)`. At `(Q1, A, F)` Revenue is 7, so F is
`G + B`: G at A is 14, and `B` reads F at `(Q1, B)`, where Revenue is 300, so
that F is G at B, 600. The answer is **614**. Rival: the memo keyed by
`formula.target` alone. F is still in progress at `(Q1, A)` when F at `(Q1, B)`
is asked, so that key reads `circular`. Injected into a full workspace run, it
fails this test and nothing else. The reviewer's probe, now committed.
### a_diamond_at_a_fold_is_not_circular
improvise-d7aj's second case, which passed at `b3379d1a` and was pinned by
nothing. `A1 = B2 + C3`, `B2 = D * 2`, `C3 = D * 3`, `D = Revenue` reaches D by
two paths in one fold evaluation, so the second reach must find D *finished*
and not *in progress*. It asserts **35** over D = 7 and exactly four
evaluations, so a memo that re-evaluates D passes neither. Rival: never store a
finished value, so only the in-progress marker stays. This test fails with
`circular`, and so does `a_fold_evaluates_each_formula_it_reaches_once`, whose
doubling chain is also a diamond at every step. So this test is a named pin of
that shape rather than the only guard against it.
### a_fold_answers_is_and_where_on_its_category_as_the_none_axis_does
improvise-lnkb, ruled by the orchestrator: on a fold, the folded category reads
+37
View File
@@ -1715,6 +1715,43 @@ mod model_tests {
);
}
/// improvise-d7aj: the memo is per KEY, not per formula — one formula met at two
/// fold keys in one evaluation. Evidence: context/test-evidence/core.md.
#[test]
fn one_formula_at_two_fold_keys_is_evaluated_at_each() {
let mut m = quarter_model(&[
("Jan", "A", 3.0),
("Feb", "A", 4.0),
("Jan", "B", 100.0),
("Feb", "B", 200.0),
]);
for text in ["G = Revenue * 2", "F = IF(Revenue > 50, G, G + B)"] {
m.add_formula(crate::formula::parse_formula(text, "_Measure").unwrap());
}
let key = coord(&[("Month", "Q1"), ("Store", "A"), ("_Measure", "F")]);
assert_eq!(
m.evaluate_covering(&key, Aggregation::Exact, &|_, _| true),
Some(CellValue::Number(614.0)),
"G at A (14) plus F at B, which is G at B (600)"
);
}
/// improvise-d7aj: a formula reached twice by two paths is finished, not in
/// progress, the second time. Evidence: context/test-evidence/core.md.
#[test]
fn a_diamond_at_a_fold_is_not_circular() {
let mut m = quarter_model(&[("Jan", "A", 3.0), ("Feb", "A", 4.0)]);
for text in ["A1 = B2 + C3", "B2 = D * 2", "C3 = D * 3", "D = Revenue"] {
m.add_formula(crate::formula::parse_formula(text, "_Measure").unwrap());
}
let key = coord(&[("Month", "Q1"), ("Store", "A"), ("_Measure", "A1")]);
super::FOLD_FORMULA_EVALUATIONS.with(|n| n.set(0));
let value = m.evaluate_covering(&key, Aggregation::Exact, &|_, _| true);
let evaluations = super::FOLD_FORMULA_EVALUATIONS.with(|n| n.get());
assert_eq!(value, Some(CellValue::Number(35.0)), "14 + 21 over D = 7");
assert_eq!(evaluations, 4, "A1, B2, C3 and D once each");
}
/// Evidence: context/test-evidence/core.md.
#[test]
fn a_groups_stored_values_fold_once_not_per_member() {