From 993418f1431947d2bbfb79555ea0a0cbb3450d6f Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Wed, 23 Sep 2026 08:11:37 -0700 Subject: [PATCH] improvise-d7aj: pin the fold memo to the whole key, and the diamond A formula met at two fold keys in one evaluation (an IF branch naming an item of another category) must be evaluated at each. A memo keyed by the formula alone read circular there and passed the whole suite; this test fails under it and nothing else does. The diamond A1 = B2 + C3 over D is pinned too. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/test-evidence/core.md | 27 +++++++++++++++++ crates/improvise-core/src/model/types.rs | 37 ++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/context/test-evidence/core.md b/context/test-evidence/core.md index d60ca91f..a83d7f08 100644 --- a/context/test-evidence/core.md +++ b/context/test-evidence/core.md @@ -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 diff --git a/crates/improvise-core/src/model/types.rs b/crates/improvise-core/src/model/types.rs index b3b5c6b7..73e86c21 100644 --- a/crates/improvise-core/src/model/types.rs +++ b/crates/improvise-core/src/model/types.rs @@ -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() {