* docs(adr-0184): scope the distinct-unit product rule — cut the product-of-all over-commit The 47-refusal coverage diagnostic surfaced that the headline 3/47/0 (serving recognizer) hides the sealed comprehension reader's real state: resolve_pooled over the 50 real train_sample cases is 2 correct / 13 WRONG / 35 refused. The confuser probe's wrong=0 was a misleading proxy — all 13 real wrongs are the whole-text product-of-all, the unique complete candidate, committed unopposed. Scopes the first lever, decided by MEASURING candidate refusal rules against the real metric (correct up, wrong down on train_sample): baseline 2 / 13 / 35 distinct-unit product (chosen) 2 / 8 / 40 <- cuts 5, zero coverage loss product spans >1 clause 1 / 4 / 45 <- destroys correct 0003 drop all products 0 / 2 / 48 The distinct-unit rule: multiply/divide may compose DISTINCT units but a multiply step whose operand repeats a non-empty unit already in the product (apples x apples, cards x cards) is unit-incoherence -> refuse (unit^2 is never the answer). Empty-unit operands exempt (0003 multiplies a blank-unit 0.75). Dimensional, not lexical (ADR-0165-safe); refines verify.py clause 3 shared by self_verifies + classify. Honest scope: 13->8, NOT 0. The remaining 8 are distinct-unit products in the wrong shape (rate problems) = cue precision (ADR-0177 CP-2b), the next lever, NOT to be faked with a per-case rule. Establishes the real scoreboard (resolve_pooled over train_sample) and notes the ratification bridge (ADR-0175 Phase 5) as the separate dependency for any of this to reach the serving headline. Spec only; serving 3/47/0 untouched (verify is not on the serving path). * feat(adr-0184): distinct-unit product rule — sealed reader real-GSM8K wrong 13->8 Cuts the over-eager product-of-all on real GSM8K. The sealed comprehension reader (resolve_pooled over train_sample) was 2 correct / 13 WRONG / 35 refused; all 13 are the whole-text product-of-all committed unopposed (0042->2.4M, 0048->19200, 0001->14400). This is the first lever measured against the REAL metric (resolve_pooled over train_sample), not the curated confuser count. Mechanism (verify._is_repeated_unit_product + classify_derivation downgrade): a pure multiplicative chain whose operands repeat a non-empty unit forms unit^2 (apples x apples, cards x cards) -- never the answer; it is the product-of-all multiplying independent groups. Such a product is classified `exempt` (commit-INELIGIBLE), NOT removed. Empty units exempt (0003 multiplies blank-unit 0.75); divide exempt (feet/feet = legitimate count). Dimensional, not lexical (ADR-0165-safe). Implementation finding (folded into ADR §3.1): the naive version put the predicate in the shared _base_reasons gate, which DROPPED the product and regressed the confuser probe 1->3 -- the disguised-polarity 0001/0003 refuse only because the coins x coins product DISAGREES with the coins + coins accumulation reading; dropping it unmasked the additive reading as a unique wrong commit (80/30). The fix is the downgrade: keep it as a commit-ineligible `exempt` candidate so it still forces the disagreement. Pinned by test_downgrade_not_removal_preserves_disagreement_refusal. Evidence (sealed lane; chat/ does not import verify -> serving frozen): - resolve_pooled over train_sample: 2 correct / 8 wrong / 40 refused (was 2/13/35); the 5 repeated-unit products (0001/0017/0042/0045/0048) now refuse, 0003/0021 kept. - confuser probe: wrong unchanged (no 0001/0003 regression), positives still solve. - serving train_sample 3/47/0 and practice (accumulation + search) 3/47/0 byte-identical; self_verifies/_base_reasons unchanged so search lanes are untouched. - 171 derivation/pool/verify tests + 40 architectural invariants green. Honest scope: 13->8, NOT 0. The remaining 8 (0011/0016/0018/0019/0025/0028/0032/0047) are distinct-unit products in the wrong shape (rate problems) = cue precision (ADR-0177 CP-2b), the next lever -- not to be faked with a per-case rule. Carries the corrected ADR-0184 (supersedes the spec-only #484).
9.1 KiB
ADR-0184 — Distinct-unit product rule: cut the product-of-all over-commit (the first lever measured against real GSM8K)
Status: Accepted / Implemented. Implementation refined the mechanism: the rule
is a commit-ineligibility downgrade in classify_derivation (§3), not the
shared unit-clause failure §3 originally proposed — putting it in the shared gate
regressed the disagreement refusals ADR-0182 relies on (see §3.1, the finding).
Follow-on to
ADR-0175 (the
self-verification gate), ADR-0182
(pooling), and ADR-0177 (cue precision — the
next lever this one hands off to).
One line. A multiplicative product may compose quantities of distinct dimensions but must not multiply two operands of the same non-empty unit (
apples × apples,cards × cards × cards→ a meaningless squared unit). This single dimensional rule cuts the sealed reader's real-GSM8K wrong count 13 → 8 with zero coverage loss, measured ontrain_sample.
1. Why this exists — the headline 3/47/0 was hiding the real reader
3/47/0 is the serving recognizer (conservative; refuses what it can't read).
Run the sealed comprehension reader (resolve_pooled — everything ADR-0182
built) over the same 50 real cases and the picture is very different:
serving (recognizer): 3 correct / 47 refused / 0 wrong ← the headline
sealed reader (resolve_pooled): 2 correct / 35 refused / 13 WRONG ← what we are building
The confuser probe's wrong=0 is real on its 30 curated cases but was a
misleading proxy for real-GSM8K safety: the same reader commits 13 wrong
answers on real problems. All 13 are the whole-text product-of-all — every step
multiply, every one the unique complete candidate, so it commits unopposed:
| case | committed | gold | operand units |
|---|---|---|---|
| 0042 | 2,400,000 | 30 | bags × apples × bags × apples × apples |
| 0048 | 19,200 | 4 | cards × weeks × cards × cards × cards |
| 0001 | 14,400 | 990 | _ × hours × your × hours × days |
| 0017 | 7,000,000 | 800 | _ × _ × days × days |
| 0018 | 60 | 16 | minutes × goals × hours |
The product-of-all self-verifies because multiplication is unit-permissive (clause 3 of the gate lets multiply compose across any units) and it uses every quantity (completeness ✓). The gate cannot tell a rate-chain product from a coincidental one — the "self-verification is necessary but not sufficient" finding (ADR-0175). This ADR removes the dimensionally impossible subset of those products; the rest hands off to cue precision (§6).
2. The measured lever (decided by the real metric, not a guess)
Each candidate refusal rule, evaluated on the sealed reader over train_sample
(the metric that matters — correct↑ ∧ wrong↓):
| rule | correct | wrong | refused |
|---|---|---|---|
| baseline (today) | 2 | 13 | 35 |
| product repeats a non-empty unit (this ADR) | 2 | 8 | 40 |
| product spans >1 clause | 1 | 4 | 45 |
| drop all products | 0 | 2 | 48 |
The distinct-unit rule is the only option that cuts wrongs with zero coverage
loss. "Span >1 clause" cuts more but destroys 0003 (a correct 3-sentence
rate-chain 48 boxes × 24 erasers/box × $0.75); "drop all products" loses both
correct answers. Coverage is never bought at the cost of a correct answer.
3. The rule (as implemented)
The dimensional predicate (_is_repeated_unit_product): a pure multiplicative
chain (every step multiply) whose operands — including the start — repeat a
non-empty unit forms unit² (apples × apples, cards × cards), which is
almost never the answer to a word problem asking for that unit. Empty-unit operands
are exempt (an unknown unit cannot be shown to collide — and the correct 0003
multiplies a blank-unit 0.75). Divide is exempt — same-unit division (feet / feet) is a legitimate dimensionless count. Dimensional, not lexical (ADR-0165-safe).
Such a product is classified exempt (commit-ineligible) in
classify_derivation, not failed in the shared _base_reasons gate. It stays in
the pool — it may force a disagreement refusal — but can never resolve as the answer.
A unique repeated-unit product is therefore exempt-only → refuse (the 0042-class
cut); a distinct-unit product stays complete → commit (0003/0021).
3.1 Finding — why a shared unit-clause failure was wrong
The first implementation put the predicate in the shared unit-consistency clause, so
a repeated-unit product failed (was dropped from the pool entirely). That cut the
5 train_sample wrongs but regressed the confuser probe 1 → 3: the
disguised-polarity cases 0001/0003 refuse only because the coins × coins product
disagrees with the coins + coins accumulation reading. Dropping the product left
the (wrong) additive reading unique → it committed (0001 → 80, 0003 → 30). The fix
is the downgrade: keep the product as a commit-ineligible exempt candidate so it
still forces the disagreement, but can never commit. (This is the same commit-
ineligibility concept ADR-0182 introduced.) Pinned by
test_downgrade_not_removal_preserves_disagreement_refusal.
Why this keeps the correct products
| case | product units | repeated non-empty unit? | verdict |
|---|---|---|---|
| 0003 ✓ | boxes × erasers × '' |
no | commit (kept) |
| 0021 ✓ | pounds × reps × sets | no | commit (kept) |
| 0042 ✗ | bags × apples × bags × apples | yes (bags, apples) | refuse (cut) |
| 0048 ✗ | cards × weeks × cards × cards | yes (cards) | refuse (cut) |
A genuine rate-chain composes distinct dimensions by construction; a product that
revisits a dimension is multiplying independent groups (4 bags×20 + 6 bags×25,
mis-read as 4×20×6×25) — never a real quantity.
4. wrong=0 obligations (must discharge before merge)
train_sample(real, the metric): sealed-readerwrong 13 → 8,correctheld at 2 (0003/0021). Failing-under-violation: a test that the 5 named cases (0001/0017/0042/0045/0048) refuse, and 0003/0021 still commit.- Confuser probe:
wrongmust not regress and positives still solve. The §3.1 finding makes this load-bearing: the naive shared-clause version regressed it1 → 3; the downgrade holds it at baseline (1on main pre-#481;0once anchor-skip lands).0001/0003must stay refused, not commit80/30. - Practice search lane:
search_runnercommits products; confirmwrong=0andcorrectnot reduced (the rule may only add refusals there). - Serving frozen:
verifyis not on the serving path (chat/→_score_one_candidate_graph), so3/47/0stays byte-identical. Lane-SHA 8/8. - Scope of the rule: applies to
multiply(anddivide) steps only;add/subtractunchanged. A rare legitimate same-unit product (areaft × ft) will refuse — accepted (refuse-preferring; vanishingly rare in GSM8K).
5. Where it lives
generate/derivation/verify.py: _is_repeated_unit_product (the dimensional
predicate) consulted by classify_derivation, which downgrades an otherwise-
complete repeated-unit product to exempt. self_verifies / _base_reasons are
unchanged — so the search lanes (practice) and serving stay byte-identical, and
the change is confined to the pool's commit-eligibility (where the over-commit lives).
Sealed lane only. tests/test_adr_0184_distinct_unit_product.py pins the predicate,
the downgrade, the 0042-class refusal, and the §3.1 downgrade-not-removal
regression guard.
6. What this does NOT fix — the honest hand-off to cue precision
wrong drops to 8, not 0. The remaining 8 (0011, 0016, 0018, 0019, 0025, 0028,
0032, 0047) are products over distinct units that are still the wrong shape —
rate problems like 0018 (2 goals / 15 min × 120 min, mis-read 15 × 2 × 2). Telling
a correct rate-chain (distinct units bound by per/each) from a coincidental
distinct-unit product is exactly the cue-precision problem
(ADR-0177 CP-2b) — which multiply a cue
licenses. That is the next lever and must not be faked with a per-case rule here (the
overfitting trap, feedback-synthetic-corpus-overfitting-trap).
This ADR is a safe, general down-payment (13→8, zero coverage loss), not the
finish. It also establishes the real scoreboard: every future coverage lever is
measured by resolve_pooled over train_sample (correct↑ ∧ wrong↓ toward
≥10, 0) — never by the curated confuser count alone.
7. Dependency for any of this to reach the headline
Even at wrong=0 the sealed reader cannot move serving 3/47/0 without a
ratification bridge (ADR-0175 Phase 5): a reviewed path that promotes the sealed
reader's reliable readings into the serving path. Cutting the 13→8→0 wrongs is the
precondition (a wrong-prone reader can never be ratified); the bridge is the
separate, later step that makes the headline finally move.