Consolidating ratification of the GSM8K design of record. Ratify the built comprehension/derivation substrate, freeze the serving regex recognizer/ injector path to lexemes + refusal-only, pin Phase 5b execution to WIRING -> COMPOSITION -> LEXICON. - ADR-0207: new consolidating decision (Accepted, ratified 2026-06-03). Supersedes ADR-0163 §Phase B-E + ADR-0136 regex sentence-template prescriptions. Freeze + wrong=0 gates (22-case corpus + sealed 1,319). - ADR-0164/0165/0174/0178/0179: -> Accepted (ratified by ADR-0207, 2026-06-03). 0164 keeps its implementation clause (Phase 1+2 shipped; remainder per §5) so Accepted != fully built. - composition_validation/v1: 20 -> 22 cases (2nd R4/R5 positives, dataset-sourced golds), +contract invariants 6-7, +dataset-gold test. Baseline 4/18/0; 47 passed. - docs/analysis: extraction-richness audit (read-only) reconciling ADR-0179 to the tree (EX-1/2/4/5/6 landed; EX-3 deferred). Non-serving (evals/docs/tests only). train_sample 6/44/0 unchanged; no-ref <N> times hazard stays refused. GB3b/0136 untouched.
166 lines
10 KiB
Markdown
166 lines
10 KiB
Markdown
<!-- analysis | extraction-richness-audit | 2026-06-03 | read-only, no code -->
|
||
|
||
# Extraction-Richness Audit — ADR-0179 named cases, reconciled to the shipped tree
|
||
|
||
**Status:** Analysis only. No serving, eval, or code edits. Every claim below
|
||
reproduced live against `origin/main` @ `2cb0922` via read-only
|
||
`extract_quantities` / derivation-composer / `parse_and_solve` probes.
|
||
|
||
**Scope.** Open [ADR-0179 — Extraction Richness](../decisions/ADR-0179-extraction-richness.md),
|
||
read the exact cases its §Context table names as starved by the thin
|
||
`(\d+(?:\.\d+)?)\s+([a-z]+)` extractor, and verify for each — *against the tree* —
|
||
whether it actually refuses at the **extraction** layer (`generate/derivation/extract.py`)
|
||
or **downstream** (`clauses` / `compose` / `multistep` / `search` / `verify`). Then map
|
||
the minimal productions the **derivation/** composer still needs. Per the brief, this is
|
||
the audit feeding ADR-0207's "ADR-0179 extraction richness" execution lever; it touches
|
||
no code.
|
||
|
||
> **Reader-disjointness caveat (load-bearing).** ADR-0179 enriches the **sealed,
|
||
> practice-only derivation reader** (`generate/derivation/*`). `chat/` does not import it,
|
||
> so none of this moves the serving metric. The serving `6/44/0` comes from the **disjoint
|
||
> candidate-graph reader** (`generate/math_candidate_graph.py`). A case can be
|
||
> serving-correct and derivation-starved at once (0003, 0024 below). This audit is about
|
||
> the derivation reader, as Task 3 specifies.
|
||
|
||
---
|
||
|
||
## 1. The headline: ADR-0179's §Context table is stale
|
||
|
||
ADR-0179 (dated 2026-05-28, when serving was `3/47/0`) diagnoses a thin extractor. The
|
||
tree has moved. `generate/derivation/extract.py`'s own module docstring + live probes
|
||
confirm the following are **already integrated**:
|
||
|
||
| ADR-0179 sub-phase | In tree? | Evidence (live) |
|
||
|---|---|---|
|
||
| **EX-1** word-numbers (`three` → 3.0, hyphen compounds) | **LANDED** | `_WORD_QTY_RE`; `0024` extracts `(3.0,'times','three')` |
|
||
| **EX-2** currency/decimal + **bare-decimal grounding** | **LANDED** | `math_roundtrip.py:362` "ADR-0179 EX-2"; `_value_grounds("0.75")` → `True`; `0003` extracts `(0.75,'','0.75')` |
|
||
| **EX-4** list-unit inheritance (trailing unit) | **LANDED** | `_LIST_WITH_TRAILING_UNIT_RE` |
|
||
| **EX-5** sentence-final / unit-less number | **LANDED** | `_FINAL_NUMBER_RE` |
|
||
| **EX-6** hyphen-bonded number-unit (`25-foot`) | **LANDED** | `_HYPHEN_QTY_RE` (ADR-0163-F2) |
|
||
| Unit hygiene (function-word filter: `$0.75 each`→blank, not `each`) | **LANDED** | `_NON_UNIT_WORDS`; `0003`/`0024` blank the adjunct unit |
|
||
| **EX-3** multi-word units (`jumping jacks`, `stop signs`) | **DEFERRED** | `TestEX3StillDeferred` pins two documented traps (below) |
|
||
|
||
So **EX-3 (multi-word units) is the only open extraction production.** It was deferred
|
||
deliberately, not overlooked — see §3. ADR-0207 should cite ADR-0179 with this
|
||
reconciliation, not the stale §Context framing.
|
||
|
||
---
|
||
|
||
## 2. Per-case verification — extraction layer vs downstream
|
||
|
||
Live extraction (`extract_quantities`) and end-to-end derivation composer
|
||
(`search_multiplicative` / `compose_sequential` / `search_chain`) at HEAD:
|
||
|
||
### 0003 — `$0.75` erasers (gold 864) — **extraction CLOSED; ADR-0179's diagnosis is stale**
|
||
- Extracts `(48,'boxes')`, `(24,'erasers')`, `(0.75,'')` — the decimal **grounds** (EX-2).
|
||
- Derivation `search_chain` → **864.0 = gold.** The case the ADR says "refuses" now **solves**
|
||
in derivation. (Serving also correct, fast-path.)
|
||
- **Residual: none.** This case is a positive proof EX-2 landed and works end-to-end.
|
||
|
||
### 0024 — Sidney jumping jacks (gold 438) — **purely extraction-blocked (EX-3 + head inherit)**
|
||
- Extracts `(20,'jumping')`, `(36,'')`, `(40,'')`, `(50,'')`, `(3.0,'times','three')`.
|
||
EX-1 caught `three`; but `"jumping jacks"` → `"jumping"` (multi-word truncation), and the
|
||
list states its unit **once at the head** (`"20 jumping jacks on Monday, 36 on Tuesday…"`),
|
||
so the trailing-unit EX-4 rule does not fire. Units are non-uniform (`jumping` vs `''`).
|
||
- Derivation: all composers → **None** (GB-2 same-unit list-sum never fires).
|
||
- **Residual: extraction** — a *head*-unit list-inheritance production + a bounded multi-word
|
||
unit. (Serving solves 0024 independently; this gap is derivation-only.)
|
||
|
||
### 0016 — Rudolph stop signs (gold 2) — **both layers**
|
||
- Extracts `(2,'')`, `(5,'miles')`, `(3,'')`, `(17,'stop')` — `"stop signs"` → `"stop"`
|
||
(EX-3 truncation), and `"2 more than 5"` / `"3 less than 17"` are extracted as **separate
|
||
bare numbers**, not composed operands.
|
||
- Derivation `search_chain` → 510.0 (≠ gold 2); the self-verify gate refuses it (no wrong
|
||
commit — wrong=0 holds). The raw 510 is a non-committed candidate.
|
||
- **Residual: extraction (EX-3 `stop signs`) AND downstream** — word-arithmetic derived
|
||
operand (`N more/less than M`) and a `"per X"` division question target. Extraction alone
|
||
will not solve 0016.
|
||
|
||
### 0033 — Rachel's father's age (gold 60) — **purely downstream**
|
||
- Extracts `(12,'years')`, `(7.0,'times','7')`, `(5,'years')`, `(25,'years')` — **complete.**
|
||
`"Rachel is 12 years old"` grounded cleanly (the `years old` postmodifier did not corrupt
|
||
it: only the first content word `years` is taken).
|
||
- Derivation: all composers → **None**. The block is the multi-step age chain
|
||
(grandfather = 7×12 = 84; mother = ½·84 = 42; father = 42+5 = 47) plus the **temporal
|
||
offset** `"when she is 25"` (Rachel +13 → father 60).
|
||
- **Residual: downstream only** (compose/multistep). Extraction needs nothing here.
|
||
|
||
### Summary table
|
||
|
||
| Case | Serving | Derivation @ HEAD | Block locus | Genuine residual |
|
||
|---|---|---|---|---|
|
||
| 0003 | correct | **solves 864** | — | none (EX-2 closed it) |
|
||
| 0024 | correct (438) | refuses | **extraction** | EX-3 multi-word + head-unit inheritance |
|
||
| 0016 | refused | gate-refused (raw 510) | **both** | EX-3 + word-arithmetic operand + division target |
|
||
| 0033 | refused | refuses | **downstream** | multi-step chain + temporal offset |
|
||
|
||
**Of the four cases ADR-0179 names as extraction-starved, exactly one (0024) is purely an
|
||
extraction problem today.** 0003 is already solved, 0033 is purely downstream, 0016 needs
|
||
both. This is the single most important correction the audit surfaces: extraction richness
|
||
is largely *shipped*; the remaining derivation coverage is now mostly a **composition**
|
||
problem, not an extraction one.
|
||
|
||
---
|
||
|
||
## 3. Why EX-3 was deferred (the two traps — preserved, not re-litigated)
|
||
|
||
`extract.py`'s docstring + `TestEX3StillDeferred` (`tests/test_adr_0179_extract.py`) pin two
|
||
failure modes that defeat the tightest lookahead rule the ADR-0165 lexeme constraint admits:
|
||
|
||
1. **Connective-crossing.** A greedy lowercase unit span eats the connective:
|
||
`"6 apples and 4 apples"` → unit `"apples and"`, regressing GB-2 same-unit detection.
|
||
2. **Postmodifier-adjective tails.** Even `digit + word + word + (?=terminator)` fires on
|
||
`"25 years old?"` → `"years old"` instead of `"years"`, regressing
|
||
`test_adr_0176_ms1_question_target`. Endemic across 0006/0033 and MS2 chain tests.
|
||
|
||
Any EX-3 proposal must clear both without regressing those pinned tests.
|
||
|
||
---
|
||
|
||
## 4. Minimal productions proposed (analysis only — no code)
|
||
|
||
Ordered by leverage / safety. All are lexeme-level (ADR-0165), refuse-preferring
|
||
(over-extraction can only cost a *refusal* through the self-verify gate, never a wrong
|
||
commit), and each is independently shippable behind the existing derivation gate.
|
||
|
||
### P1 — Head-unit list inheritance (unblocks 0024; sidesteps both EX-3 traps)
|
||
Symmetric to the landed trailing-unit EX-4, but for the unit stated **once at the list
|
||
head**: `"<n0> <UNIT-PHRASE> <adjunct>, <n1> <adjunct>, … <nk> <adjunct>"` → attach
|
||
`<UNIT-PHRASE>` to the unit-blank members `n1…nk`. Bound the head `<UNIT-PHRASE>` on the
|
||
right by a **closed adjunct stop set** `{on, at, in, per, each, of, and}` — so
|
||
`"20 jumping jacks on Monday"` yields unit `"jumping jacks"`, stopping cleanly before `on`.
|
||
This makes 0024 a uniform-unit list → GB-2 list-sum fires, **without** general multi-word
|
||
capture, so neither §3 trap is touched. Highest leverage, lowest risk.
|
||
|
||
### P2 — Bounded 2-word content unit with a closed measurement-postmodifier stop list
|
||
The general `"stop signs"` shape (0016 extraction half). Allow exactly two content words
|
||
**only when** the second is not in a closed stop list of measurement postmodifiers
|
||
`{old, tall, long, wide, deep, high, away, apart, ago, …}` **and** not a connective. This is
|
||
the production the Track-C brief judged "too open-ended"; the audit's position is that the
|
||
stop set is *enumerable and closed* if scoped to measurement postmodifiers, and must ship
|
||
with `TestEX3StillDeferred` converted to a positive/negative matrix (the two traps become
|
||
must-refuse rows). Medium leverage, medium risk — gate it behind P1.
|
||
|
||
### P3 — (explicitly **not** extraction — flagged, out of Task-3 scope)
|
||
0016's `"N more/less than M"` word-arithmetic operand and `"per X"` division question
|
||
target, and 0033's multi-step age chain + temporal `"when she is 25"` offset, are
|
||
**composition** concerns for `compose.py` / `multistep.py` / `search.py`, not `extract.py`.
|
||
The audit names them so they are not mistaken for extraction work: feeding richer quantities
|
||
will not solve 0016/0033 without these downstream productions.
|
||
|
||
---
|
||
|
||
## 5. Bottom line for ADR-0207
|
||
|
||
- ADR-0179's "extraction is too thin" framing is **mostly resolved in-tree** (EX-1/2/4/5/6 +
|
||
unit hygiene landed). Cite the reconciled state, not the stale §Context table.
|
||
- The one open extraction lever is **EX-3**, and its highest-value, lowest-risk slice is
|
||
**P1 (head-unit list inheritance)** — it unblocks 0024 in derivation with no EX-3-trap
|
||
exposure.
|
||
- The broader Phase-5b coverage wall is now **composition** (P3), not extraction. ADR-0207's
|
||
two diagnosed levers (ADR-0179 extraction richness + ADR-0164 lexicon expansion) should be
|
||
read with this correction: extraction richness is near-complete; the residual lift is in
|
||
the comprehension/derivation composer, gated by self-verification (wrong=0 preserved).
|
||
|
||
**Invariants observed by this audit:** read-only; train_sample reproduced live `6/44/0`;
|
||
no serving code touched; the no-ref `<N>×` hazard remains refused (untouched).
|