Phase A — RuntimeConfig flag:
core/config.py: adds `comprehension_reader_questions: bool = False`
Default OFF preserves byte-identical behaviour with today.
Phase B — Hybrid wiring in candidate-graph path:
generate/math_candidate_graph.py:
- _try_reader_for_question() dispatches to the comprehension reader
BEFORE the regex question parser; refusal falls through to regex
- reader_trace: tuple[str, ...] field on CandidateGraphResult captures
JSON-encoded admit/fallthrough events for audit
generate/comprehension/lifecycle_runtime_adapter.py (new):
- build_problem_state_from_candidates(): converts regex-parser output
to ProblemReadingState for the reader's pronoun-resolution step
- invoke_reader_for_question(): tokenises sentence, drives lifecycle
- project_to_candidate_unknown(): QuestionTargetSlot → CandidateUnknown
- trace-event constructors for admit and fallthrough
Phase C — Capability-axis regression:
All existing tests pass with flag OFF and ON; zero new regressions.
Two pre-existing failures on main are unrelated to this PR.
Phase D — GSM8K train_sample measurement:
evals/gsm8k_math/train_sample/v1/runner.py: --use-reader flag triggers
baseline-off + reader-on runs and writes reader_phase1_delta.json
evals/gsm8k_math/train_sample/v1/reader_phase1_delta.json (new):
baseline-off: correct=3 refused=47 wrong=0
reader-on: correct=3 refused=47 wrong=0
delta: all zeros — Mixed result expected (Phase 2 scope)
wrong=0 invariant preserved in both modes.
Phase E — Coexistence tests:
tests/test_reader_coexistence.py (new): 13 tests covering
flag-OFF byte-identity, flag-ON determinism, wrong=0 invariant,
trace shape validation, Brief-8 target admission, and fallthrough
preservation for unknown-unit words.
Admission gate result: Mixed (correct=3, below the ≥10 bar).
All statement-side barriers remain in place; Phase 2 (reader for
statement sentences) is required to drive correct≥10. Documented in
reader_phase1_delta.json and train_sample/v1/runner.py docstring.
Adds the three lifecycle functions for the incremental compositional
reader per ADR-0164.3 §Lifecycle API:
- begin_sentence(problem_state, source_text_offset) -> SentenceReadingState
- apply_word(sentence_state, problem_state, word) -> SentenceReadingState | ReaderRefusal
- end_sentence(sentence_state, problem_state) -> ProblemReadingState | ReaderRefusal
Phase 1 scope is question sentences only. The update rules for the
question_frame live in a single readable table (_QUESTION_FRAME_RULES);
statement-side frames (initial_state_frame, operation_frame,
descriptive_frame) refuse with a Phase-2 diagnostic.
The five Brief-8 GSM8K target question sentences (0007, 0017, 0027,
0036, 0043) produce valid QuestionTargetSlot outputs end-to-end.
_interface_stubs.py provides a thin, functional surface for the
lexeme-primitive scanner (Brief 6) and lexicon loader (Brief 7) so
this PR does not block on them. The stub honours the en_core_math_v1
pack entries and adds a closed Phase-1 supplemental vocabulary marked
for fold-in to the pack once Briefs 6/7 land.
Tests cover determinism (byte-equal canonical bytes), the five GSM8K
target sentences with expected (entity, unit_class, kind) triples,
all token-level and sentence-level refusal modes, and lifecycle
invariants (registry preservation, sentence_index advance).
Stacked on feat/state-two-level-split (PR #323) per ADR-0164.3
§Naming — state types live in state.py.
Adds generate/comprehension/lexeme_primitives.py with the eight seed
primitives specified by ADR-0164.1:
decimal-currency-literal (priority 10)
currency-literal (priority 20)
percentage-literal (priority 30)
fraction-literal (priority 40)
time-amount-literal (priority 50)
ordinal-literal (priority 60)
mass-noun-token (priority 70)
numeric-literal (priority 100)
LexemePrimitive and LexemeMatch are frozen/slots dataclasses. scan()
runs primitives in priority order and returns the first hit wrapped in
a MappingProxyType over sorted-key extracted_values for canonical-bytes
stability. All patterns use explicit space characters ([ ]?, [- ]?) not
\s so the ADR-0165 compliance invariant holds.
55 tests cover: construction invariants, canonical fires (each
primitive on its own example), overlap precedence ($18.00, 1/2, 50%),
refusal on Tina/empty/verbs, determinism, sorted-key stability, and
the ADR-0165 compliance smoke test.