# ADR-0136.S.2 — Conditional-Op Question (Statement-Layer Corridor) **Status:** Active — *regex patterns scheduled for removal under [ADR-0164](./ADR-0164-incremental-comprehension-reader.md) Phase 3; closed-set vocabulary preserved as lexicon seed* **Date:** 2026-05-23 **Parent:** [ADR-0136](./ADR-0136-statement-layer-corridor.md) — see [ADR-0136 §Amendment 2026-05-26](./ADR-0136-statement-layer-corridor.md) --- ## Context After S.0 (context-sentence classifier) and S.1 (rate/event statement parsing) landed, the GSM8K train-sample probe sat at **2/50** admitted (`gsm8k-0014`, `gsm8k-0018`), `wrong == 0`. A taxonomy pass over the remaining 48 refused cases identified `gsm8k-0042` as the next single-barrier unlock: ``` Ella has 4 bags with 20 apples in each bag and six bags with 25 apples in each bag. If Ella sells 200 apples, how many apples does Ella has left? ``` The initial-state sentence already parses via `_CONJ_EMBEDDED_RE` (ADR-0131.G.4 embedded quantifier) to `InitialPossession(entity="Ella", value=230, unit="apples")`. The **only** barrier is the question form, which neither `_Q_ENTITY_RE` nor `_Q_TOTAL_RE` cover: the `If , how many does [left|...]?` shape combines a conditional-action operand with the entity-recall question. --- ## Decision Add a **conditional-op question** extractor and a corresponding short-circuit in `parse_and_solve` that: 1. Matches the closed shape `If , how many does []?` 2. Classifies `` against two closed sets: - `_COND_SUBTRACT_VERBS` (sell/sells/sold, give/gives/gave, eat/eats/ate, use, lose, spend, donate, remove, take, send, pay, drop, throw) - `_COND_ADD_VERBS` (buy/buys/bought, get/gets/got, receive, find, add, collect, pick, earn, gain) 3. Refuses on any of: unknown verb, unit mismatch (`` vs `` after canonicalization), entity mismatch (`` vs `` case-insensitively), `N <= 0`. 4. In `parse_and_solve`: if the question yields exactly one `CandidateConditionalOpQuestion`, collect all `extract_initial_candidates` from every statement sentence and look for **exactly one** matching IC by `(entity, unit)`. If found, compute `initial_value ± operand` by verb polarity; emit only when `answer >= 0`. Refuses otherwise. The short-circuit is structurally identical to the S.1 capacity/earnings paths: it bypasses graph construction, returns `selected_graph=None`, and preserves `wrong == 0` by refusing rather than guessing on any ambiguity. --- ## Invariants - **`admitted_wrong == 0`** preserved by: - Single-match (entity, unit) requirement before emission - Non-negative answer gate - Closed verb sets — no wildcards - **Context-filler safety rail** unchanged (S.0) - **No solver/graph/verifier changes** — extractor lives in `math_candidate_parser.py`; short-circuit lives in `math_candidate_graph.py` --- ## Honest GSM8K delta | Stage | Admitted | Wrong | |---|---|---| | Pre-S.0 | 0/50 | 0 | | Post-S.1 | 1/50 (`0014`) | 0 | | Post-S.0 classifier | 2/50 (`+0018`) | 0 | | **Post-S.2** | **3/50** (`+0042`) | **0** | `gsm8k-0042` admits with `answer == 30.0` (expected: 30). --- ## Consequences - The S.x corridor now spans `parser` (S.1 capacity/earnings, S.2 question shape) and `pre-pass classifier` (S.0). Future phases (S.3 compound statements, S.4 coreference) extend this pattern. - The canonical GSM8K runner (`evals/gsm8k_math/runner.py`) still asserts `selected_graph is not None` on admission and therefore cannot score the short-circuit admissions. The `report.json` artifact remains stale (0/50/0); the honest count is asserted via direct `parse_and_solve` test (`test_gsm8k_post_s2_admission_honest`). Aligning the canonical runner with the short-circuit paths is deferred (out of S.2 scope). --- ## Deferred - Conditional-op question forms with **more than one** operand (e.g. "If she gives away 3 and buys 5, …") — needs two-op composition. - Question forms without `does ` aux (e.g. "how many apples remain?") — needs a sibling regex. - Conditional questions where the conditional and the question reference **different** units that are unit-related (e.g. dollars↔cents) — needs unit-relation taxonomy.