From cd97d59f1368c4362becd8981ed8a99471f7ac51 Mon Sep 17 00:00:00 2001 From: Shay Date: Sun, 31 May 2026 16:36:44 -0700 Subject: [PATCH] fix(math): restore WAVE-A multiplicative aggregate completeness provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ADR-0191 completeness guard (added after WAVE-A) requires aggregating initials to expose every consumed source token via `consumed_value_tokens`, so it can confirm no source quantity was silently dropped. The WAVE-A "each weighing" injector predates the guard and left that field empty, so for every " M , each ... N " reading the guard computed required={M, N} vs consumed={M*N} and refused as "incomplete reading: source quantities [M, N] not consumed". This silently regressed the entire WAVE-A capability — the canary `test_lilibeth_canary_solves_end_to_end` has been red on main (it failed byte-identically on f79b647; the smoke gate does not collect this dedicated test file, so it merged green originally). Fix: populate `consumed_value_tokens=(count_a_token, count_b_token)` on the composed initial — exactly the contract the day-enumeration and embedded-quantifier aggregators already satisfy. wrong==0 preserved: the two tokens genuinely ARE the multiplicands of the emitted value; the guard remains refusal-only. Serving frozen: this shape does not occur in train_sample, so the fix is serving-neutral. Evidence: - tests/test_wave_a_multiplicative_aggregation_injector.py: 11 passed (was 1 failed) incl. test_wrong_zero_preserved (full train_sample eval, wrong==0). - core test --suite packs: 141 passed (was 1 failed, 140 passed). - core test --suite smoke: 67 passed. - scripts/verify_lane_shas.py: lanes 8/8 match pinned SHAs — the train_sample_v1 serving SHA is byte-identical, proving zero serving count change. PR checklist: - Capability: restores WAVE-A multiplicative-aggregate reading regressed by ADR-0191. - Invariant: wrong==0 (completeness guard stays refusal-only; tokens are true multiplicands). - Lane: core test --suite packs / smoke + lane-SHA gate (8/8). - No hidden normalization, stochastic fallback, approximate recall, or unreviewed mutation. - Trust boundary: none widened — internal candidate provenance only. --- generate/recognizer_match.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/generate/recognizer_match.py b/generate/recognizer_match.py index 37eaa32b..54096d87 100644 --- a/generate/recognizer_match.py +++ b/generate/recognizer_match.py @@ -1441,6 +1441,16 @@ def _try_extract_each_weighing_anchor( "input_tokens": f"{count_a_token}|{count_b_token}", "entity_source": "same_sentence", }, + # ADR-0191 completeness provenance. This is an aggregating initial: + # its derived value (count_a × count_b) collapses TWO source tokens + # into one quantity. Without recording them, the completeness guard + # (generate/math_completeness.py) sees required={count_a, count_b} + # but consumed={composed_value} and refuses every WAVE-A reading as + # "incomplete" — a silent regression of the WAVE-A capability that + # predates the guard. Expose both consumed tokens so the guard can + # confirm no source quantity was dropped. wrong==0 is preserved: + # these tokens genuinely ARE the multiplicands of the emitted value. + consumed_value_tokens=(count_a_token, count_b_token), ) anchor: Mapping[str, Any] = {