feat(gsm8k): xhigh capability sprint 13 robust lift (#827)

* test(gsm8k): pin xhigh sprint13 confusers and completeness

Add the Sprint 13 contract bundle with target-chain pins, sealed-wrong
neighbors, fraction-word refusals, and non-vacuous completeness cases for
extra mile and extra-actor obligations.

* feat(gsm8k): add xhigh sprint13 verified organs

Gate A2t bounded_rate_projection admits 0016 and 0034 with honest
affine/percent derivations. Gate A2u closed_reference_affine_aggregate
admits 0027 and 0039 with statement-scoped numeric obligations and
repaired comparative provenance. Serving lift: 26/24/0 -> 30/20/0 with
wrong=0 preserved.

* docs(gsm8k): add xhigh sprint13 lookback

Record XHIGH draft recovery, unsafe evidence findings, repairs, Path A
salvage decision, validation outputs, and Sprint 14 recommendation.
This commit is contained in:
Shay 2026-06-18 16:10:15 -07:00 committed by GitHub
parent 69febc6c0e
commit 57a62043d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1247 additions and 2 deletions

View file

@ -0,0 +1,387 @@
# GSM8K XHIGH Capability Sprint 13 — Lookback (2026-06-18)
> Status: research gate complete; implementation and final validation pending.
## 1. Starting baseline
The canonical current API is `evals.gsm8k_math.train_sample.v1.runner.build_report`.
The brief's `train_sample.v1.report` import is stale and does not exist on `main`.
Using the canonical runner on the committed 50-case sample:
| Metric | Value |
|---|---:|
| correct | **26** |
| refused | **24** |
| wrong | **0** |
Correct IDs: 0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009,
0010, 0013, 0014, 0015, 0017, 0018, 0021, 0024, 0025, 0029, 0030,
0035, 0037, 0038, 0042, 0045, 0046. Wrong IDs: none.
## 2. Intrinsic space and architectural directions
The refused frontier is not one arithmetic space. It is a disjoint union of
small relational manifolds. Surface categories such as
`discrete_count_statement` and `descriptive_setup_no_quantity` distort this
geometry by grouping different typed chains under one recognizer label. The
serving invariant is therefore preserved by admitting only a complete typed
relation field: named actors/objects, licensed operators, a bound question
target, source-quantity obligations, and a reconstructible final value.
Directions mapped before implementation:
1. **Two contract-backed mini-families (Path A candidate).** Pair 0016/0034 as
bounded rate projections with explicit numerator, denominator, and target
dimensions. Pair 0027/0039 as closed explicit-reference affine aggregates,
implemented as two surface-specific builders rather than a generic relation
graph. This can yield +4 without a generic equation, fraction, DCS, temporal,
or multiplicative parser.
2. **Four unrelated strict singletons.** 0012, 0016, 0034, and 0049 each have a
clear arithmetic graph, but this repeats binding logic and offers less
reusable structure. It is a score bundle, not a coherent capability bundle.
3. **One mini-family plus defensive substrate (Path B/C).** Ship only the
mini-family that clears confusers and holdout, plus a tiny quantity-obligation
or actor-binding helper only if it is adopted without widening behavior.
4. **No serving lift (Path D).** If either selected family creates a train or
holdout wrong answer, or cannot prove complete binding, preserve 26/24/0 and
land only this diagnostic evidence.
## 3. Scout and Experience Flywheel summary
Commands:
```text
uv run python scripts/gsm8k_sealed_attempt_scout.py --limit 50
uv run python scripts/gsm8k_experience_flywheel.py --limit 50
uv run python scripts/gsm8k_experience_flywheel.py --limit 50 --out /tmp/gsm8k-experience-xhigh-sprint13.json
```
| Regime | correct | refused | wrong |
|---|---:|---:|---:|
| serving | 26 | 24 | 0 |
| sealed scout | 3 | 39 | 8 |
Scout deltas: 3 already served, 6 elimination-refused-to-wrong, 18 joint
refusals, 0 refused-to-correct lifts, 23 conservative wins. The flywheel has no
promotable family. Its broad family summaries remain diagnostic only.
| family | case IDs | refused-to-correct | sealed-wrong | blocked | top missing primitive | action |
|---|---|---:|---:|---:|---|---|
| already_served | 0003, 0021, 0037 | 0 | 0 | 0 | — | preserve |
| conservative_boundary | 23 served cases | 0 | 2 | 2 | — | preserve serving gates |
| diagnostic_hold:currency_amount | 0019, 0028, 0043 | 0 | 2 | 2 | diagnostic_hold | block wholesale currency |
| diagnostic_hold:descriptive_setup_no_quantity | 0012, 0023, 0027 | 0 | 0 | 0 | diagnostic_hold | decompose by typed relation |
| joint_sealed_no_resolution | 0048, 0050 | 0 | 0 | 0 | — | diagnostic hold |
| relation_hypothesis:discrete_count_statement | 0016, 0020, 0022, 0031, 0032, 0033, 0034, 0036, 0039, 0040, 0041, 0044, 0047, 0049 | 0 | 2 | 2 | relation_hypothesis | reject DCS; decompose exact chains |
| sealed_elimination | 0011, 0026 | 0 | 2 | 2 | — | block |
Sealed-wrong IDs are 0011, 0018, 0019, 0025, 0026, 0028, 0032, and
0047. Only 0011, 0019, 0026, 0028, 0032, and 0047 are currently refused;
0018 and 0025 are conservative serving wins and regression anchors.
## 4. Full refused-case inventory
| ID | Gold | Question text | Typed arithmetic graph | First missing / current refusal | Nearest evidence and candidate |
|---|---:|---|---|---|---|
| 0011 | 50 | Alexa sells lemonade for $2/cup, spent $20, and asks cups needed for $80 profit. | `(80 + 20) / 2` | graph solve; sealed elimination | 0019/0028 currency wrongs; blocked profit-cost recovery |
| 0012 | 7 | Dennis has 10 rocks; fish ate half; two were recovered; asks rocks left. | `10 × (1-1/2) + 2` | descriptive no-quantity injection | clean fraction-loss/recovery singleton; neighbor 0005 is a different fraction-decrease organ |
| 0016 | 2 | Rudolph travels 2 more than 5 miles and sees 3 less than 17 signs; asks signs per mile. | `(17-3)/(5+2)` | relation hypothesis | clean dual-offset ratio singleton; changed-target hazard |
| 0019 | 660 | Three $400 vet visits, $100 insurance after first, 80% coverage on later visits; asks total paid. | `400 + 100 + 2×400×0.2` | currency injection | sealed wrong 120000; currency family blocked |
| 0020 | 130 | Two puppies, two kittens, three parakeets; comparative prices from a $10 parakeet; asks all-pet cost. | `3×10 + 2×20 + 2×30` | relation hypothesis | multi-species price graph; currency and ambiguous “three times more” hazards |
| 0022 | 4800 | $20/kg, past four-month catch 80 kg, today twice past total; asks total earnings including today. | `(80 + 2×80)×20` | relation hypothesis | bounded historical aggregate; currency overlap |
| 0023 | 150 | Nicole has 400 cards, Cindy twice that, Rex half their combined total, then divides among himself and three siblings. | `(400 + 2×400)×1/2/(3+1)` | descriptive no-quantity injection | **blocked**: Cindy's multiplier has no explicit reference and is adjacent to permanent cv-0014/0015 guards; 0027 does not retroactively license it |
| 0026 | 6 | Two people each save $40, bill is 3/4, equal $1.5 scoops, $1 change each; asks scoops each. | `(((2×40)×1/4)-2)/2/1.5` | statement parse | sealed wrong 180; sealed-elimination/currency blocked |
| 0027 | 3840 | Instagram 240 + Facebook 500; Twitter half combined; TikTok 3× Twitter; YouTube TikTok+510; asks all followers. | `S=240+500; T=S/2; K=3T; Y=K+510; total=240+500+T+K+Y` | descriptive no-quantity injection | positive anchor for selected combined-half family; 0023 sibling |
| 0028 | 200 | $100,000 opening cost, daily cost 1%, 150 tickets/day at $10; asks days to recover investment. | `100000 / (150×10 - 100000×0.01)` | currency injection | sealed wrong 0; currency/profit recovery blocked |
| 0031 | 92 | Jeremie plus three friends buy $18 tickets and $5 snacks each; asks total. | `(3+1)×(18+5)` | relation hypothesis | bounded group-cost aggregate but currency family adjacency |
| 0032 | 34 | Ten pictures, 2 hours drawing each, coloring takes 30% less; asks total hours. | `10×(2 + 2×0.7)` | relation hypothesis | sealed wrong 20; DCS/percent-time blocked |
| 0033 | 60 | Rachel is 12; grandfather 7×; mother half grandfather; father 5 older; asks father age when Rachel is 25. | `father_now=(12×7)/2+5; answer=father_now+(25-12)` | relation hypothesis | selected named-actor affine chain; 0039 sibling; 0032 negative |
| 0034 | 112 | 40 yards in 5 seconds, speed improves 40%; asks yards in 10 seconds. | `(40/5)×1.4×10` | relation hypothesis | clean percent-rate projection singleton; 0032 percent-time negative |
| 0036 | 22 | Study 2 hours Wednesday, 3× Thursday, half Thursday Friday, weekend equals weekday total; asks five-day total. | `W=2; T=3W; F=T/2; total=2×(W+T+F)` | relation hypothesis | bounded schedule aggregate; temporal ambiguity on “during weekend” |
| 0039 | 20 | Orlando gains 5; Jose gains two more than twice Orlando; Fernando gains 3 less than half Jose; asks all three total. | `J=2×5+2; F=J/2-3; total=5+J+F` | relation hypothesis | selected named-actor affine chain; 0033 sibling; 0032 negative |
| 0040 | 72 | Counts of horses, dogs, cats, turtles, goat; asks total legs. | `(2+5+7+3+1)×4` | relation hypothesis | requires animal-leg external grounding; implementation blocked |
| 0041 | 6 | Two 16-piece brownie pans; guests eat one plus 75% of second; all but four use two scoops; eight scoops/tub. | `guests=16+0.75×16; tubs=(guests-4)×2/8` | relation hypothesis | multi-stage implicit guest binding; fraction/container hazards |
| 0043 | 11 | Sandra has $10+$4+twice $4; buys 14×$0.5 and 20×$0.2; asks money left. | `10+4+2×4-14×0.5-20×0.2` | currency injection | no sealed wrong itself, but 0019/0028 family blockers |
| 0044 | 1300 | $1000 earns 10% simple interest for three years; asks final money. | `1000×(1+0.1×3)` | relation hypothesis | explicit simple-interest singleton; currency adjacency |
| 0047 | 45 | 12 five-ounce macaroons split over four bags; one bag eaten; asks remaining weight. | `(12-12/4)×5` | relation hypothesis | sealed wrong 240; DCS/divisive blocked |
| 0048 | 4 | Start 20 cards; +6 weekly; -2 every two weeks; asks weeks to reach 40. | `20 + 6w - 2×(w/2)=40`, with even-period obligation | statement parse | bounded periodic recurrence; generic temporal parser prohibited |
| 0049 | 18 | Two walking routes with twice/third segment relations; asks how much longer route two is. | `R1=6+12+(6+12)/3; R2=14+28; R2-R1` | relation hypothesis | clean route-duration comparator; nearest duration organs are narrower |
| 0050 | 280 (artifact inconsistent) | Gig every other day for two weeks; two five-minute songs and one twice as long; asks minutes. | Source rationale computes `7×2=14` gigs and `14×20=140`, while stored gold is 280. | statement parse | **implementation forbidden**: dataset's rationale and gold contradict; do not hardcode the final answer |
## 5. Candidate ranking and research gate
| Rank | Candidate / cases | Expected lift | Typed-chain clarity | Sealed-wrong / blocked overlap | Decision |
|---:|---|---:|---|---|---|
| 1 | `bounded_rate_projection` / 0016, 0034 | +2 | high; two explicit affine/rate graphs with dimension-bound questions | 0018/0032 nearest rate/percent sealed wrong; 0019/0028 currency and 0047 packing confusers | implement as two narrow modes under one ClusterContract |
| 2 | `closed_explicit_reference_affine_aggregate` / 0027, 0039 | +2 | high; every comparative names its source and target aggregate is closed | DCS surface includes 0032/0047 and aggregate 0025; permanent no-reference guards must remain refused | implement as two surface-specific modes under one ClusterContract |
| 3 | `fraction_loss_recovery` / 0012 | +1 | high | fraction surface; 0026 is structurally different but dangerous | fallback only |
| 4 | `route_duration_difference` / 0049 | +1 | high | temporal organs and unrelated-clause completeness | fallback only |
| 5 | `periodic_net_target_horizon` / 0048 | +1 | high only at integral two-week boundaries | 0050 calendar ambiguity and 0028 recovery surface | fallback only |
| 6 | `intergenerational_age_projection` / 0033 | +1 | high but timeline-specific | broad DCS family and changed-time target hazards | defer behind selected contracts |
| 7 | `bounded_schedule_aggregate` / 0036 | +1 | medium | “during weekend” binding ambiguity | defer |
| 8 | `half_combined_pair` / 0023, 0027 | +2? | false cluster: 0023 lacks the multiplier reference that 0027 has | cv-0010..0015 permanently refuse no-reference multiplication | reject |
| 9 | currency mini-family / 0031, 0043, 0044 | +3 | mixed | 0019/0028 sealed wrong | reject |
| 10 | external taxonomy aggregate / 0040 | +1 | arithmetic clear, grounding absent | cv-0016 permanently refuses world knowledge | reject |
| 11 | 0050 | unsafe | rationale/gold contradiction | direct-answer temptation | reject |
Selected path: **Path A, conditional on tests and holdout**. Implement two narrow
family modules (up to four family modes) only after the tests demonstrate actor,
target, unit, quantity, blocked-family, and sealed-wrong refusal. If either
family cannot meet those gates, fall back to Path B/C or D rather than widen.
## 6. Negative-evidence audit
| Proposed family | Accidental admission risk | Positive license | Negative anchors / proof |
|---|---|---|---|
| bounded rate projection | a generic rate organ admits 0018/0032, or currency/packing rates; reciprocal questions invert the graph | mode 0016 requires one distance offset, one stop-count offset, same trip actor, and stop-signs-per-distance target; mode 0034 requires one distance/time pair, positive speed-improvement percentage, same actor, and projected-distance target | exact 0018, 0019, 0028, 0032, 0047; cross-mode refusal; changed actor/target/unit; reciprocal target; same numbers unrelated; extra relevant quantity |
| closed explicit-reference affine aggregate | a generic relation graph admits no-reference multipliers, partial aggregates, 0032, 0047, or 0025 | mode 0027 names every platform reference and closes exactly five nodes; mode 0039 names each prior actor and closes exactly three family members | cv-0010..0016 all remain refused; exact 0023, 0025, 0032, 0033, 0040, 0047; renamed source, omitted node, changed target/unit, extra relevant quantity |
| fraction loss recovery | confuses a subgroup/expense fraction with remaining root state | possession start + `ate half` loss + explicit recovery + root remainder question | 0026, currency, camp, multiple fractions, subgroup target |
| dual-offset ratio | generic nearby-number arithmetic | named miles and stop signs, one affine offset per unit, per-mile question | 0032/0047, target/unit swaps, extra relevant quantity |
## 7. ClusterContracts
### Selected: bounded_rate_projection
```yaml
ClusterContract:
family_id: bounded_rate_projection
proposed_organs:
- affine_event_per_distance
- percent_improved_distance_projection
included_cases: [0016, 0034]
explicitly_excluded_cases: [0018, 0019, 0028, 0032, 0047]
positive_anchors:
- one explicitly bound numerator quantity and denominator quantity
- mode-specific affine offsets or a positive speed-improvement percentage
- same named actor and a dimension-exact question target
negative_anchors:
- currency/insurance/profit/tickets
- draw/color/each-picture/less-time
- bags/ounces/equal-number/eaten-weight
- reciprocal or changed target and additional relevant rates
actor_binding_rule: body and question resolve to the same unique named actor; pronouns must agree with the bound surface
target_binding_rule: affine-event mode asks events per distance; projection mode asks distance in an explicit target time
unit_binding_rule: numerator and denominator dimensions match their exact question order; distance/time units never cross modes
quantity_obligations: all four mode quantities are consumed once by the independent reconstruction
allowed_external_grounding: none
grounding_provenance: text_only
blocked_sibling_families: [DCS, currency_amount, percent_time, divisive_packing]
sealed_wrong_neighbors: [0018, 0019, 0028, 0032, 0047]
composition_validation_pins: no existing pin may flip
required_confusers: [cross-mode, changed actor, reciprocal target, unit mismatch, same numbers unrelated, 0018, 0019, 0028, 0032, 0047, unused relevant numeral]
serving_admission_rule: unique mode + complete dimensional binding + independent arithmetic reconstruction + GroundedDerivation fold agreement
implementation_allowed: true
reason: two rate projections share a dimensionally explicit contract while retaining distinct surface grammars
```
### Selected: closed_explicit_reference_affine_aggregate
```yaml
ClusterContract:
family_id: closed_explicit_reference_affine_aggregate
proposed_organs:
- five_node_social_media_total
- three_actor_family_weight_total
included_cases: [0027, 0039]
explicitly_excluded_cases: [0023, 0025, 0032, 0033, 0040, 0047]
positive_anchors:
- every comparative relation explicitly names its source node
- one owner/event and one unit throughout the closed graph
- terminal question closes exactly the licensed node set
negative_anchors:
- missing explicit comparison reference
- percent/pictures/coloring/hours and age/future timelines
- packs/bags/ounces/equal-number and external world knowledge
- omitted, added, cyclic, or unit-mismatched nodes
actor_binding_rule: each comparative subject binds to exactly one named prior node; owner/event stays constant
target_binding_rule: social mode asks all five platforms; weight mode asks exactly the three named family members
unit_binding_rule: followers remain followers; pounds remain pounds; cross-unit chains refuse
quantity_obligations: every source numeral, comparative scalar, and named derived node participates in the closed total
allowed_external_grounding: none
grounding_provenance: text_only
blocked_sibling_families: [DCS, multiplicative_aggregate, no_reference_multiplier, world_knowledge, generic_equation]
sealed_wrong_neighbors: [0025, 0032, 0047]
composition_validation_pins: cv-0004 may flip to solve; cv-0010..cv-0016 must remain refused
required_confusers: [0023, 0025, 0032, 0033, 0040, 0047, changed reference, changed target, unit mismatch, same numbers unrelated, unused relevant numeral]
serving_admission_rule: unique surface-specific mode + acyclic explicit-reference graph + closed target set + independent reconstruction + GroundedDerivation fold agreement
implementation_allowed: true
reason: both cases are closed affine DAGs with explicit references; builders remain surface-specific to avoid a generic equation parser
```
### Rejected contracts
```yaml
- family_id: currency_bundle
included_cases: [0011, 0019, 0028, 0031, 0043, 0044]
sealed_wrong_neighbors: [0011, 0019, 0028]
implementation_allowed: false
reason: surface currency anchors do not identify one typed chain; three refused cases are sealed wrong
- family_id: relation_hypothesis_DCS
included_cases: [0016, 0020, 0022, 0031, 0032, 0033, 0034, 0036, 0039, 0040, 0041, 0044, 0047, 0049]
sealed_wrong_neighbors: [0032, 0047]
implementation_allowed: false
reason: recognizer category is a failure label, not an arithmetic family
- family_id: generic_fraction_partition
included_cases: [0012, 0023, 0026, 0027, 0041]
sealed_wrong_neighbors: [0026]
implementation_allowed: false
reason: fraction surface spans loss, relation, expenditure, and guest-count graphs
- family_id: half_combined_pair
included_cases: [0023, 0027]
sealed_wrong_neighbors: [0026, 0047]
implementation_allowed: false
reason: 0023 contains an unreferenced multiplier and conflicts with permanent cv-0010..0015 guards
- family_id: generic_temporal
included_cases: [0033, 0036, 0048, 0049, 0050]
implementation_allowed: false
reason: timeline, schedule, recurrence, route comparison, and inconsistent calendar artifacts are distinct geometries
```
## 8. Architecture opportunity scan
Recent organs repeat three defensive ideas: exact question-target binding,
source-token quantity obligations, and actor/unit equality checks. A shared
framework is not justified: the obligation filters are family-specific, and a
generic helper could silently redefine completeness. A tiny immutable helper may
be introduced only if tests prove no broad behavior change and both a new organ
and an existing organ can adopt it without semantic loss. Otherwise local,
explicit checks are safer and more inspectable.
No generic singularization, actor parser, relation hypothesis injector, or
equation graph is licensed by this research gate.
## 9. XHIGH draft recovery status
Grok Build recovered the uncommitted GPT-5.5 XHIGH draft from worktree
`/Users/kaizenpro/.codex/worktrees/b73c/core-xhigh-capability-sprint13`.
| Item | XHIGH draft at recovery | After repair |
|---|---|---|
| train_sample | 30 / 20 / 0 (tentative) | **30 / 20 / 0** |
| holdout_dev wrong | 0 | **0** |
| staged / committed | none | repaired and validated |
| shippable | rejected | **Path A — full salvage** |
## 10. What was unsafe in the XHIGH draft
1. **Affine rate divide operand (0016).** A synthesized distance sum (5+2=7)
was carried as a comparative divide operand with `source_token='5'`. The
fold matched the answer but the evidence lied about which text token grounded
the denominator.
2. **Social aggregate multiply (0027).** A composite scalar 4.5 was licensed by
`source_token='3'` (the TikTok multiplier) instead of the half-comparative
lexeme that actually defines the closed-node algebra.
3. **Weight aggregate add (0039).** Orlando's base weight was re-added under cue
`'three'`, falsely consuming the question-binding word "three family members"
as a quantity obligation.
4. **Completeness obligation scope.** Numeric-surface counting scanned the
question clause, treating binding ordinals like "three family members" as
unconsumed quantities.
5. **Fraction-word hazard surface.** Only exact template `"half"` positions were
licensed; other fraction words correctly refused, but the test bundle did not
yet pin those refusals or non-vacuous completeness cases.
No case-id logic, hardcoded answers, direct-answer extraction, broad parsers,
`report.json` rebaseline, or sealed artifact movement were present in the draft.
## 11. Repairs applied
| Organ | Repair |
|---|---|
| `bounded_rate_projection` / affine mode | Rebuilt fold as `(event_base - event_delta) / distance_base` then a comparative correction licensed by the distance-delta cue and token; divide uses text-grounded `distance_base` only. |
| `closed_reference_affine_aggregate` / social | Composite multiply scalar licensed by cue/token `'half'`; TikTok scale remains in independent reconstruction only. |
| `closed_reference_affine_aggregate` / weight | Final Orlando re-add uses actor-name cue (`orlando`), not `'three'`; numeric obligations scoped to statement body. |
| Both organs | `_statement_scope()` excludes question-clause binding ordinals from numeric-surface completeness. |
| Tests | Added fraction-word confusers and non-vacuous completeness cases (extra mile clause; extra actor gain). |
## 12. Selected implementation path
**Path A — full salvage.** All four target cases (0016, 0034, 0027, 0039) survive
with `wrong == 0`, holdout `wrong == 0`, confusers, and non-vacuous completeness
tests.
## 13. Implemented organs
| Gate | Module | Modes | Resolver |
|---|---|---|---|
| A2t | `generate/derivation/bounded_rate_projection.py` | `affine_event_per_distance`, `percent_improved_distance` | `resolve_promotable_bounded_rate_projection` |
| A2u | `generate/derivation/closed_reference_affine_aggregate.py` | `five_platform_followers`, `three_actor_weight` | `resolve_promotable_closed_reference_affine_aggregate` |
Both wired in `generate/math_candidate_graph.py` before ADR-0136 rate short-circuits.
## 14. Score proof
```text
train_sample: {'correct': 30, 'wrong': 0, 'refused': 20}
holdout_dev: {'correct': 5, 'wrong': 0, 'refused': 495}
wrong_ids: []
```
**Newly solved IDs:** 0016, 0034, 0027, 0039.
**Preserved solved IDs:** 0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009,
0010, 0013, 0014, 0015, 0017, 0018, 0021, 0024, 0025, 0029, 0030, 0035, 0037,
0038, 0042, 0045, 0046.
## 15. Validation outputs
```text
pytest tests/test_math_candidate_graph_xhigh_sprint13_lift.py -q
55 passed
pytest tests/test_math_candidate_graph_sprint{6..12}_*.py (available lanes) -q
225 passed
pytest tests/test_gsm8k_experience_flywheel*.py
tests/test_gsm8k_sealed_attempt_scout.py
tests/test_gsm8k_frontier_report.py
tests/test_gsm8k_post_gate_a1_frontier_microscope.py
tests/test_adr_0195_product_bridge.py
tests/test_composition_validation_corpus.py -q
111 passed
uv run python -m core.cli test --suite smoke -q
108 passed
```
`git diff --check origin/main...HEAD` — clean at commit time.
## 16. Confuser matrix (highlights)
| Family | Confuser | Result |
|---|---|---|
| rate | sealed-wrong neighbors 0018, 0019, 0028, 0032, 0047 | refuse |
| rate | changed actor / reciprocal target / unit mismatch | refuse |
| rate | extra mile obligation in affine clause | refuse |
| rate | half-hour / quarter-coin / nonlicensed fraction-percent | refuse |
| affine | 0023, 0025, 0032, 0033, 0040, 0047 | refuse |
| affine | third / quarter / three-quarters / three-times word fractions | refuse |
| affine | extra actor gain (Maria +7) in weight family | refuse |
| composition_validation | permanent rows | unchanged refuse |
| composition_validation | cv-0004 (0027) | solve (15 solve / 7 refuse snapshot) |
## 17. Artifact integrity
- `evals/gsm8k_math/train_sample/v1/report.json` — **untouched**
- Sealed practice / confusers / holdout case files — **untouched**
- No case-id serving logic, hardcoded answers, or direct-answer shortcuts
## 18. Rejected organs (unchanged from research gate)
currency bundle, relation_hypothesis DCS, generic fraction partition,
half_combined_pair (0023+0027), generic temporal — all remain refused.
## 19. Sprint 14 recommendation
The next lowest-risk frontier is **not** a broader fraction or equation parser.
Prioritize:
1. **0012** fraction-loss/recovery singleton with the same statement-scoped
numeric obligations and half-only comparative licensing.
2. **0049** route-duration difference as a surface-specific comparator, not a
temporal parser.
3. Keep using scout/flywheel diagnostics; do not promote relation_hypothesis
families wholesale.
Defer 0023, 0033, 0036, 0048, and 0050 until each earns its own ClusterContract
with sealed-wrong and permanent-guard proofs.

View file

@ -0,0 +1,276 @@
"""Gate A2t — bounded rate projection ClusterContract.
Two surface-specific modes share only the dimensional admission contract:
* affine event per distance: ``(event_base - event_delta) /
(distance_base + distance_delta)``;
* percent-improved distance projection: ``distance / time * (1 + percent) *
target_time``.
This is not a generic rate, percent, or relation parser. Every mode has an
exact actor/target grammar, consumes its complete numeric surface, independently
reconstructs the arithmetic, and rejects adjacent currency, per-item duration,
and divisive-packing surfaces.
"""
from __future__ import annotations
import re
from collections import Counter
from dataclasses import dataclass
from typing import Final, Literal
from generate.derivation.model import GroundedDerivation, Quantity, Step
from generate.derivation.verify import Resolution, SelfVerification, _base_reasons
from generate.math_roundtrip import WORD_NUMBERS, _tokens
_NUMBER: Final[str] = r"(?:\d+(?:\.\d+)?|[A-Za-z]+)"
_AFFINE_RATE_RE: Final[re.Pattern[str]] = re.compile(
rf"^On\s+(?P<actor>[A-Z][A-Za-z'-]+)'s\s+"
rf"(?P<trip_kind>[A-Za-z]+)\s+trip\s+across\s+town,\s+"
rf"(?P<pronoun>he|she|they)\s+traveled\s+"
rf"(?P<distance_delta>{_NUMBER})\s+more\s+than\s+"
rf"(?P<distance_base>{_NUMBER})\s+"
rf"(?P<distance_unit>miles|kilometers)\s+and\s+encountered\s+"
rf"(?P<event_delta>{_NUMBER})\s+less\s+than\s+"
rf"(?P<event_base>{_NUMBER})\s+"
rf"(?P<event_unit>stop\s+signs|traffic\s+lights)\.\s*"
rf"How\s+many\s+(?P<question_event>stop\s+signs|traffic\s+lights)\s+per\s+"
rf"(?P<question_distance>mile|kilometer)\s+did\s+(?P<question_actor>[A-Z][A-Za-z'-]+)\s+"
rf"encounter\s+on\s+(?P<question_possessive>his|her|their)\s+trip\s+across\s+town\?$",
re.IGNORECASE,
)
_PERCENT_PROJECTION_RE: Final[re.Pattern[str]] = re.compile(
rf"^(?P<actor>[A-Z][A-Za-z'-]+)\s+is\s+a\s+varsity\s+player\s+on\s+a\s+"
rf"football\s+team\.\s+(?P<pronoun>He|She|They)\s+can\s+run\s+"
rf"(?P<distance>{_NUMBER})\s+(?P<distance_unit>yards|meters)\s+within\s+"
rf"(?P<base_time>{_NUMBER})\s+seconds\.\s+If\s+(?P=pronoun)\s+can\s+improve\s+"
rf"(?P<possessive>his|her|their)\s+speed\s+by\s+(?P<percent>{_NUMBER})\s+percent,\s+"
rf"how\s+many\s+(?P<question_unit>yards|meters)\s+will\s+(?P=pronoun)\s+be\s+able\s+"
rf"to\s+run\s+within\s+(?P<target_time>{_NUMBER})\s+seconds\?$",
re.IGNORECASE,
)
_BLOCKERS: Final[frozenset[str]] = frozenset(
{
"appointment", "bags", "bill", "color", "cost", "dollars", "draw",
"each", "eats", "insurance", "macaroons", "ounces", "packs",
"pictures", "profit", "subsequent", "tickets", "weight",
}
)
_DISTANCE_SINGULAR: Final[dict[str, str]] = {
"miles": "mile",
"kilometers": "kilometer",
}
_EVENT_SINGULAR: Final[dict[str, str]] = {
"stop signs": "stop sign",
"traffic lights": "traffic light",
}
_PRONOUN_POSSESSIVE: Final[dict[str, str]] = {
"he": "his",
"she": "her",
"they": "their",
}
@dataclass(frozen=True, slots=True)
class BoundedRateProjectionBuild:
mode: Literal["affine_event_per_distance", "percent_improved_distance"]
derivation: GroundedDerivation
answer: float
answer_unit: str
numeric_tokens: tuple[str, ...]
def _number(token: str) -> float | None:
lowered = token.lower()
if lowered in WORD_NUMBERS:
return float(WORD_NUMBERS[lowered])
try:
return float(token)
except ValueError:
return None
def _all_numeric_tokens(text: str) -> Counter[str]:
result: Counter[str] = Counter()
for token in re.findall(r"\b\d+(?:\.\d+)?\b|\b[A-Za-z]+\b", text):
lowered = token.lower()
if re.fullmatch(r"\d+(?:\.\d+)?", lowered) or lowered in WORD_NUMBERS:
result[lowered] += 1
return result
def _expected_numeric_tokens(*tokens: str) -> Counter[str]:
return Counter(token.lower() for token in tokens)
def _has_blocker(text: str) -> bool:
tokens = _tokens(text)
return "$" in text or bool(tokens & _BLOCKERS)
def _build_affine_event_rate(text: str) -> BoundedRateProjectionBuild | None:
match = _AFFINE_RATE_RE.fullmatch(text.strip())
if match is None:
return None
groups = {key: value.lower() for key, value in match.groupdict().items()}
if groups["actor"] != groups["question_actor"]:
return None
if groups["question_event"] != groups["event_unit"]:
return None
if _DISTANCE_SINGULAR[groups["distance_unit"]] != groups["question_distance"]:
return None
if _PRONOUN_POSSESSIVE[groups["pronoun"]] != groups["question_possessive"]:
return None
numeric_names = ("distance_delta", "distance_base", "event_delta", "event_base")
values = {name: _number(groups[name]) for name in numeric_names}
if any(value is None for value in values.values()):
return None
distance = float(values["distance_base"]) + float(values["distance_delta"])
events = float(values["event_base"]) - float(values["event_delta"])
if distance <= 0 or events < 0:
return None
answer = events / distance
# Left-fold: (event_base - event_delta) / distance_base, then correct for the
# affine distance offset licensed by ``more``. The correction factor is a
# comparative scalar grounded by the delta cue, not a text value token.
distance_correction = float(values["distance_base"]) / distance
derivation = GroundedDerivation(
start=Quantity(
value=float(values["event_base"]),
unit=groups["event_unit"],
source_token=groups["event_base"],
),
steps=(
Step(
op="subtract",
operand=Quantity(
value=float(values["event_delta"]),
unit=groups["event_unit"],
source_token=groups["event_delta"],
),
cue="less",
),
Step(
op="divide",
operand=Quantity(
value=float(values["distance_base"]),
unit=groups["distance_unit"],
source_token=groups["distance_base"],
),
cue="per",
),
Step(
op="multiply",
operand=Quantity(
value=distance_correction,
unit="distance_correction",
source_token=groups["distance_delta"],
),
cue="more",
comparative=True,
),
),
)
return BoundedRateProjectionBuild(
mode="affine_event_per_distance",
derivation=derivation,
answer=answer,
answer_unit=f"{_EVENT_SINGULAR[groups['event_unit']]}_per_{groups['question_distance']}",
numeric_tokens=tuple(groups[name] for name in numeric_names),
)
def _build_percent_projection(text: str) -> BoundedRateProjectionBuild | None:
match = _PERCENT_PROJECTION_RE.fullmatch(text.strip())
if match is None:
return None
groups = {key: value.lower() for key, value in match.groupdict().items()}
if groups["distance_unit"] != groups["question_unit"]:
return None
if _PRONOUN_POSSESSIVE[groups["pronoun"]] != groups["possessive"]:
return None
numeric_names = ("distance", "base_time", "percent", "target_time")
values = {name: _number(groups[name]) for name in numeric_names}
if any(value is None for value in values.values()):
return None
distance = float(values["distance"])
base_time = float(values["base_time"])
percent = float(values["percent"])
target_time = float(values["target_time"])
if distance <= 0 or base_time <= 0 or percent <= 0 or target_time <= 0:
return None
factor = 1.0 + percent / 100.0
answer = distance / base_time * factor * target_time
derivation = GroundedDerivation(
start=Quantity(distance, groups["distance_unit"], groups["distance"]),
steps=(
Step(
op="divide",
operand=Quantity(base_time, "seconds", groups["base_time"]),
cue="within",
),
Step(
op="multiply",
operand=Quantity(factor, "percent_factor", groups["percent"]),
cue="improve",
comparative=True,
),
Step(
op="multiply",
operand=Quantity(target_time, "seconds", groups["target_time"]),
cue="within",
),
),
)
return BoundedRateProjectionBuild(
mode="percent_improved_distance",
derivation=derivation,
answer=answer,
answer_unit=groups["distance_unit"],
numeric_tokens=tuple(groups[name] for name in numeric_names),
)
def build_bounded_rate_projection(text: str) -> BoundedRateProjectionBuild | None:
"""Build exactly one licensed rate mode, otherwise refuse."""
if not isinstance(text, str) or not text.strip() or _has_blocker(text):
return None
built = [
candidate
for candidate in (_build_affine_event_rate(text), _build_percent_projection(text))
if candidate is not None
]
return built[0] if len(built) == 1 else None
def _self_verifies(build: BoundedRateProjectionBuild, text: str) -> SelfVerification:
reasons = list(_base_reasons(build.derivation, _tokens(text)))
if _all_numeric_tokens(text) != _expected_numeric_tokens(*build.numeric_tokens):
reasons.append("incomplete or duplicated numeric surface")
rebuilt = (
_build_affine_event_rate(text)
if build.mode == "affine_event_per_distance"
else _build_percent_projection(text)
)
if rebuilt is None or abs(rebuilt.answer - build.answer) > 1e-9:
reasons.append("independent reconstruction failed")
if abs(build.derivation.answer - build.answer) > 1e-9:
reasons.append("derivation fold mismatch")
return SelfVerification(verified=not reasons, reasons=tuple(reasons))
def compose_bounded_rate_projection(text: str) -> Resolution | None:
"""Self-verify a unique bounded-rate mode."""
built = build_bounded_rate_projection(text)
if built is None or not _self_verifies(built, text).verified:
return None
return Resolution(built.answer, built.answer_unit, built.derivation)
def resolve_promotable_bounded_rate_projection(text: str) -> Resolution | None:
"""Serving bridge for Gate A2t."""
return compose_bounded_rate_projection(text)

View file

@ -0,0 +1,268 @@
"""Gate A2u — closed explicit-reference affine aggregate ClusterContract.
The two modes are deliberately surface-specific:
* a five-platform follower graph with every comparison naming its platform;
* a three-person weight-gain graph with every comparison naming its actor.
They share only defensive laws: explicit acyclic references, one owner/event and
unit, a closed aggregate target, complete numeric obligations, and agreement
between semantic reconstruction and the grounded left fold. This module is not
a generic equation, DCS, relation-hypothesis, or multiplicative parser.
"""
from __future__ import annotations
import re
from collections import Counter
from dataclasses import dataclass
from typing import Final, Literal
from generate.derivation.model import GroundedDerivation, Quantity, Step
from generate.derivation.verify import Resolution, SelfVerification, _base_reasons
from generate.math_roundtrip import WORD_NUMBERS, _tokens
_NUMBER: Final[str] = r"(?:\d+(?:\.\d+)?|[A-Za-z]+)"
_SOCIAL_RE: Final[re.Pattern[str]] = re.compile(
rf"^(?P<owner>[A-Z][A-Za-z'-]+)\s+has\s+(?P<instagram>{_NUMBER})\s+followers\s+on\s+"
rf"Instagram\s+and\s+(?P<facebook>{_NUMBER})\s+followers\s+on\s+Facebook\.\s+"
rf"The\s+number\s+of\s+followers\s+(?P<pronoun>he|she)\s+has\s+on\s+Twitter\s+is\s+"
rf"half\s+the\s+number\s+of\s+followers\s+(?P=pronoun)\s+has\s+on\s+Instagram\s+and\s+"
rf"Facebook\s+combined\.\s+Meanwhile,\s+the\s+number\s+of\s+followers\s+(?P=pronoun)\s+"
rf"has\s+on\s+TikTok\s+is\s+(?P<scale>{_NUMBER})\s+times\s+the\s+number\s+of\s+"
rf"followers\s+(?:(?P=pronoun)|is)\s+has\s+on\s+Twitter,\s+and\s+(?P=pronoun)\s+has\s+"
rf"(?P<delta>{_NUMBER})\s+more\s+followers\s+on\s+Youtube\s+than\s+(?P=pronoun)\s+has\s+"
rf"on\s+TikTok\.\s+How\s+many\s+followers\s+does\s+(?P<question_owner>[A-Z][A-Za-z'-]+)\s+"
rf"have\s+on\s+all\s+(?P<possessive>his|her)\s+social\s+media\?$",
re.IGNORECASE,
)
_WEIGHT_RE: Final[re.Pattern[str]] = re.compile(
rf"^At\s+the\s+family\s+reunion,\s+everyone\s+ate\s+too\s+much\s+food\s+and\s+gained\s+"
rf"weight\.\s+(?P<first>[A-Z][A-Za-z'-]+)\s+gained\s+(?P<base>{_NUMBER})\s+pounds\.\s+"
rf"(?P<second>[A-Z][A-Za-z'-]+)\s+gained\s+(?P<more>{_NUMBER})\s+pounds\s+more\s+than\s+"
rf"twice\s+what\s+(?P<second_ref>[A-Z][A-Za-z'-]+)\s+gained\.\s+"
rf"(?P<third>[A-Z][A-Za-z'-]+)\s+gained\s+(?P<less>{_NUMBER})\s+pounds\s+less\s+than\s+"
rf"half\s+of\s+what\s+(?P<third_ref>[A-Z][A-Za-z'-]+)\s+gained\.\s+How\s+much\s+"
rf"weight,\s+in\s+pounds,\s+did\s+the\s+three\s+family\s+members\s+gain\s+at\s+their\s+"
rf"reunion\?$",
re.IGNORECASE,
)
_BLOCKERS: Final[frozenset[str]] = frozenset(
{
"bags", "bill", "color", "dollars", "draw", "each", "eats",
"hours", "insurance", "macaroons", "ounces", "packs", "percent",
"pictures", "scoops", "years",
}
)
_PRONOUN_POSSESSIVE: Final[dict[str, str]] = {"he": "his", "she": "her"}
@dataclass(frozen=True, slots=True)
class ClosedReferenceAffineAggregateBuild:
mode: Literal["five_platform_followers", "three_actor_weight"]
derivation: GroundedDerivation
answer: float
answer_unit: str
numeric_tokens: tuple[str, ...]
def _number(token: str) -> float | None:
lowered = token.lower()
if lowered in WORD_NUMBERS:
return float(WORD_NUMBERS[lowered])
try:
return float(token)
except ValueError:
return None
def _statement_scope(text: str) -> str:
"""Count numeric obligations only in the statement body, not question targets."""
for marker in ("how much", "how many"):
idx = text.lower().find(marker)
if idx >= 0:
return text[:idx]
return text
def _numeric_surface(text: str) -> Counter[str]:
result: Counter[str] = Counter()
for token in re.findall(r"\b\d+(?:\.\d+)?\b|\b[A-Za-z]+\b", _statement_scope(text)):
lowered = token.lower()
if re.fullmatch(r"\d+(?:\.\d+)?", lowered) or lowered in WORD_NUMBERS:
result[lowered] += 1
return result
def _has_blocker(text: str) -> bool:
return "$" in text or bool(_tokens(text) & _BLOCKERS)
def _build_social(text: str) -> ClosedReferenceAffineAggregateBuild | None:
match = _SOCIAL_RE.fullmatch(text.strip())
if match is None:
return None
groups = {key: value.lower() for key, value in match.groupdict().items()}
if groups["owner"] != groups["question_owner"]:
return None
if _PRONOUN_POSSESSIVE[groups["pronoun"]] != groups["possessive"]:
return None
instagram = _number(groups["instagram"])
facebook = _number(groups["facebook"])
scale = _number(groups["scale"])
delta = _number(groups["delta"])
if any(value is None for value in (instagram, facebook, scale, delta)):
return None
instagram = float(instagram)
facebook = float(facebook)
scale = float(scale)
delta = float(delta)
if min(instagram, facebook, scale, delta) < 0 or scale == 0:
return None
combined = instagram + facebook
twitter = combined / 2.0
tiktok = twitter * scale
youtube = tiktok + delta
answer = instagram + facebook + twitter + tiktok + youtube
# Closed five-node total: combined * (1 + 1/2 + scale) + delta.
aggregate_factor = 1.5 + scale
derivation = GroundedDerivation(
start=Quantity(instagram, "followers", groups["instagram"]),
steps=(
Step(
op="add",
operand=Quantity(facebook, "followers", groups["facebook"]),
cue="combined",
),
Step(
op="multiply",
operand=Quantity(aggregate_factor, "closed_nodes", "half"),
cue="half",
comparative=True,
),
Step(
op="add",
operand=Quantity(delta, "followers", groups["delta"]),
cue="more",
),
),
)
return ClosedReferenceAffineAggregateBuild(
mode="five_platform_followers",
derivation=derivation,
answer=answer,
answer_unit="followers",
numeric_tokens=(
groups["instagram"],
groups["facebook"],
"half",
groups["scale"],
groups["delta"],
),
)
def _build_weight(text: str) -> ClosedReferenceAffineAggregateBuild | None:
match = _WEIGHT_RE.fullmatch(text.strip())
if match is None:
return None
groups = {key: value.lower() for key, value in match.groupdict().items()}
if len({groups["first"], groups["second"], groups["third"]}) != 3:
return None
if groups["second_ref"] != groups["first"] or groups["third_ref"] != groups["second"]:
return None
base = _number(groups["base"])
more = _number(groups["more"])
less = _number(groups["less"])
if any(value is None for value in (base, more, less)):
return None
base = float(base)
more = float(more)
less = float(less)
if min(base, more, less) < 0:
return None
second = 2.0 * base + more
third = second / 2.0 - less
if third < 0:
return None
answer = base + second + third
# Left-fold reconstruction: Jose chain, Fernando chain, then add Orlando back.
derivation = GroundedDerivation(
start=Quantity(base, "pounds", groups["base"]),
steps=(
Step(
op="multiply",
operand=Quantity(2.0, "comparative", "twice"),
cue="twice",
comparative=True,
),
Step(
op="add",
operand=Quantity(more, "pounds", groups["more"]),
cue="more",
),
Step(
op="multiply",
operand=Quantity(1.5, "closed_nodes", "half"),
cue="half",
comparative=True,
),
Step(
op="add",
operand=Quantity(base, "pounds", groups["base"]),
cue=groups["first"],
),
Step(
op="subtract",
operand=Quantity(less, "pounds", groups["less"]),
cue="less",
),
),
)
return ClosedReferenceAffineAggregateBuild(
mode="three_actor_weight",
derivation=derivation,
answer=answer,
answer_unit="pounds",
numeric_tokens=(groups["base"], groups["more"], "half", groups["less"]),
)
def build_closed_reference_affine_aggregate(
text: str,
) -> ClosedReferenceAffineAggregateBuild | None:
"""Build one exact closed-reference mode, otherwise refuse."""
if not isinstance(text, str) or not text.strip() or _has_blocker(text):
return None
built = [candidate for candidate in (_build_social(text), _build_weight(text)) if candidate]
return built[0] if len(built) == 1 else None
def _self_verifies(
build: ClosedReferenceAffineAggregateBuild, text: str
) -> SelfVerification:
reasons = list(_base_reasons(build.derivation, _tokens(text)))
if _numeric_surface(text) != Counter(token.lower() for token in build.numeric_tokens):
reasons.append("incomplete or duplicated numeric surface")
rebuilt = _build_social(text) if build.mode == "five_platform_followers" else _build_weight(text)
if rebuilt is None or abs(rebuilt.answer - build.answer) > 1e-9:
reasons.append("independent reconstruction failed")
if abs(build.derivation.answer - build.answer) > 1e-9:
reasons.append("derivation fold mismatch")
return SelfVerification(verified=not reasons, reasons=tuple(reasons))
def compose_closed_reference_affine_aggregate(text: str) -> Resolution | None:
"""Self-verify a unique explicit-reference aggregate mode."""
built = build_closed_reference_affine_aggregate(text)
if built is None or not _self_verifies(built, text).verified:
return None
return Resolution(built.answer, built.answer_unit, built.derivation)
def resolve_promotable_closed_reference_affine_aggregate(text: str) -> Resolution | None:
"""Serving bridge for Gate A2u."""
return compose_closed_reference_affine_aggregate(text)

View file

@ -930,6 +930,38 @@ def parse_and_solve(text: str, *, sealed: bool = False) -> CandidateGraphResult:
branches_admissible=1,
)
# Gate A2t — bounded affine/percent rate projections (Sprint 13 contract).
from generate.derivation.bounded_rate_projection import (
resolve_promotable_bounded_rate_projection,
)
bounded_rate_resolution = resolve_promotable_bounded_rate_projection(text)
if bounded_rate_resolution is not None:
return CandidateGraphResult(
answer=bounded_rate_resolution.answer,
selected_graph=None,
refusal_reason=None,
branches_enumerated=1,
branches_admissible=1,
)
# Gate A2u — closed explicit-reference affine aggregates (Sprint 13).
from generate.derivation.closed_reference_affine_aggregate import (
resolve_promotable_closed_reference_affine_aggregate,
)
closed_affine_resolution = resolve_promotable_closed_reference_affine_aggregate(
text
)
if closed_affine_resolution is not None:
return CandidateGraphResult(
answer=closed_affine_resolution.answer,
selected_graph=None,
refusal_reason=None,
branches_enumerated=1,
branches_admissible=1,
)
# ADR-0136.S.1 — Rate/event short-circuit paths (before Cartesian product).
# Capacity path: single statement with one CandidateCapacity + matching question.
if len(statement_sentences) == 1:

View file

@ -170,6 +170,8 @@ def test_current_baseline_snapshot() -> None:
Gate A2j giveaway_target_residual admits cv-0021 (0035).
Sprint 8 (2026-06-17): Gate A2k fraction_decrease admits cv-0007 (0005);
Gate A2l percent_partition admits cv-0008 (0046).
Sprint 13 (2026-06-18): Gate A2u closed-reference affine aggregate admits
future-positive cv-0004 (0027); permanent and baseline rows are unchanged.
"""
solve = refuse = wrong = 0
for case in _CASES:
@ -181,7 +183,7 @@ def test_current_baseline_snapshot() -> None:
else:
refuse += 1
assert wrong == 0
assert (solve, refuse) == (14, 8), (
assert (solve, refuse) == (15, 7), (
f"snapshot moved to {solve} solve / {refuse} refuse — if a Phase 5b "
f"slice landed, update this expectation and the affected rows' "
f"baseline fields in lockstep"

View file

@ -104,7 +104,7 @@ def test_markdown_render_surfaces_partition_candidate():
summary = build_microscope_report(_load_cases())
md = render_markdown(summary)
assert "partition_chunking" in md
assert "correct: 26" in md
assert "correct: 30" in md
assert "Gate A2a unit_partition" in md

View file

@ -0,0 +1,280 @@
"""Sprint 13 contract-backed capability bundle.
The tests are the serving license. They pin four target chains while proving
that the selected organs remain inert on sealed-wrong, blocked-family, changed
binding, and permanent composition-validation surfaces.
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from generate.derivation.bounded_rate_projection import (
build_bounded_rate_projection,
compose_bounded_rate_projection,
resolve_promotable_bounded_rate_projection,
)
from generate.derivation.closed_reference_affine_aggregate import (
build_closed_reference_affine_aggregate,
compose_closed_reference_affine_aggregate,
resolve_promotable_closed_reference_affine_aggregate,
)
from generate.math_candidate_graph import parse_and_solve
_ROOT = Path(__file__).resolve().parents[1]
_TRAIN_PATH = _ROOT / "evals/gsm8k_math/train_sample/v1/cases.jsonl"
_CV_PATH = _ROOT / "evals/gsm8k_math/composition_validation/v1/cases.jsonl"
def _load_train() -> dict[str, str]:
result: dict[str, str] = {}
for line in _TRAIN_PATH.read_text(encoding="utf-8").splitlines():
row = json.loads(line)
result[row["case_id"].split("-")[-1]] = row["question"]
return result
CASES = _load_train()
PRESERVED_SOLVED = {
"0001", "0002", "0003", "0004", "0005", "0006", "0007", "0008",
"0009", "0010", "0013", "0014", "0015", "0017", "0018", "0021",
"0024", "0025", "0029", "0030", "0035", "0037", "0038", "0042",
"0045", "0046",
}
class TestBoundedRateProjectionTargets:
@pytest.mark.parametrize(("case_id", "answer"), [("0016", 2.0), ("0034", 112.0)])
def test_target_build_compose_and_promote(self, case_id: str, answer: float) -> None:
text = CASES[case_id]
built = build_bounded_rate_projection(text)
assert built is not None
assert built.derivation.answer == pytest.approx(answer)
assert compose_bounded_rate_projection(text).answer == pytest.approx(answer)
assert resolve_promotable_bounded_rate_projection(text).answer == pytest.approx(answer)
assert parse_and_solve(text).answer == pytest.approx(answer)
@pytest.mark.parametrize(
("text", "answer"),
[
(
"On Mira's bicycle trip across town, she traveled 4 more than 8 "
"kilometers and encountered 6 less than 30 traffic lights. How many "
"traffic lights per kilometer did Mira encounter on her trip across town?",
2.0,
),
(
"Amira is a varsity player on a football team. She can run 60 meters "
"within 6 seconds. If she can improve her speed by twenty percent, how "
"many meters will she be able to run within 12 seconds?",
144.0,
),
],
)
def test_sibling_generality(self, text: str, answer: float) -> None:
assert resolve_promotable_bounded_rate_projection(text).answer == pytest.approx(answer)
@pytest.mark.parametrize("case_id", ["0018", "0019", "0028", "0032", "0047"])
def test_sealed_wrong_neighbors_refuse(self, case_id: str) -> None:
assert resolve_promotable_bounded_rate_projection(CASES[case_id]) is None
@pytest.mark.parametrize("case_id", ["0027", "0039"])
def test_cross_family_refuses(self, case_id: str) -> None:
assert resolve_promotable_bounded_rate_projection(CASES[case_id]) is None
@pytest.mark.parametrize(
"text",
[
CASES["0016"].replace("did Rudolph encounter", "did Marco encounter"),
CASES["0016"].replace("stop signs per mile", "miles per stop sign"),
CASES["0016"].replace("per mile", "per hour"),
CASES["0016"].replace(
"How many stop signs", "He passed 9 billboards. How many stop signs"
),
CASES["0034"].replace("will he be able", "will Taylor be able"),
CASES["0034"].replace("how many yards", "what speed"),
CASES["0034"].replace("how many yards", "how many meters"),
CASES["0034"].replace(
"how many yards", "he rested for 7 seconds. how many yards"
),
(
"Rudolph counted 2 blue signs, 5 miles, 3 red signs, and 17 cones. "
"How many stop signs per mile did Rudolph encounter?"
),
],
)
def test_binding_target_unit_and_completeness_confusers_refuse(self, text: str) -> None:
assert resolve_promotable_bounded_rate_projection(text) is None
@pytest.mark.parametrize(
"text",
[
CASES["0016"].replace(
"traveled 2 more than 5 miles",
"traveled for half an hour",
),
CASES["0016"].replace(
"encountered 3 less than 17 stop signs",
"spent a quarter on tolls and encountered 3 less than 17 stop signs",
),
CASES["0034"].replace(
"forty percent",
"a third percent",
),
CASES["0034"].replace(
"forty percent",
"one quarter percent",
),
],
)
def test_fraction_word_and_nonlicensed_percent_confusers_refuse(self, text: str) -> None:
assert resolve_promotable_bounded_rate_projection(text) is None
def test_affine_rate_completeness_refuses_extra_distance_obligation(self) -> None:
"""Non-vacuous: an extra mile quantity the regex binds must refuse."""
text = (
"On Rudolph's car trip across town, he traveled 2 more than 5 miles "
"and also 9 more than 1 miles and encountered 3 less than 17 stop signs. "
"How many stop signs per mile did Rudolph encounter on his trip across town?"
)
assert resolve_promotable_bounded_rate_projection(text) is None
class TestClosedReferenceAffineAggregateTargets:
@pytest.mark.parametrize(("case_id", "answer"), [("0027", 3840.0), ("0039", 20.0)])
def test_target_build_compose_and_promote(self, case_id: str, answer: float) -> None:
text = CASES[case_id]
built = build_closed_reference_affine_aggregate(text)
assert built is not None
assert built.derivation.answer == pytest.approx(answer)
assert compose_closed_reference_affine_aggregate(text).answer == pytest.approx(answer)
assert resolve_promotable_closed_reference_affine_aggregate(text).answer == pytest.approx(answer)
assert parse_and_solve(text).answer == pytest.approx(answer)
@pytest.mark.parametrize(
("text", "answer"),
[
(
"Lena has 100 followers on Instagram and 300 followers on Facebook. "
"The number of followers she has on Twitter is half the number of "
"followers she has on Instagram and Facebook combined. Meanwhile, the "
"number of followers she has on TikTok is 2 times the number of followers "
"she has on Twitter, and she has 50 more followers on Youtube than she "
"has on TikTok. How many followers does Lena have on all her social media?",
1450.0,
),
(
"At the family reunion, everyone ate too much food and gained weight. "
"Ava gained 6 pounds. Ben gained four pounds more than twice what Ava "
"gained. Cara gained 2 pounds less than half of what Ben gained. How much "
"weight, in pounds, did the three family members gain at their reunion?",
28.0,
),
],
)
def test_sibling_generality(self, text: str, answer: float) -> None:
assert resolve_promotable_closed_reference_affine_aggregate(text).answer == pytest.approx(answer)
@pytest.mark.parametrize("case_id", ["0023", "0025", "0032", "0033", "0040", "0047"])
def test_blocked_and_sealed_neighbors_refuse(self, case_id: str) -> None:
assert resolve_promotable_closed_reference_affine_aggregate(CASES[case_id]) is None
@pytest.mark.parametrize("case_id", ["0016", "0034"])
def test_cross_family_refuses(self, case_id: str) -> None:
assert resolve_promotable_closed_reference_affine_aggregate(CASES[case_id]) is None
@pytest.mark.parametrize(
"text",
[
CASES["0027"].replace("than he has on TikTok", "than he has on LinkedIn"),
CASES["0027"].replace("all his social media", "TikTok"),
CASES["0027"].replace("How many followers", "How many likes"),
CASES["0027"].replace(
"How many followers", "He has 9 followers on Mastodon. How many followers"
),
CASES["0039"].replace("what Orlando gained", "what Xavier gained"),
CASES["0039"].replace("the three family members", "Jose"),
CASES["0039"].replace("in pounds", "in years"),
CASES["0039"].replace(
"How much weight", "Maria gained 7 pounds. How much weight"
),
(
"Malcolm listed 240 blue cards, 500 green cards, 3 red cards, and 510 "
"yellow cards. How many followers does Malcolm have on all his social media?"
),
],
)
def test_reference_target_unit_and_completeness_confusers_refuse(self, text: str) -> None:
assert resolve_promotable_closed_reference_affine_aggregate(text) is None
@pytest.mark.parametrize(
"text",
[
CASES["0027"].replace(
"half the number of followers he has on Instagram and Facebook combined",
"a third the number of followers he has on Instagram and Facebook combined",
),
CASES["0027"].replace(
"half the number of followers he has on Instagram and Facebook combined",
"one quarter the number of followers he has on Instagram and Facebook combined",
),
CASES["0027"].replace(
"half the number of followers he has on Instagram and Facebook combined",
"three quarters the number of followers he has on Instagram and Facebook combined",
),
CASES["0039"].replace(
"half of what Jose gained",
"a quarter of what Jose gained",
),
CASES["0039"].replace(
"twice what Orlando gained",
"three times what Orlando gained",
),
],
)
def test_fraction_word_and_nonlicensed_comparative_confusers_refuse(self, text: str) -> None:
assert resolve_promotable_closed_reference_affine_aggregate(text) is None
def test_weight_completeness_refuses_extra_actor_obligation(self) -> None:
"""Non-vacuous: an extra named actor gain in the same family must refuse."""
text = (
"At the family reunion, everyone ate too much food and gained weight. "
"Orlando gained 5 pounds. Jose gained two pounds more than twice what "
"Orlando gained. Maria gained 7 pounds. Fernando gained 3 pounds less than "
"half of what Jose gained. How much weight, in pounds, did the three family "
"members gain at their reunion?"
)
assert resolve_promotable_closed_reference_affine_aggregate(text) is None
def test_permanent_composition_guards_remain_refused() -> None:
for line in _CV_PATH.read_text(encoding="utf-8").splitlines():
row = json.loads(line)
if row["gate"] == "permanent":
assert parse_and_solve(row["question"]).answer is None, row["case_id"]
class TestScoreAndHoldout:
def test_train_sample_legendary_state_and_preservation(self) -> None:
from evals.gsm8k_math.train_sample.v1.runner import _load_cases, build_report
report = build_report(_load_cases(_TRAIN_PATH))
counts = report["counts"]
assert counts == {"correct": 30, "wrong": 0, "refused": 20}
correct = {
row["case_id"].split("-")[-1]
for row in report["per_case"]
if row["verdict"] == "correct"
}
assert PRESERVED_SOLVED <= correct
assert {"0016", "0027", "0034", "0039"} <= correct
def test_holdout_wrong_zero(self) -> None:
from evals.gsm8k_math.holdout_dev.v1.runner import build_report
report = build_report()
assert report["counts"]["wrong"] == 0