Three reviewed, non-serving analysis/handoff docs for the COMPOSITION lever. Read-only; no serving code; train_sample 6/44/0 untouched. - analysis/wiring-promotion-gate-brief: product_bridge is the only serving derivation tendril (two-token target whitelist); resolve_pooled refuses all ten R1/R4/R5/R6 positives and wrong-commits 0016 -> 510. Wiring is trivial; the gate is the work. - analysis/composition-wall-execution-plan: per-case stage taxonomy (A/B/C/D) for all 15 corpus positives, reproduced live; R4-first scope (new goal-residual production, verified by code-read of accumulate.py, NOT a target tweak); promotion gate generalizes product_bridge, never disagreement alone; gates inherit ADR-0207 §6 (corpus + sealed 1,319). - handoff/stage-c-composition-investigation: dispatch task for the execution lane to pin the per-case bail for the production-wrong shapes (the real grouping/op-order wall), read-only, in-tree.
11 KiB
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).
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:
- The general composition entry already exists — it is
resolve_pooled(text), not a thing to build. (The brief's earlier "there is noresolve_composition" was wrong.) - The general self-verify gate already exists and is composition-agnostic
(
select_self_verified: grounding ∧ cue ∧ unit ∧ completeness ∧ uniqueness). product_bridgeis already the promotion gate for one shape (pure products) and is already wired atmath_candidate_graph.py:530. The wiring lever, for what the composer can safely certify today, is already done.- The composer refuses every R1/R4/R5/R6 target in the validation corpus (§3). So
wiring
resolve_pooledyields zero lift on the target shapes — there is nothing correct yet to promote. - Wiring
resolve_pooledwholesale is a livewrong=0regression — it commits0016 → 510(gold 2) (§3). The promotion gate is mandatory from the first wire.
Therefore: WIRING and COMPOSITION (§5 steps 1–2) 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:524–540 |
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.
0016is 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 — awrong=0violation.product_bridge's_has_hazard_surfaceblocks0016today 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:
_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 stops0016.)resolve_pooled— the pooled candidate + disagreement rule._is_complete_pure_product— every stepmultiply, non-comparative,classify == "complete"._has_product_target— a near-whitelist of two target shapes:money … make/earn, orweight … total/movewith 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_bridgeat :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:
- 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). - General self-verify (reuse, don't reinvent):
select_self_verified— grounding ∧ cue ∧ unit ∧ completeness ∧ uniqueness. - 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. - Hazard firewall (generalize layer 1): the residual
wrong=0hazards that survive self-verify (the0016-class: a unique complete reading that is still wrong) must be refused. The0016 → 510commit is the mandatory first firewall test — the gate is not done untilresolve_promotable_compositionrefuses 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_candidatesalready exists andcompose_accumulationis wired into the pool). Confirm whether the composer can build0037/0035at 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_derivationtrace read (the next brief). - Open Q2: can
extract_targetexpress the R4/R5/R6 target op-classes (residual, duration-sum, percent-of) precisely enough to replace product_bridge's whitelist without admitting a0016-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).