core/docs/research/trackb-inc2-generalization-2026-07-20.md
Shay 2544fda6f6 fix(trackb): pure-S1 extract refuse multi-clause overmatch (0361 wrong=0)
The inverted-seed P2 regex treated optional "times" as optional, so
additive "8 more" on multi-entity zoo case 0361 extracted as pure S1 and
the three-gate corridor emitted 1.125 vs gold 114.0 (wrong≠0).

- Require explicit multiplicative surface (times as many / twice the …)
- Fail-closed purity gate: multi-clause markers, multi-numeric, multi-mult
- Regression tests for 0361 and bare "N more"
- measure --mode full-holdout-wrong0 over all 500 holdout cases

[Verification]: full-holdout emit_ok=9 wrong=0 refused=491; unit 37 passed
2026-07-19 20:04:27 -07:00

169 lines
8.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.

# Track B, Increment 2 — Generalization bar (S1S4, selector, coverage)
**Date**: 2026-07-20
**Branch**: `feat/trackb-symbolic-sme-s2s4`
**Paradigm**: ADR-0252
**Prerequisite**: Increment 1 (PR #85) merged; §5 geometric SME remains NO-GO
**Scope**: Off-serving. S1S4 canonicals + selector + SM-owned pure-S1 extract. No serving cutover. No organ retirement in this PR.
---
## 1. The bar (ADR-0252 §4 / Increment 2)
| # | Requirement | Result |
| --- | --- | --- |
| 1 | Generalization ratio > 1 on ≥1 canonical (real holdout IDs) | **PASS** — S1 ratio = **9.0** (9 cases / 1 template) |
| 2 | Coverage gain **or** organ retirement | **PASS** — coverage gain: **+4** holdout cases (organ 5 → trackb 9), `wrong=0` |
| 3 | Selector works (S1S4 held; ties refuse; surface≠structure) | **PASS** — routing table below |
Honest negatives (not failures of the bar):
- S2/S3/S4 holdout ratios = 0 — **parser frontier**: organ yields graphs on only 5/500 holdout cases (all S1).
- S3/S4 multi-register corridor cannot certify pure rebuilds yet (`apply_rate` / `compare_additive` out of scope) — map succeeds, emit refuses (gate held, not weakened).
- Organ retirement **not** performed (off-serving path; coverage-gain chosen).
---
## 2. What shipped
| Piece | Path |
| --- | --- |
| Canonicals S1S4 | `generate/structure_mapping/canonicals.py` |
| Role schema + `compare_add` | `generate/structure_mapping/role_predicate.py` |
| Converter (incl. additive compare) | `generate/structure_mapping/convert.py` |
| Pure-family mappers S1S4 | `generate/structure_mapping/mapper.py` |
| Overlapping-waves selector | `generate/structure_mapping/selector.py` |
| Generalized 3-gate solve | `generate/structure_mapping/solve.py` |
| SM-owned pure-S1 text extract | `generate/structure_mapping/text_extract.py` |
| Pipeline (organ parse → extract fallback) | `generate/structure_mapping/pipeline.py` |
| Measure script | `scripts/measure_trackb_inc2.py` |
| Tests | `tests/test_trackb_inc2_structure_mapping.py` (15) + Inc1 suite (20) |
Serving reader (`parse_and_solve` dispatch, organs, `chat/runtime.py`) **untouched**.
---
## 3. Measurements (command + real output)
### 3a. Generalization-ratio table
```text
$ uv run python scripts/measure_trackb_inc2.py --mode ratio
=== generalization ratio (holdout_dev/v1, command-backed) ===
CASE gsm8k-holdout-dev-v1-0101 sid=S1 emit=9.0 gold=9.0 right=True src=organ ...
CASE gsm8k-holdout-dev-v1-0108 sid=S1 emit=110.0 gold=110.0 right=True src=organ ...
CASE gsm8k-holdout-dev-v1-0148 sid=S1 emit=1500.0 gold=1500.0 right=True src=sm_extract pat=s1_p3_day_pair ...
CASE gsm8k-holdout-dev-v1-0228 sid=S1 emit=600.0 gold=600.0 right=True src=sm_extract pat=s1_p2_b_seeded ...
CASE gsm8k-holdout-dev-v1-0234 sid=S1 emit=240.0 gold=240.0 right=True src=sm_extract pat=s1_p2_deaf_blind ...
CASE gsm8k-holdout-dev-v1-0268 sid=S1 emit=16.0 gold=16.0 right=True src=organ ...
CASE gsm8k-holdout-dev-v1-0411 sid=S1 emit=52.0 gold=52.0 right=True src=organ ...
CASE gsm8k-holdout-dev-v1-0441 sid=S1 emit=75.0 gold=75.0 right=True src=sm_extract pat=s1_p2_b_seeded ...
CASE gsm8k-holdout-dev-v1-0453 sid=S1 emit=28.0 gold=28.0 right=True src=organ ...
--- ratio table ---
canonical=S1 templates=1 cases_carried=9 ratio=9.000 ids=[...9 holdout ids...]
canonical=S2 templates=1 cases_carried=0 ratio=0.000 ids=[]
canonical=S3 templates=1 cases_carried=0 ratio=0.000 ids=[]
canonical=S4 templates=1 cases_carried=0 ratio=0.000 ids=[]
SUMMARY active_canonicals=1 total_cases=9 aggregate_ratio=9.000
```
All nine S1 carries include the 3-part proof (right answer, derivation `b=k×a; total=a+b`, multi-register + classical cert). Surface-variant re-extract of the inverted-seed form still maps (Ava/Ben → 48).
### 3b. Coverage gain
```text
$ uv run python scripts/measure_trackb_inc2.py --mode coverage-gain
organ_correct=5 trackb_correct=9 newly_solved=4
NEW gsm8k-holdout-dev-v1-0148 ans=1500.0 gold=1500.0 sid=S1 extract=s1_p3_day_pair
NEW gsm8k-holdout-dev-v1-0228 ans=600.0 gold=600.0 sid=S1 extract=s1_p2_b_seeded
NEW gsm8k-holdout-dev-v1-0234 ans=240.0 gold=240.0 sid=S1 extract=s1_p2_deaf_blind
NEW gsm8k-holdout-dev-v1-0441 ans=75.0 gold=75.0 sid=S1 extract=s1_p2_b_seeded
```
These four are **real surface-distinct holdout cases**, not synthetic clones. The SM-owned extract layer is structure-mapping-owned (regex pure-family templates) and runs only when the serving reader refuses — it does not modify the live reader.
### 3c. Selector
```text
$ uv run python scripts/measure_trackb_inc2.py --mode selector
CASE real_s1_0411 expect=S1 got=S1 refused=False ok=True
CASE real_s2_public_transfer expect=S2 got=S2 refused=False ok=True
CASE struct_s2_not_s1 expect=S2 got=S2 refused=False ok=True
CASE struct_s1 expect=S1 got=S1 refused=False ok=True
CASE empty_ops_refuse expect=None got=None refused=True reason=no_family_matched ok=True
CASE struct_s3_rate expect=S3 got=S3 refused=False ok=True
```
Transfer graphs route to S2 even when the surface text could mention multiplicative language elsewhere; empty ops refuse. Ranking uses fixed systematicity scores, not entity names or numbers.
### 3d. Parser frontier
```text
organ_selected_graph_count=5/500
role_kind_multiset_counts={'compare,contain,total': 5}
--- case 0148 ---
organ_parsed=False
sm_pipeline emit=True answer=1500.0 gold=1500.0 src=sm_extract sid=S1
```
**0148** (the original S1 family namesake) is a **parser-frontier refusal** on the serving reader; the SM-owned extract recovers it. Holdout still has zero organ graphs for transfer/rate/additive-compare families — that is the next structural bottleneck, not the mapper.
### 3e. S2 on real public transfers (not holdout; engineering evidence only)
```text
SUMMARY s2_public_sample: ok=5 scanned_transferish=20
S2 gma-102 ans=54.0 ... transfer 7 Eve→David ...
```
S2 pure-family map+solve works on real public transfer graphs. Not counted in the holdout ratio table (discipline: holdout IDs only).
### 3f. Right-reason summary
```text
SUMMARY right-reason: correct=9 wrong=0 refused=0
SUMMARY surface-variant: ok=True
```
### 3g. Full holdout wrong=0 gate (all 500 — not curated subset)
Regression: a loose inverted-seed regex matched multi-entity zoo case **0361**
(`John has 8 more pandas…`) as pure S1 and the corridor emitted `1.125` vs
gold `114.0`. Fixed by pure-family surface gates (require explicit multiplicative
language; refuse bare `N more` / multi-clause / multi-numeric surfaces).
```text
$ uv run python scripts/measure_trackb_inc2.py --mode full-holdout-wrong0
=== full holdout_dev/v1 wrong=0 gate (all 500 cases) ===
SUMMARY full-holdout: emit_ok=9 wrong=0 refused=491 n=500
PASS: wrong=0 absolute on full holdout_dev/v1
```
---
## 4. Discipline checks
- **Blind**: mappers/selector take only `RoleGraph`; no label parameter. Static audit: `generate/structure_mapping/` does not import scoring labels.
- **wrong=0**: emit only if classical verify **and** multi-register cert **and** agreement; original-graph backstop when a graph is supplied.
- **No crash-as-signal**: corridor exceptions → refuse outcomes.
- **Off-serving**: no organ retirement; no `parse_and_solve` dispatch edits.
---
## 5. Deviations / frontiers (honest)
1. **S2S4 holdout ratio = 0** until the serving reader (or a reviewed SM extract family) produces pure transfer/rate/additive graphs on holdout. Do not manufacture synthetic holdout clones for ratio.
2. **S3/S4 solve emit** blocked by multi-register scope (`apply_rate`, `compare_additive` not Tier-2a). Gates not weakened — refuse.
3. **SM extract is narrow** (regex pure-S1 templates only, with fail-closed purity gates). It is not a general parser; expanding it is a deliberate future increment. Overmatch risk is gated by `mode full-holdout-wrong0` on all 500 cases.
4. **Organ retirement** deferred; coverage-gain path met bar 2 without serving risk.
5. **0361 pure-S1 overmatch** (fixed in follow-up commit): optional `times` in P2 regex treated additive `8 more` as multiplicative; purity gate + required `times as many` closed it. Full-holdout scan is now mandatory before claiming wrong=0.
---
## 6. STOP
One PR (base `main`). Reviewer verifies:
- blindness + pure-family gates,
- ratio > 1 cases are real holdout IDs (not clones),
- `wrong=0` three-gate path,
- serving reader unchanged.
No S2S4 serving cutover until a later ruling. No sealed-test run.