core/docs/analysis/wiring-promotion-gate-brief-2026-06-03.md
Shay 5ae377065e docs: composition-wall execution scope (ADR-0207 §5 step 2)
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.
2026-06-03 21:14:02 -07:00

11 KiB
Raw Blame History

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:

  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.

  • 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).