core/docs/decisions/ADR-0185-division-reading.md
Shay 171b1cd7a5
feat(adr-0186): sealed candidate-graph injector lane + topology correction (#487)
* docs(adr-0186): sealed candidate-graph injector lane (resume ADR-0170 W2-W5 under ADR-0175 seal)

Topology audit found two disjoint GSM8K readers: the candidate-graph reader
owns the official 3/47/0 metric and already has divide/multiply/compare; its
wall is the recognizer->injection coverage gap (ADR-0170 W2-W5 backlog), not
arithmetic. The derivation reader (resolve_pooled) is a separate sealed organ
that cannot reach the goal without an unbuilt Phase-5 bridge.

ADR-0186 reconciles ADR-0170's injector roadmap with ADR-0175's serving seal:
develop W2-W5 injectors behind a default-off 'sealed' flag on inject_from_match,
measured on a new report_sealed.json, with frozen 3/47/0 byte-identical until a
reviewed Phase-5 promotion. wrong=0 gated on both paths.

Live-loop instrumentation found a schema-vs-extraction split: rate_with_currency
and temporal_aggregation matchers already extract full anchors (blocked on the
CandidateRate union / apply_rate primitive), while discrete_count/currency/
multiplicative are blocked on matcher extraction. And no refused case is one
injector away - every case is multi-statement, so the unit of measurable
progress is a target case's complete injector+composition set. Sequencing:
seal mechanism first, CandidateRate schema next, then a first complete
target-case unlock.

* feat(adr-0186): sealed candidate-graph injector lane mechanism (default-off)

Adds the seal mechanism for developing ADR-0170 W2-W5 injectors without
mutating the frozen serving metric:

- inject_from_match(match, sentence, *, sealed=False): when sealed=True,
  consults the new _SEALED_INJECTORS table first; default-off never touches it.
- _SEALED_INJECTORS: empty at land (this PR ships the mechanism; the first
  sealed capability is the CandidateRate schema, ADR-0186 §5.3).
- parse_and_solve(text, *, sealed=False): threads the flag to the per-statement
  injection site. The seal is injector eligibility, not a forked reader.

wrong=0 guarantees (tests/test_adr_0186_sealed_injector_lane.py, failing-
under-violation): empty seal is a strict no-op; a registered sealed injector
admits ONLY under sealed=True and is invisible to the frozen path; train_sample
report stays 3/47/0. Verified: coverage probe 3/47/0 byte-identical, smoke +
architectural invariants green, 288 candidate-graph/recognizer tests pass (2
pre-existing failures unrelated, confirmed on unmodified main).

* docs(adr-0185): mark superseded by ADR-0186 (premise refuted by topology audit)

The topology audit proved ADR-0185's premise ('the engine cannot divide') is
true only of the derivation reader, which is disjoint from the candidate-graph
reader that owns the official 3/47/0 metric. The goal organ already divides;
its wall is injection coverage. ADR-0185 is retained as a record, not
implemented; cross-referenced from ADR-0186 §header.
2026-05-30 08:35:43 -07:00

159 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-0185 — Division reading (rate / partition): the first genuine multi-step capability, eliminate-then-solve
> **⚠️ SUPERSEDED (premise refuted) — 2026-05-29.** This ADR's central premise,
> *"the engine cannot divide,"* is true only of the **derivation reader**
> (`generate/derivation/*`, `resolve_pooled`), which the 2026-05-29 topology audit
> proved is **disjoint from the reader that owns the official `3/47/0` metric**. The
> goal organ — the candidate-graph reader (`generate/math_candidate_graph`) —
> **already divides, multiplies, and does comparative arithmetic**; its wall is the
> recognizer→injection coverage gap, not arithmetic. A division *reading* added to the
> derivation reader therefore cannot move `3/47/0`. See
> [ADR-0186](./ADR-0186-sealed-candidate-graph-injector-lane.md) §1 for the topology
> finding. **This ADR is retained as a record only; it is NOT implemented.** Any
> division work belongs in the candidate-graph path under ADR-0186's seal.
**Status:** **Superseded by [ADR-0186](./ADR-0186-sealed-candidate-graph-injector-lane.md)**
(premise refuted; not implemented). Originally: Proposed (spec only — no code). Follow-on to
[ADR-0184](./ADR-0184-distinct-unit-product-rule.md) (which cut the dimensionally-
impossible products, `13 → 8`) and [ADR-0182](./ADR-0182-cross-composer-disagreement-pooling.md)
(pooling + commit-ineligibility).
> **One line.** The engine can multiply and add/subtract but **cannot divide** — and
> ~⅔ of the remaining real-GSM8K failures need division (rate, ratio, partition).
> Add a division *reading* (cue-licensed, gated by self-verification, pooled), used
> **eliminate-first** (a grounded `÷` reading disagrees with the flat product → the
> wrong refuses) then **solve** (the `÷` reading commits when it is the unique
> grounded reading).
---
## 1. The metric this serves (and why the headline is the wrong one)
`serving = 3/47/0` is the conservative recognizer, **frozen** until a Phase-5
ratification ([ADR-0175](./ADR-0175-calibrated-attempt-and-eliminate-learning.md)).
The metric this ADR moves is the **sealed comprehension reader** over the real
`train_sample` (`resolve_pooled`): currently **2 correct / 8 wrong / 40 refused**
(post-ADR-0184). Progress = `correct ↑``wrong ↓` toward `≥10 / 0`. Every claim
below is measured there, never on the curated confuser count.
## 2. What the microscope proved about the remaining 8 wrongs
The 8 (`0011 0016 0018 0019 0025 0028 0032 0047`) are **all** the flat product-of-all
committed unopposed. Classifying each by what its *gold* computation needs:
| need | cases |
|---|---|
| **division** (rate / ratio / partition) | 0011, 0016, 0018, 0028, 0047 |
| **percentage / decimal** | 0019, 0028, 0032 |
| comparative arithmetic (`N more/less than M`) | 0016 |
| multi-step grouping | 0025 |
| **extraction gaps** (currency `$100,000 → 100`, units `coconut`/`different`) | 0011, 0028, 0047 |
**Division is the single largest need** (5 of 8), and it is also the most common
missing op across the 47 refused. The engine today has *no* division reading at all —
the only readings it forms are additive accumulation and multiplicative products.
## 3. Why there is no flat shortcut (the two traps measured and rejected)
Eliminate-first is right, but it must not overfit or trade away a correct answer.
Both flat shortcuts were measured and rejected:
| candidate | result | verdict |
|---|---|---|
| remove `per` from multiplicative cues | 8 → 7 (zero loss *on the 50*) | **overfit** — on real GSM8K `12 eggs per carton × 5 cartons = 60` is a legitimate rate×quantity multiply; `per` removal would lose it. Tuning to the sample. |
| downgrade cross-clause flat product → exempt | 8 → 2, but `correct 2 → 1` | **loses `0003`** (a correct rate-chain) — trades a real solve to kill guesses. |
The distinguisher between the 8 wrong products and the 2 correct ones (`0003`
`boxes×erasers×$`, `0021` `pounds×reps×sets`) **is the structure** — rate-binding,
comparative, division — which is exactly what the engine cannot read. So there is no
non-overfitting flat rule; the only honest path is to **read the structure**.
## 4. The mechanism — a division reading
A composer that proposes a grounded `÷` candidate, licensed by a **division cue**
(lexeme-level, ADR-0165-safe — names the markers, does not parse grammar):
- **rate / ratio:** `X per Y`, `X / Y`, `how many X per Y``X ÷ Y`.
- **partition:** `split / divide / share N into M`, `N split among M`,
`packs … into M (groups/bags)``N ÷ M`.
The candidate runs through the **unchanged** self-verification gate (grounding ∧ cue
∧ unit ∧ completeness) and the pool. Two roles, in order:
1. **Eliminate (first).** A grounded `÷` reading that competes with the flat product
forces a **disagreement → refuse** (the wrong product stops committing). Even an
incomplete `÷` reading (isolated-foreign / partial) enters as a commit-ineligible
`exempt` candidate and still forces the refusal (the ADR-0182/0184 pattern).
2. **Solve (then).** When the `÷` reading is the unique grounded reading, it commits —
coverage.
### 4.1 The `per` cue-semantics correction (done right)
`per` is currently in `MULTIPLICATIVE_CUES`, which is wrong: **`X per Y` denotes a
rate `X/Y` (division)**, not `X×Y`. But §3 showed *removing* it overfits. The correct
fix is **reclassification, not removal**: `per` licenses the new **division**
composer (the rate numerator/denominator), and the multiply that *scales by* a rate
(`rate × quantity`) is licensed by the quantity structure, not by `per`. This makes
`per`-products refuse-or-divide instead of blindly multiplying, without losing
legitimate rate×quantity multiplies (which the division reading + a scaling step
recover). Validated on `train_sample` + the confuser probe, not the 50-sample alone.
## 5. Honest scope — division alone will not flip most of the 8
This is the load-bearing caveat. Division is **necessary but not sufficient** for the
8 because they are entangled with other missing capabilities:
- `0016` needs **comparative arithmetic** (`2 more than 5 → 7`) *and* `per`-as-rate
before `14 ÷ 7 = 2`.
- `0011`/`0028` need **currency extraction** (`$100,000`, `$2`) before any op.
- `0019`/`0032` need **percentage** reading.
So division's *immediate* `train_sample` effect is mostly **elimination** (flat
products refuse where a `÷` reading disagrees), with **solves** arriving only as the
companion capabilities land. The obligation (§7) is therefore framed as `wrong ↓`
(never ↑) with `correct` held or rising — not an inflated solve claim. Claiming
division alone flips these would be the overfitting trap (`feedback-synthetic-corpus-
overfitting-trap`).
## 6. The roadmap this opens (eliminate-then-solve, each measured)
Division is capability **1** of the multi-step reading the goal requires. Sequenced by
leverage × foundational-ness, each gated by `wrong=0` and measured on `train_sample`:
1. **Division reading** (this ADR) — rate/ratio/partition + `per` reclassification.
2. **Comparative arithmetic**`N more/less than M → M±N` (a reading-correctness fix;
unblocks `0016` and is general across GSM8K).
3. **Currency / decimal extraction richness**`$100,000`, `$2`, `1%` (the
extraction gaps that garble `0011`/`0028`).
4. **Percentage reading**`N% of X`, `N% less/more` (`0019`/`0032`).
5. **Multi-step chain shapes**`anchor sum`, `sum-then-scale`, division-then-±
(the mixed-op chains gold actually uses).
6. **Ratification bridge** ([ADR-0175](./ADR-0175-calibrated-attempt-and-eliminate-learning.md)
Phase 5) — the hard dependency: until a reviewed promotion path exists, **none** of
this moves the serving `3/47/0` headline, however good the sealed reader gets.
## 7. wrong=0 obligations (before any merge of the implementation)
1. **`train_sample`:** sealed-reader `wrong` must **drop, never rise**; `correct` held
or rising. Failing-under-violation: the cases a `÷` reading refuses (disagreement)
are pinned; any case it *commits* is gold-checked.
2. **Confuser probe:** `wrong` does not regress; positives still solve. A division
composer must not let any curated case commit a wrong `÷`.
3. **Serving frozen:** the composer is sealed (`chat/` does not import it); `3/47/0`
byte-identical, lane-SHA 8/8.
4. **No overfitting:** every cue is a *general* division marker validated beyond the
50-sample; the `per` reclassification is justified by cue semantics, not by a
sample delta. No per-case rule.
5. **Commit-ineligibility:** an incomplete `÷` reading may force a refusal but never
commit alone (full completeness still required to commit — the ADR-0175 guarantee).
## 8. Why this obeys the standing principles
- **Decode, don't guess** (thesis): a division *reading* comprehends rate/partition
structure; it does not store a flat product. It teaches the engine to *find* better.
- **Eliminate-first:** the `÷` reading's first job is to make wrong flat products
refuse (disagreement), before it earns any commit.
- **wrong=0 > coverage** (ADR-0175): obligations gate on `wrong ↓`, not solve count.
- **ADR-0165:** cues are lexeme markers, not grammar templates.
- **No contradiction** with in-use ADRs: extends the pool/gate, changes no serving
path, reuses the commit-ineligibility mechanism rather than inventing a new gate.