- recognizer_registry: ratified registry grew past round-1 (later ratification
rounds); assert the Phase C three remain present (subset) rather than pinning
exactly 3.
- brief_11b: 0021 joined the pre_frame_filler set as the verb-class auditor
evolved; it refuses, rows[0]=pre_frame_filler, and post-skip still refuses
(zero-lift verified). Canary 0050 untouched; wrong=0 unaffected (audit-only).
- cli_test_suites: assert the packs invocation against the live _TEST_SUITES
definition instead of a hardcoded file list (suite grew).
Unblocks the four Phase B round-2 exemplar corpora (PR #306) so they
can flow through `core teaching propose-from-exemplars`. The corpora
were committed in #306 but Phase C's ingest validator + synthesizer
were hard-coded to round-1 categories; this PR closes that gap.
Extends three modules with the three new categories
(discrete_count_statement, multiplicative_aggregation, currency_amount):
- teaching/exemplar_ingest.py — per-category validator dispatch +
_SUPPORTED_CATEGORIES. The file-stem rule loosens from
exact ``<category>_v1`` to ``<category>_v<N>`` so the
temporal_aggregation v2 widening from #306 ingests.
- teaching/recognizer_synthesis.py — per-category synthesizers
following the same observed_*-set + coverage-histogram pattern as
round 1. Determinism, narrowness rule (narrower-not-broader),
rules-only — same discipline.
- generate/recognizer_match.py — per-category matchers shipped as
DETECTION-ONLY (return empty parsed_anchors). Consistent with
Phase D's current skip-only wiring (PR #302). Real value
extraction lands when Phase D.2 plumbs parsed_anchors into the
solver; until then, detection-only is the right shape and
preserves wrong=0 by construction.
graph_intent Literal expanded to include "count" and "amount".
Test updates:
- tests/test_exemplar_ingest.py: extend _ROUND_1 with _ROUND_2;
test_list_corpora_loads_every_round_1_file now asserts every
committed corpus (round 1 + round 2) loads.
- tests/test_recognizer_registry.py: rename + repair
test_live_proposal_log_has_phase_c_pending_proposals →
test_live_proposal_log_has_phase_c_proposals. The original
asserted state=="pending"; PR #304 ratified the three, so the
test now asserts state=="accepted" and registry length matches.
Pre-existing failure on main, fixed here.
Validation:
- 132 passed across exemplar_ingest, recognizer_synthesis,
recognizer_match, recognizer_registry, candidate_graph_wiring,
admissibility_exemplars, refusal_taxonomy_lane,
admissibility_replay_gate
- 222 capability-axis tests passed / 2 pre-existing main failures /
3 skipped — G1..G5 + S1 wrong=0 invariant intact
- 67 smoke passed
- End-to-end CLI sanity check: `core teaching propose-from-exemplars
teaching/admissibility_exemplars/discrete_count_statement_v1.jsonl
--log /tmp/test.jsonl` produced proposal_id 8c7645b4..., state
pending, replay_equivalent=True, wrong_count_delta=0
Empirical projection: of 47 still-refused GSM8K train_sample
statements, ~22 match the discrete_count_statement recognizer, ~2
match multiplicative_aggregation, plus 3 rate_with_currency + 3
temporal_aggregation + 18 descriptive_setup_no_quantity recognized
under the existing round-1 wiring. After operator ratifies round-2
proposals, the candidate-graph skip-only wiring will drop those
sentences from the math state and a meaningful lift is projected.
wrong=0 preserved at every level by Phase D's skip-only
construction.
Scope: enables the round-2 pipeline; does NOT ratify anything;
does NOT modify generate/math_candidate_graph.py. Operator runs
propose-from-exemplars + review --accept after merge.
* chore(ADR-0163.C): land three Phase C pending proposals in live log
Phase C (#301) shipped the CLI but its PR dry-run wrote to a tmp log
path. This commit moves the three Phase C proposals into the live
teaching/proposals/proposals.jsonl so the Phase B→C audit trail is
visible in the proposal log and the proposals are ready for the
operator to ratify after Phase D ships.
Proposals (all state=pending, kind="exemplar_corpus"):
- 59223f13722f906a1cf9b65d9b01c990 — descriptive_setup_no_quantity
- 46ce297f797ff16da12db5de422ca3c9 — rate_with_currency
- a3b892546977c5f0f64c578d6052adbd — temporal_aggregation
Produced by `core teaching propose-from-exemplars --all` against the
live Phase B corpora. No ratification (ADR-0161 §5 — only the repo
owner ratifies). The Phase D admissibility-replay gate confirmed
replay_equivalent=true, wrong_count_delta=0 for all three.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(ADR-0163.D): wire ratified RecognizerSpecs into math_candidate_graph admissibility surface
Phase D is the first PR to extend the math admission surface. The
audit (#294) said the gap was admission, not operators, algebra,
substrate, or packs. Phase A measured the refusal taxonomy. Phase B
authored seeds. Phase C synthesized recognizers. Phase D wires
those recognizers into generate/math_candidate_graph.py.
Modules
- generate/recognizer_registry.py — pure projection over the proposal
log. Only proposals with source.kind="exemplar_corpus" AND
review_state="accepted" enter the tuple. Sorted by
(review_date, proposal_id). In-process cache keyed on log
(mtime, sha256) — no filesystem cache (ADR-0161 §1). Malformed
accepted specs raise RegistryLoadError citing the offending
proposal_id; silent drops are forbidden.
- generate/recognizer_match.py — per-category rules-only matchers
(no LLM, no embedding, no learned classifier). Honors the Phase C
synthesizer's narrowness rule: out-of-corpus currency symbols,
window units, and per-unit values do NOT match. Three matchers:
_match_descriptive_setup_no_quantity (zero-quantity surface),
_match_temporal_aggregation (event_count_per_window with
observed_window_units/quantifiers honored), _match_rate_with_currency
(currency_per_unit_rate with observed currency/per-unit/amount-kind
honored).
- generate/math_candidate_graph.py — narrowest-edit guard at the
per-statement choice loop. Before the existing
"no admissible candidate for statement" refusal, consult the
ratified registry. Recognized statements are dropped from
per_sentence_choices (zero math state) so the Cartesian product is
identical to "this statement was never there." Empty registry is
a no-op — backward compatibility preserved byte-identically.
Downstream consumption of parsed_anchors (turning recognized
rate/temporal surfaces into solver state that produces concrete
answers) is Phase E follow-up.
Tests (32 new)
- tests/_phase_d_fixture.py — synthetic in-memory ratified registry
built from the three Phase C pending proposals' content. Per
ADR-0161 §5 the agent does NOT ratify the live log; the synthetic
registry round-trips the real RecognizerSpec bytes the operator
will ratify after Phase D ships.
- tests/test_recognizer_registry.py (9) — empty/pending/wrong-kind
filtering, sort order, malformed-spec rejection, cache hit +
invalidation, live-log Phase C audit check.
- tests/test_recognizer_match.py (14) — per-category positive cases,
narrowness (out-of-corpus surface forms rejected), no-LLM import
check.
- tests/test_candidate_graph_recognizer_wiring.py (7) — empty registry
preserves existing refusal; synthetic registry: recognized
statements no longer trigger per-statement refusal;
wrong_count_delta == 0 on GSM8K train_sample; capability axes G1..
G5+S1 wrong=0 unchanged; per-category admission counts on the
refused-set; unrecognized statements still refuse with the
existing reason.
- tests/test_phase_d_replay_evidence.py (2) — full admissibility
replay gate under synthetic registry: replay_equivalent=true,
wrong_count_delta=0, every capability axis wrong=0; each
ratified recognizer admits >= 1 train_sample statement (wiring
is consequential).
Per-category fixture-based admission counts (synthetic registry vs
GSM8K train_sample refused-set sentences):
- descriptive_setup_no_quantity: 40
- rate_with_currency: 2
- temporal_aggregation: 7
Narrowness-invariant negative case results (matcher correctly
returns None on out-of-corpus / load-bearing-math surfaces):
- rate_with_currency: "She paid $5 for the book." (no per-unit)
- temporal_aggregation: "On Saturday she went to the store." (single day token)
- descriptive_setup_no_quantity: "There are some kids in camp." (indefinite quantifier)
Candidates for Phase B round 2 (3 of 20 temporal seeds match the
spec's structural commitment but not my surface regex — author_notes
explicitly flagged these as schema-gap edge cases):
- ta-v1-0004 "Mark does a gig every other day for 2 weeks."
- ta-v1-0012 "Robin walks 4 dogs every other day around the park."
- ta-v1-0019 "The pump fills the tank with 80 gallons over 6 hours."
Three landed wirings DO NOT shift the GSM8K train_sample baseline
counts under fixture (correct=3, wrong=0, refused=47 unchanged) —
Phase D's narrow wiring is wrong=0 safe by construction; lift to
"correct" requires Phase E's downstream parser-side consumption of
parsed_anchors. Capability axes G1..G5+S1 wrong=0 unchanged.
Cross-refs: ADR-0163 (Phase D), ADR-0057 (proposal review),
ADR-0151 (auto-proposal), ADR-0161 §5 (ratification boundary),
Phase A PR #297, Phase B PR #298, Phase C PR #301.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>