Merge pull request #542 from AssetOverflow/docs/exec-scope-composition-wall

docs: composition-wall execution scope (ADR-0207 §5 step 2)
This commit is contained in:
Shay 2026-06-03 21:24:54 -07:00 committed by GitHub
commit f7e95be211
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 407 additions and 0 deletions

View file

@ -0,0 +1,172 @@
<!-- CANONICAL | composition-wall-execution-plan-2026-06-03.md | 2026-06-03 | spec-and-review (Opus) lane | read-only analysis / execution plan for ADR-0207 §5 step 2 | verified against origin/main d9fc7f9 | NOT a new design -->
# Composition Wall — Execution Plan (R1/R4 first)
This is the execution scope for **ADR-0207 §5 step 2 (COMPOSITION)***not* a new design. It
takes the ratified substrate as given and answers the only question that matters next: *what,
concretely, must change in the built `generate/derivation/` composer to move the live metric,
and where is each target shape actually blocked?* Every per-case claim below was reproduced
read-only against the tree on 2026-06-03 by running the actual composer on the corpus cases.
## 1. The wall, restated from reproduction (folds in the wiring brief)
- `resolve_pooled` (the general pooled composer, `pool.py:62`) **refuses all ten R1/R4/R5/R6
corpus lift-targets**, so wiring it to serving yields **+0 correct**; and it **wrong-commits
`0016 → 510`** (gold 2), so wiring it wholesale is a live `wrong=0` regression.
- The only derivation symbol on serving is `product_bridge.resolve_promotable_product`
(`math_candidate_graph.py:530`), and its safety is a **two-token target near-whitelist**
(`money`+`make/earn`, `weight`+`total/move`) — it does not generalize.
- Therefore **WIRING is trivial and already done for the safe shapes**; the actual work is
making the composer *build the right chain for a new shape and certify it*. The wire is the
last, one-line step (mirror the `:530` block) once a shape builds-and-certifies.
## 2. Grounded per-shape diagnosis (all 15 corpus positives)
Ran `extract_quantities``extract_target` → the three composers
(`accumulation_candidates` / `multiplicative_candidates` / `candidate_chains`) on each. "Stage"
is where the shape is blocked (see §3). Golds are dataset-sourced (corpus invariant 6).
| case | comp | gold | #qty | target-agg | acc/mul/chn | result | **stage** |
|---|---|---|---|---|---|---|---|
| cv-0001 | R1 | 64 | 1 | None | 0/0/0 | none | **A** extraction (`$16` not extracted) |
| cv-0003 | R1 | 9 | 6 | None | 0/0/0 | none | **B** target |
| cv-0002 | R1 | 400 | 3 | total | 0/0/4 | wrong-only | **C** production |
| cv-0004 | R1 | 3840 | 4 | None | 0/1/4 | wrong-only | **C** production |
| cv-0005 | R4 | 3 | 3 | None | 0/0/0 | none | **B** target |
| cv-0021 | R4 | 4 | 4 | None | 0/0/0 | none | **B** target |
| cv-0006 | R5 | 14 | 2 | None | 3/0/4 | wrong-only | **C** production |
| cv-0022 | R5 | 38 | 2 | total | 0/1/4 | wrong-only | **C** production |
| cv-0007 | R6 | 21 | 3 | None | 0/0/0 | none | **B** target |
| cv-0008 | R6 | 15 | 1 | None | 0/0/0 | none | **A**+**B** extraction+target |
| cv-0009 | compare_mult | 60 | 1 | altogether | 0/0/0 | none | **A** extraction |
| cv-0018 | compare_mult | 28 | 2 | None | 0/0/2 | wrong-only | **C** production |
| cv-0019 | additive | 1200 | 2 | None | 0/0/0 | none | **B** target |
| cv-0017 | *(control)* | 438 | 5 | None | 0/0/2 | wrong-only | control — solves on serving |
| cv-0020 | *(control)* | 450 | 3 | total | 0/1/2 | **GOLD-BUILT** | control — **D** gate exemplar |
Controls (cv-0017=case 0024, cv-0020=case 0021) already solve on serving via other paths; they
are regression anchors, not lift targets. cv-0020 is the **proof the build-then-gate pattern
works**: the pool *builds the gold* (450) but only commits it on serving because
`product_bridge`'s gate promotes it.
## 3. The four-stage failure taxonomy (and the `wrong=0` firewall at each)
The composer is a pipeline: **extract → characterize target → build production → certify (gate)**.
A shape can be blocked at any stage; each stage refuses (never commits) when incomplete, so
`wrong=0` holds throughout.
- **Stage A — extraction-thin.** The composer is starved of a needed quantity, so no chain is
possible. *cv-0001* (currency literal `$16` not extracted — the derivation reader has no `$N`
production; this is the gap I earlier *overstated as closed*), *cv-0008*, *cv-0009*. Firewall:
no quantity → no candidate → refuse.
- **Stage B — target-uncharacterized.** Quantities are present but `extract_target` returns
`aggregation=None`, so the composer doesn't know *what* to build. *cv-0003, cv-0005, cv-0007,
cv-0019, cv-0021*. **Both R4 cases live here.** Firewall: no target → no admissible chain → refuse.
- **Stage C — production-wrong.** The composer builds chains, but the *wrong* ones (none equals
gold), and the disagreement rule correctly refuses them. *cv-0002, cv-0004, cv-0006, cv-0018,
cv-0022.* This is the hard core — "which quantities group, via which ops, in what order."
Firewall: disagreement among wrong candidates → refuse.
- **Stage D — gold-built, needs gate.** The composer builds the gold (possibly among rivals); a
*structural* promotion gate certifies and commits it. Exemplar: *cv-0020* via `product_bridge`.
Firewall: the gate must prove the reading; a lone wrong chain with no rival (the *0016* pattern)
is exactly what disagreement cannot catch, so **the gate must be structural, never
disagreement alone.**
## 4. Execution sequence — one shape, end-to-end, instrumented
Each shape is a **hypothesis to test against the gates**, not a guaranteed win. Drive *one* shape
through its blocked stages to green on the corpus, then check the sealed 1,319, before starting
the next.
1. **R4 first — but it is NEW PRODUCTION, not a target tweak** (verified 2026-06-03 by code-read
of `accumulate.py`: `_build_accumulation` is single-referent gain/loss running-total with **no
goal/target/residual concept** — target characterization alone does **not** unblock it). cv-0005
(single-referent `goal Σchanges`) is the nearest extension of GB-3b.1 and the first build;
cv-0021 (multi-referent give-away to a remainder) is two layers harder — separate, do not bundle.
Goal-recognition is a new `wrong=0` surface (must refuse on ambiguous goal-language), and beware
cv-0005 passing for the wrong reason (`1034=3` also falls out of treating the goal as a start);
the gate must consume the goal *as a goal*, and the real test is the sealed set, not cv-0005.
Detailed first-build scope: **§10**.
2. **R1 next (cv-0001) — Stage A (extraction).** Add currency-literal (`$N`) extraction to the
derivation reader, then test whether the comparative-multiplicative production (multiplier ×
base) fires and builds `4×16=64`. Extraction-first; different stage from R4, so it exercises a
different pipeline leg.
3. **Then Stage C shapes (cv-0002/0004/0006/0022)** — the genuine wall: building the correct
grouping/op-order. Highest risk, do last, with the most instrumentation. Investigation task:
`docs/handoff/stage-c-composition-investigation-2026-06-03.md`.
## 5. The promotion gate (generalize `product_bridge`, do not relax disagreement)
Every new production needs a co-designed structural gate, modeled on `product_bridge` but real:
- Use `extract_target` + `target_units` to prove the built chain answers *the asked question in
the asked units* (op-class + target + unit agreement), replacing the two-token whitelist with a
general op-class/target proof.
- A chain may commit only if it is the unique reading that *structurally* certifies; otherwise
refuse. **Disagreement is not the defense***cv-0016/0016* shows a lone wrong chain commits
with no rival to disagree. The gate, not the pool, is the `wrong=0` firewall for committed
compositions.
## 6. Acceptance gates (inherit ADR-0207 §6 verbatim)
train_sample stays `wrong=0` (6/44/0 or better); the no-reference `<N> times` hazard stays
refused; no partial chain commits with an unbound quantity; **progress measured on the 22-case
corpus AND the sealed 1,319** — a train_sample/corpus gain that does not move held-out does not
count; each gain lands as composer/extractor enrichment behind a gate, audited to confirm it is
**not** a new serving recognizer/injector branch (the §4 freeze).
## 7. Honest uncertainty
This is the research wall, not engineering. The payoff against the **sealed 1,319** is genuinely
unknown: the composer building gold on a corpus case does not mean the same production fires on
held-out paraphrases, and Stage C (correct grouping/op-order) is an open research problem, not a
known build. I can scope the work and the gates; I cannot promise R1/R4 transfer until the
productions exist and the sealed number actually moves. The gates (§6) are what keep that honest
— they will tell us the truth either way.
## 8. Correction recorded
I earlier asserted "extraction is substantially done; only EX-3 deferred; extraction is not the
open lever." Stage A (cv-0001: `$16` present in text, not extracted) shows that was overstated —
the ADR-0179 lever was largely shipped *for its four cases*, but per-shape extraction gaps remain
(currency literals at minimum). This does not unwind ADR-0207; the per-shape gaps are execution
detail and live here, not in the ratification ADR.
## 9. Reproduction-methodology correction
The `target-agg` column was first computed by passing the **full problem text** to
`extract_target`; serving passes only the **question clause**. For cv-0004 and cv-0006 the
aggregation word is in the body, not the question, so full-text wrongly read `combined`/`total`
where the serving-faithful call returns `None` (now corrected above). The other agg cells are
unaffected (their agg word is in the question). This changes no stage (both are Stage C) and no
claim. Methodology fix carried forward: reproduce serving behaviour with serving's inputs —
`extract_target` gets the question clause, the composers get the full problem.
## 10. R4 first-build scope (from code-read of `accumulate.py`)
R4 is **new production, not a target tweak** — confirmed by reading the source (not a trace):
`compose_accumulation` / `_build_accumulation` (GB-3b.1) computes single-referent gain/loss
*running-total* `start ± changes` and consults **no goal/target/residual concept**. Hard bails:
the anchor must establish exactly one quantity (`accumulate.py:70`); a new named actor refuses
(`continues_anchor_referent`, `:76`/`:143`); no unambiguous licensed change cue refuses
(`:87`/`:149`). So R4's `goal accumulated` cannot be expressed regardless of target — both R4
cases build **0 candidates** at `d9fc7f9e` (latent, nothing passes for any reason yet).
- **cv-0005 first (single-referent, nearest extension of GB-3b.1).** A new production recognizing
(a) a **goal** quantity as a distinct anchor type (*not* a running total), (b) accumulated
same-referent changes, (c) a **residual** question target, computing `goal Σchanges`. Single
referent (Michael), so it reuses GB-3b.1's referent/cue guards.
- **cv-0021 second (harder).** Multi-referent give-away to a remainder —
`(start desired_remainder) Σgiven`. Outside GB-3b.1's single-referent scope (refused at
`continues_anchor_referent`); needs multi-referent accumulation **and** residual handling. Two
layers beyond cv-0005; do not bundle.
- **Gate (mandatory, co-designed).** The residual chain commits only if `extract_target`
structurally proves the residual question + op-class + unit agreement, and completeness consumes
the **goal** quantity. Never disagreement alone (the 0016 firewall).
- **`wrong=0` hazard (latent, build-time guard).** `10 3 4 = 3` also falls out of
*mis-anchoring* the goal as a running-total start — coincidental correctness. The production must
consume the goal *as a goal*; the real test is the **sealed set**, not cv-0005's arithmetic.
- **Build-time discipline.** Whoever builds instruments the exact `_build_accumulation` interaction
and the new production's path through `self_verifies` (completeness must require the goal
consumed) **in-tree** — the code-read scopes *what* to build; the runtime path confirms *that it
fires* before the one-line wire.

View file

@ -0,0 +1,166 @@
<!-- analysis | wiring-promotion-gate-brief | 2026-06-03 | read-only, no code | ADR-0207 §5 step 1 execution brief -->
# WIRING + Promotion-Gate Brief — feeding the derivation composer into serving
**Status:** Analysis only. No serving, eval, or code edits. Every claim reproduced
read-only against `origin/main` @ `2cb0922` (corpus + branch state per PR
[core#536](https://github.com/AssetOverflow/core/pull/536)).
**Scope.** ADR-0207 §5 step 1 (WIRING) is the named next lever: connect the disjoint
`generate/derivation/` composer to the serving candidate-graph so its output counts
toward 6/44/0. This brief reads the actual integration surface — `product_bridge.py`,
`pool.py`, `verify.py`, `target.py`, the `math_candidate_graph.py:530` boundary — and
defines what the wiring *and its promotion gate* must be. It also reports an empirical
finding that **partly corrects ADR-0207 §5's sequencing claim**; see §5.
---
## 1. Bottom line
"The wiring is trivial, the gate is the work" is correct — but incomplete, and the
honest version is sharper:
1. **The general composition entry already exists** — it is `resolve_pooled(text)`, not a
thing to build. (The brief's earlier "there is no `resolve_composition`" was wrong.)
2. **The general self-verify gate already exists and is composition-agnostic**
(`select_self_verified`: grounding ∧ cue ∧ unit ∧ completeness ∧ uniqueness).
3. **`product_bridge` is already the promotion gate for one shape (pure products) and is
already wired** at `math_candidate_graph.py:530`. The wiring lever, for what the
composer can safely certify *today*, is **already done**.
4. **The composer refuses every R1/R4/R5/R6 target in the validation corpus** (§3). So
wiring `resolve_pooled` yields **zero lift** on the target shapes — there is nothing
correct yet to promote.
5. **Wiring `resolve_pooled` *wholesale* is a live `wrong=0` regression** — it commits
`0016 → 510` (gold 2) (§3). The promotion gate is mandatory from the first wire.
**Therefore:** WIRING and COMPOSITION (§5 steps 12) are **not separable**, and WIRING is
**not the lowest-risk first step** as ADR-0207 §5 states. The safe wire already exists
(`product_bridge`); extending it to a new shape requires the composer to *build that
shape's chain* (it currently refuses) **and** a promotion gate to *certify it*, in
lockstep. The gate and the composition capability are the same problem (§6).
## 2. The verified integration surface
| Component | What it is | Serving-wired? |
|---|---|---|
| `math_candidate_graph.py:524540` | The ADR-0195 boundary: `r = resolve_promotable_product(text); if r is not None: return CandidateGraphResult(answer=r.answer, branches_enumerated=1, branches_admissible=1)` | **yes** (the one tendril) |
| `derivation.pool.resolve_pooled(text) → Resolution \| None` | General composition resolver: pools accumulation + multiplicative + chain candidates, classifies each `complete`/`exempt`/`None`, refuses on disagreement or exempt-only, guards `asks_prior_state` | no (sealed) |
| `derivation.verify.select_self_verified(derivs, text, target_units=…)` | Composition-agnostic gate: grounding ∧ cue ∧ unit ∧ completeness, then uniqueness/disagreement; `target_units` drops chains answering the wrong dimension | no (sealed) |
| `derivation.verify.classify_derivation` | ADR-0182 commit-eligibility: `complete` (resolves) / `exempt` (pool-only, forces disagreement) / `None` (fails a clause) | no |
| `derivation.target.extract_target(question, known_units) → Target` | Question-target extraction (a general analogue of product_bridge's hardcoded target check) | no |
| `product_bridge.resolve_promotable_product` | The promotion gate for **one shape** (pure products) — see §4 | **yes** |
**Wiring shape (mechanically trivial, confirmed).** A second block at the same `:530`
boundary: `c = resolve_promotable_composition(text); if c is not None: return
CandidateGraphResult(answer=c.answer, branches_enumerated=1, branches_admissible=1)`.
The work is entirely inside `resolve_promotable_composition` — the gate.
## 3. The empirical crux (live `resolve_pooled`, all corpus positives)
Ran `resolve_pooled` on the validation corpus's composition positives + diagnostics:
| Cases | Composition | `resolve_pooled` result |
|---|---|---|
| 0029, 0038, 0008, 0027 | R1 | **refuse** (×4) |
| 0037, 0035 | R4 | **refuse** (×2) |
| 0030, 0015 | R5 | **refuse** (×2) |
| 0005, 0046 | R6 | **refuse** (×2) |
| 0033 | downstream | refuse |
| 0024 | extraction (EX-3) | refuse |
| 0040 | world-knowledge | refuse |
| **0016** | hazard-both | **COMMIT 510 — WRONG (gold 2)** |
Two facts fall out, both load-bearing:
- **Zero lift from wiring.** The pool commits *nothing correct* on the 10 R1/R4/R5/R6
positives — it refuses all of them. The composers cannot yet assemble a committable
complete-unique chain for these shapes. Wiring the pool moves 6/44/0 by **+0 correct**.
- **A live wrong commit.** `0016` is a unique complete self-verifying reading that is
*wrong* (the chain `… → 510`, no rival to trigger the disagreement rule). This is
exactly the "known wrong commits" `product_bridge`'s docstring warns of. Wiring the pool
*ungated* makes serving **6/43/1** — a `wrong=0` violation. `product_bridge`'s
`_has_hazard_surface` blocks `0016` today via its `{less, more, per, …}` cues.
## 4. What `product_bridge` actually proves (the gate to generalize)
`resolve_promotable_product` is a **four-layer** promotion gate over `resolve_pooled`:
1. `_has_hazard_surface` — a refuse-preferring **blocklist**: comma-thousands, `%`/percent,
text blockers `{left, more, less, gave, spent, profit, remaining, …}`, question
blockers `{per, after, before, remaining, profit, …}`. (This is what stops `0016`.)
2. `resolve_pooled` — the pooled candidate + disagreement rule.
3. `_is_complete_pure_product` — every step `multiply`, non-comparative, `classify == "complete"`.
4. `_has_product_target` — a **near-whitelist of two target shapes**: `money … make/earn`,
or `weight … total/move` with a mass unit.
**The tell:** layer 4 is almost hardcoded to 0003 (revenue) and 0021 (weight). Its safety
comes from its narrowness — it admits essentially two target forms. **This does not
generalize.** A composition gate for R4/R5/R6 cannot copy a two-entry target whitelist; it
needs a *general* op-class/target proof (the natural tool is `extract_target` +
`select_self_verified(target_units=…)`), and that proof is the hard part.
## 5. Correction to ADR-0207 §5 (flagged, not buried)
ADR-0207 §5 step 1 calls WIRING "the crux … the lowest-risk of the execution steps … it's
wiring, with `product_bridge` already proving the pattern … the central task, not a
preliminary." The verified surface refines this:
- **The safe wire is already done** (`product_bridge` at :530). What remains to wire is, by
shape, gated on composition capability that **does not exist yet** (the pool refuses every
R4/R5/R6 target).
- **WIRING is not lower-risk than COMPOSITION** — they are the same step. The risk lives in
the promotion gate, which is where "which composed readings are safe to promote" = "which
readings the composer got right" = the §5 step-2 composition wall.
- **The ungated pool is `wrong=0`-negative** (0016), so "just wire it in" is not a safe
preliminary.
**This does not unwind the ratification.** ADR-0207's core — freeze the regex serving path,
design-of-record, the `wrong=0` gates, measure on the sealed set — all stand. The
correction is to §5's *sequencing optimism*. Recommended: amend §5 step 1 to "extend the
`product_bridge` promotion-gate pattern to one new composition shape, in lockstep with the
composer capability for it; never wire the pool ungated," or carry this brief as the §5
execution refinement. Operator's call which.
## 6. The promotion-gate contract (what to build, no code)
`resolve_promotable_composition(text) → Resolution | None`, mirroring `product_bridge` but
**per chosen shape**, must prove all of:
1. **Candidate source.** Obtain the shape's candidate (via `resolve_pooled`, or a
shape-scoped composer call) — only if the composer actually *builds* it (today: none of
R4/R5/R6, so this gate is empty until composition lands).
2. **General self-verify** (reuse, don't reinvent): `select_self_verified`
grounding ∧ cue ∧ unit ∧ completeness ∧ uniqueness.
3. **Target proof** (generalize layer 4): the question's target op-class matches the
chain's, via `extract_target` + `target_units` — not a per-shape whitelist.
4. **Hazard firewall** (generalize layer 1): the residual `wrong=0` hazards that survive
self-verify (the `0016`-class: a unique complete reading that is still wrong) must be
refused. The `0016 → 510` commit is the **mandatory first firewall test** — the gate is
not done until `resolve_promotable_composition` refuses it.
**`wrong=0` boundary contract:** every corpus case + every sealed case either **refuses or
returns exactly gold**. Proven on the 22-case `composition_validation/v1` corpus (the
instrument) **and** the sealed 1,319 (the real bar, ADR-0207 §6). A train_sample/corpus
gain that does not move the sealed set is overfitting and does not count.
## 7. Recommended first slice + open questions
- **First slice:** pick the *one* shape whose composer is closest to building a correct
committable chain (candidate: R4 accumulation/residual — `accumulation_candidates` already
exists and `compose_accumulation` is wired into the pool). Confirm whether the composer
can build `0037`/`0035` at all (today: pool refuses both — so step 0 is *composer* work,
not gate work). Only once a correct chain exists does the gate become the lever.
- **Open Q1:** *why* does the pool refuse the R4/R5/R6 positives — is it a missing composer
production (no chain built) or a gate over-refusal (chain built but fails completeness/
uniqueness)? This split decides whether step 1 is composer or gate work, and it needs a
per-case `pooled_candidates` + `classify_derivation` trace read (the next brief).
- **Open Q2:** can `extract_target` express the R4/R5/R6 target op-classes (residual,
duration-sum, percent-of) precisely enough to replace product_bridge's whitelist without
admitting a `0016`-class wrong? This is the generalization risk.
- **Open Q3:** does any composer *currently* produce a unique-complete wrong on a sealed
case (not just 0016)? The hazard firewall must be characterized on the sealed set, not
only the corpus, before any pool output is wired.
**Invariants observed by this brief:** read-only; `train_sample` 6/44/0 reproduced live;
no serving code touched; the no-ref `<N> times` hazard and 0016 remain refused on the
serving path (product_bridge blocks 0016).

View file

@ -0,0 +1,69 @@
<!-- handoff | stage-c-composition-investigation | 2026-06-03 | execution-lane dispatch task | read-only investigation, no code | input: composition-wall-execution-plan-2026-06-03.md -->
# Stage-C Composition Investigation — dispatch task (execution lane)
**Type:** Read-only, in-tree investigation. **No code changes.** Output is a per-case fix-spec.
**Dispatch:** execution lane (this is the fine-grained runtime tracing the spec-and-review lane
deliberately does *not* do from a sandbox — it must run the real code paths in-tree).
**Input:** [composition-wall-execution-plan-2026-06-03.md](../analysis/composition-wall-execution-plan-2026-06-03.md).
**Base:** current `origin/main` (`git fetch && git reset --hard origin/main` first — the tree
moves constantly; verify the SHA you run against and record it).
## Why this is an execution-lane task, not a sandbox one
The execution plan's stage taxonomy is a verified **symptom-map** (what's blocked, by candidate
count). It is **not** a fix-spec. Pinning *why* a Stage-C production builds the wrong chain needs
instrumenting the real production internals — the kind of fine-grained trace that has been
unreliable from the spec lane's sandbox. Run the actual functions, in-tree, faithfully.
## The targets (Stage C — production-wrong)
The composer builds chains but only **wrong** ones (none equals gold); the disagreement rule then
correctly refuses. Five cases, from the plan's §2 table:
| case | comp | gold | acc/mul/chn | what it builds instead (plan) |
|---|---|---|---|---|
| cv-0002 | R1 | 400 | 0/0/4 | wrong chains only |
| cv-0004 | R1 | 3840 | 0/1/4 | wrong chains only |
| cv-0006 | R5 | 14 | 3/0/4 | wrong (e.g. accumulation + chain rivals) |
| cv-0022 | R5 | 38 | 0/1/4 | (plan: builds 80/160/18/36, none = 38) |
| cv-0018 | compare_mult | 28 | 0/0/2 | wrong chains only |
## What to produce, per case
For each of the five, using the **serving-faithful inputs** (`extract_target` gets the *question
clause*; the composers get the *full problem* — the §9 methodology fix):
1. **Enumerate every candidate** each composer builds (`accumulation_candidates`,
`multiplicative_candidates`, `candidate_chains`): the exact step list (op, operand, unit) and
answer of each, not just the count.
2. **Locate the gold chain.** Is the correct grouping/op-order *expressible* by the current
composer primitives at all? If a sequence of existing ops yields gold, the gap is **search/
ranking** (the production can build it but doesn't, or builds it and a wrong rival outvotes it).
If no sequence of existing ops yields gold, the gap is a **missing primitive/production**.
3. **Pin the divergence.** Where does the built (wrong) chain diverge from the gold reading — wrong
clause segmentation, wrong referent binding, wrong op for a relational cue, wrong grouping order?
Name the exact function and the exact decision (cite `file:line`).
4. **Minimal production fix.** State the smallest change that would make the gold chain *buildable
and uniquely certifiable*, and the **co-designed structural gate** that admits it without
admitting the wrong rivals (per plan §5 — `extract_target` + `target_units`, never disagreement
alone).
## Hard constraints (non-negotiable — from ADR-0207 §6 / plan §6)
- **Read-only.** No edits to `generate/` in this task. The output is a spec; building is a later,
separately-gated task.
- **`wrong=0` is the governor.** Any proposed fix must be argued to preserve: train_sample
`wrong=0` (6/44/0+), the no-reference `<N> times` refusal, and the 0016 firewall (a lone wrong
chain with no rival must still refuse — the gate, not disagreement, is the defense).
- **Sealed set is the real bar.** Note explicitly that building gold on a corpus case proves
nothing about transfer; the fix-spec must say what would be measured on the sealed 1,319.
- **Serving-faithful reproduction.** `extract_target(question_clause)`; composers get full text.
Record the `origin/main` SHA you ran against.
## Output
A markdown report, one section per case, with the four items above, ending in a ranked list:
which Stage-C case has the **smallest buildable gap** (search/ranking) vs which needs a **new
primitive** — so the build lane starts with the cheapest real lift. This report becomes the
fix-spec; it does not authorize a build.