core/docs/handoff/DCS-S1-FINDING.md
Shay ce6b2f5173
docs(ADR-0170): injector contract widening + DCS-S1 schema-gap finding (#372)
DCS-S1 (proper-noun possession sub-shape expansion) investigation
revealed that the recognizer-injector path's `CandidateInitial`-only
return type is a substrate-level constraint blocking four Wave-Next
sub-shape categories — not just one.

## Two artifacts

1. **`docs/handoff/DCS-S1-FINDING.md`** — investigation result. Of
   the 21 DCS-refused GSM8K cases, zero are pure S1-only blockers.
   Acquisition-verb expansion (`collected`, etc.) conflicts with
   ADR-0131.G.1's branch-disagreement discipline. The right fix is
   the DCS injector emitting `CandidateOperation(add)`, but the
   `inject_from_match` return type doesn't allow that.

2. **`docs/decisions/ADR-0170-injector-contract-widening.md`** —
   scoping ADR. Names the contract change, the four categories it
   unblocks (DCS-S1 acquisition, A1 currency, A3 multiplicative,
   A4 temporal), the three load-bearing rules it must preserve
   (ADR-0131.G.1, SentenceChoice union, admissibility gates), and
   a 5-step implementation outline.

## Pattern recognised

Wave-Next surfaced four schema gaps. All four trace to the same
constraint: per-category injectors can only emit `CandidateInitial`.
The right next-capability work is ADR-0170 ratification, then a
small no-behavior-change PR widening the contract, then per-injector
follow-up PRs against the widened contract.

That is the actual lift-per-risk path for GSM8K Round-1 closure.

## Test plan

Docs-only. No code, no test, no eval, no pack change.

## Cross-references

- ADR-0163.D.2 — original parsed_anchors → solver-state ADR
- ADR-0131.G.1 — branch-disagreement discipline ADR-0170 preserves
- ADR-0167 — parallel teaching-corridor mechanism (independent)
- ADR-0167-FOLLOWUPS §7 — Wave-Next findings backlog
- WAVE-NEXT-REVISED — parent plan; ADR-0170 is the upstream blocker
- PR #369 — A2's schema-refusal artifact (first observation of gap)
2026-05-27 10:51:46 -07:00

6.2 KiB
Raw Blame History

DCS-S1 Finding — Injector Contract Is the Substrate Bottleneck

Date: 2026-05-27 Status: Finding; no implementation. Routes work to ADR-0170. Parent: docs/handoff/WAVE-NEXT-REVISED.md Result: DCS-S1 (proper-noun possession sub-shape expansion) cannot ship meaningful lift on current GSM8K without first widening the inject_from_match return-type contract. Same schema gap A3 (multiplicative) and A4 (temporal) identified.


What was investigated

The Wave-Next-Revised plan named DCS-S1 as the next tractable sub-shape: extend the existing v1 inject_discrete_count_statement injector to cover more of the 21 discrete_count_statement refusals on the GSM8K train_sample.

The hypothesis: most DCS refusals are proper-noun possessions whose extraction fails for one of a few narrowable reasons (verb whitelist, clause-split markers, single-quantity rule). Loosening the matcher + extending the injector to cover those would lift several cases.

What the data actually shows

All 21 DCS-refused cases were enumerated and the triggering statements inspected. The structural breakdown:

Sub-shape pattern in DCS-refused statements Count
Acquisition verb (collected, donated, bought) — not in possession whitelist ~5
Multi-clause / enumeration (and, then, ,) ~8
Pronoun subject (he, she, they) ~4
Comparative reference (twice as many, half of) ~3
Anonymous subject (There are, The guests) ~3
Other (modal verbs, copula, multi-verb) ~4

(Buckets overlap — many cases hit multiple narrowness rules.)

Critical observation: of the 21 cases, zero are pure S1-only blockers. Every single one has additional blockers downstream in the same problem. Even if the DCS injector admitted the first sentence, sentences 2/3/4 carry their own refusals.

Concrete trace — case gsm8k-train-sample-v1-0023:

S1: "Nicole collected 400 Pokemon cards."
    → would inject Nicole=400 if `collected` were in possession verbs
S2: "Cindy collected twice as many, and Rex collected half of Nicole and Cindy's combined total."
    → multi-clause + comparative reference; will refuse on current rules
S3: question with `If Rex divided...` conditional prefix
    → conditional prefix recovery exists but answer still requires S2's admission

Admitting S1 alone does not close the case. The bottleneck moves from S1 to S2; no admission-lift is delivered.

The architectural blocker

To admit "Nicole collected 400 Pokemon cards" as initial state, three edits would be needed:

  1. Add collected to _POSSESSION_VERBS (matcher narrowness in generate/recognizer_match.py)
  2. Extend _locate_possession_verb in the injector
  3. Add collect, collects, collected to CandidateInitial.__post_init__ whitelist in generate/math_candidate_parser.py

ADR-0131.G.1 explicitly removed collected/bought/saved/buys/makes from initial-anchor extraction — they were routed exclusively to ADD_VERBS (operation extraction) to avoid branch disagreement when the same sentence could produce both CandidateInitial(Sam=5) AND CandidateOperation(Sam, add, 5).

The solver "defaults from zero for operations" so Sam collected 5 apples. How many does Sam have?0 + 5 = 5. That discipline is load-bearing for wrong=0 in the regex path.

The right architectural fix is not to break ADR-0131.G.1. It's to teach the DCS injector to emit CandidateOperation(add) for acquisition verbs — the same kind of state-introducing operation the parser already emits for them.

But: inject_from_match's return type is tuple[CandidateInitial, ...]. It cannot emit CandidateOperation. Widening the contract is the prerequisite.

The pattern — fourth time this gap appears

Wave-Next surfaced four schema gaps. All four trace back to the same substrate-level constraint: the recognizer-injector path can only emit CandidateInitial.

Brief What needed emission Available type Blocker
A2 rate_with_currency CandidateRate (carries Rate(value, num_unit, den_unit)) None — Rate not in SentenceChoice union Schema gap (PR #369)
A3 multiplicative_aggregation CandidateInitial(outer × inner) OR CandidateOperation(multiply) CandidateInitial works for product semantics; but other cases need composition Half-blocked
A4 temporal_aggregation CandidateOperation(apply_rate, ...) apply_rate primitive doesn't exist Algebra-level gap
DCS-S1 CandidateOperation(add) for acquisition verbs Injector returns only CandidateInitial Schema gap (this finding)

This is no longer four separate sub-shape problems. It's one substrate bottleneck affecting all four categories. The right artifact is a contract-widening ADR — see ADR-0170.

What this PR ships

  • This finding doc (docs/handoff/DCS-S1-FINDING.md)
  • docs/decisions/ADR-0170-injector-contract-widening.md — the scoping ADR that names the contract change and the four categories it unblocks

What this PR does NOT ship:

  • Any DCS-S1 implementation (it's blocked on ADR-0170)
  • Any matcher/extractor edits
  • Any test changes
  • Any pack changes

ADR-0170 ratification, then a small focused PR implementing the contract widening (no behavior change, type-only). Then a follow-up wave (DCS-S1 acquisition, A1 currency, A2 rate, A3 multiplicative, A4 temporal) can ship in parallel — each as a focused injector PR against the widened contract.

That's the actual lift-per-risk path. Until ADR-0170 lands, no recognizer-injector work other than CandidateInitial-only narrow possession can ship cleanly.

Cross-references

  • WAVE-NEXT-REVISED — parent plan; needs an update pointing to ADR-0170 as the prerequisite
  • ADR-0170 — the scoping ADR this finding routes to
  • PR #369 (A2) — first observation of the gap; documents Rate extension steps
  • ADR-0131.G.1 — the original branch-disagreement discipline that forces acquisition verbs to be operations, not initials
  • ADR-0167-FOLLOWUPS §7 — Wave-Next findings backlog