Commit graph

809 commits

Author SHA1 Message Date
Shay
fbbc57edff
feat(ADR-0172/W3): core eval math-contemplation CLI lane (#384)
Wires `decompose_audit()` into a new `core eval math-contemplation`
subcommand:

- `cmd_eval_math_contemplation` in `core/cli.py` dispatched via `cmd_eval`
  when `lane == "math-contemplation"`
- `--audit` (default: audit_brief_11.json) + `--output` (default:
  teaching/math_proposals/proposals.jsonl) with path-traversal validation
  (absolute paths and directory-escaping relative paths → exit 2)
- exit 0 success / exit 1 audit-not-found / exit 2 parse-error or rejection
- `--json` flag for machine-readable output
- idempotent: re-run on same audit writes byte-identical JSONL
- output sorted by proposal_id (inherits decomposer sort contract)
- forbidden: no auto-apply, no writes outside teaching/math_proposals/,
  no audit-file mutation
- `teaching/math_proposals/.gitkeep` directory scaffold committed
- `.gitignore` entry for `teaching/math_proposals/proposals.jsonl`
- 11 tests in `tests/test_adr_0172_w3_cli_lane.py`; runtime suite green
2026-05-27 12:58:31 -07:00
Shay
af3821f0ed
feat(ADR-0172/W2): audit-corpus decomposer (#383)
Add decompose_audit(audit_path) to teaching/math_contemplation.py.
Groups audit_brief_11.json refusal rows by
(refusal_reason, missing_operator), emits one
MathReaderRefusalShapeProposal per group of >=2 rows, each carrying a
4-step ReasoningTrace (observation -> grouping -> hypothesis ->
conclusion).

Determinism:
- Group iteration sorted by (refusal_reason, missing_operator).
- Evidence per group sorted by case_id.
- Output tuple sorted by proposal_id.
- 10x rerun -> byte-identical proposals + trace_ids.

Pure read-only: audit file is not mutated, no proposals written to
disk, no chat/field/generate/algebra imports.

Tests (tests/test_adr_0172_w2_decomposer.py): real-audit emission,
determinism (10x), evidence floor, change-kind dispatch over all four
heuristic branches, four-step trace, case_id sort, proposal_id sort,
empty input -> empty tuple, unmapped operator skip, missing file ->
FileNotFoundError, no-mutation contract.

Added to core test --suite teaching.
2026-05-27 12:39:53 -07:00
Shay
87790ad60b
test(ADR-0172/W0.1): add trace replay-equivalence pinning tests (#382) 2026-05-27 12:36:51 -07:00
Shay
981d764810
feat(ADR-0172/W1): MathReaderRefusalShapeProposal schema (#380)
New module `teaching/math_contemplation_proposal.py` defines the
`MathReaderRefusalShapeProposal` dataclass — the math-domain analog of
`TeachingChainProposal` for the Tier-1 contemplation corridor.

- `build_proposal` enforces all seven invariants: math domain, ShapeCategory
  enum membership, ≥2 evidence pointers, valid ChangeKind Literal, JSON-
  serializable payload, ≥40-char wrong_zero_assertion, and non-None
  reasoning_trace with a non-empty trace_id.
- `canonical_bytes` / `compute_proposal_id` produce stable sha256-based IDs;
  evidence reduced to evidence_hash, trace to trace_id for stability.
- `ReasoningTrace` imported under TYPE_CHECKING only (W0/A1 not yet merged);
  duck-typed at runtime via trace_id attribute.
- 16 tests cover all eight brief obligations plus freeze and sensitivity checks.
- `core test --suite teaching -q` green (17 passed).
2026-05-27 12:25:49 -07:00
Shay
f16ac96fb7
feat(teaching/W0): ReasoningTrace substrate for ADR-0172 Tier 1 (#379)
Schema-only module defining ReasoningStep / ReasoningTrace with
byte-identical canonical serialization and sha256 trace_id derivation.
Replay-equivalence is enforced by:

- sorted-key JSON, no whitespace, ensure_ascii=False, allow_nan=False
- recursive rejection of float values in payloads (replay hazard)
- step_index monotonicity from 0
- empty trace rejected
- Literal-checked step_kind across all eight Tier 1+2 kinds

No runtime hook. No import from chat/field/generate/algebra.
Downstream (W1 ShapeProposal, W2 decomposer) consume this schema.

Tests: 12 new, full teaching suite green (17 passed).
2026-05-27 12:21:59 -07:00
Shay
3aea5a1fa8
docs(ADR-0172): fix evidence-module path in Tier 1 brief pack (#381)
The brief pack referenced `teaching/audit_evidence.py` in 3 spots
(A2 schema field, A2 read-required list, B2 algorithm step 3a). The
actual module on main is `teaching/math_evidence.py` (carries
`MathReaderRefusalEvidence` per ADR-0167).

Sonnet (A2 / PR #380) discovered the discrepancy and correctly used
the real module. This patch corrects the brief so Wave B operators
(B1 / B2) do not hit the same gap on dispatch.

No runtime change. Pure docs.
2026-05-27 12:18:19 -07:00
Shay
2d5b119c6a
docs(ADR-0172): Tier 1 brief pack — W0 through W4 with parallel waves (#378)
Coordination artifact for ADR-0172 Tier 1 dispatch. Six PRs in the
ADR (W0, W0.1, W1, W2, W3, W4) collapsed into a 4-wave DAG with two
concurrent briefs per wave A and B.

Operators are dispatched in their own UIs by pointing at the
section heading — "read §Brief A1" etc. Bundling options A/B/C
inside the doc let the operator choose CI economy vs wall-clock.

No runtime change. Pure docs.
2026-05-27 12:07:59 -07:00
Shay
b190f3b6c5
feat(ADR-0170/W2): DCS-S1 acquisition verbs — first CandidateOperation emission (#377)
Second implementation PR of the ADR-0170 wave. Extends the DCS injector
to emit ``CandidateOperation(kind='add')`` for acquisition verbs
alongside the existing ``CandidateInitial`` emission for possession
verbs. Proves the W1 type-widening with real emission of both union
members.

## What changes

### `generate/recognizer_match.py`
- New `_ACQUISITION_VERBS` frozenset (12 verbs: collect/get/receive/buy
  inflections). Each member is a subset of `ADD_VERBS` so the downstream
  CandidateOperation post-init whitelist accepts the matched_verb token.
- Extractor now accepts either possession OR acquisition verbs and
  records `anchor_kind` (`"possession"` | `"acquisition"`) plus
  `verb_token` in the parsed anchor schema.

### `generate/recognizer_anchor_inject.py`
- `inject_discrete_count_statement` dispatches on `anchor_kind`:
  - `"possession"` → `CandidateInitial` (existing behavior unchanged)
  - `"acquisition"` → `CandidateOperation(add)` (new)
- New helper `_build_operation_from_discrete_count_acquisition`
  constructs the operation. Operand uses `_resolve_count_value`;
  matched_verb uses `_locate_token` for round-trip ground check.
- Return type uses `InjectorEmission` from W1.

### Tests
- `tests/test_adr_0170_w2_dcs_acquisition_verbs.py` (new) — 22 tests:
  - Verb-set membership pins
  - Acquisition ⊂ ADD_VERBS sanity check
  - Possession + Acquisition disjoint
  - Extractor records anchor_kind correctly
  - Injector emits CandidateOperation for acquisition verbs
  - Possession path still emits CandidateInitial unchanged
  - Deliberate exclusions (gained / donated / saved) still refuse
  - Case 0050 hazard pinned (does/contemplates not in either set)
  - Determinism + roundtrip_admissible passes

- Updated `tests/test_adr_0163_d2_discrete_count_injection.py` to
  reflect new anchor schema fields (anchor_kind, verb_token).

- Updated `tests/test_adr_0170_w1_injector_type_widening.py` —
  the DCS injector now legitimately returns
  `tuple[InjectorEmission, ...]` (not narrower).

## Deliberate exclusions

These verbs are NOT in `_ACQUISITION_VERBS` and the extractor refuses
them — preserving wrong=0:

- `gained / gains / gain` — delta-of-attribute (weight, age), not
  acquisition. Admitting as add-operation would risk wrong>0 on
  questions that ask total state.
- `donated / donates / donate` — SUBTRACT semantics (actor gives away).
- `saved / saves / save` — ambiguous (time vs money vs effort).

Widening this set is operator-reviewable per `feedback-wrong-zero-
hazard-case-0050` discipline.

## ADR-0131.G.1 branch-disagreement discipline preserved

The regex parser already emits `CandidateOperation(add)` for
acquisition verbs via `ADD_VERBS` for single-word units. The new DCS
injector path emits the same kind of operation for multi-word units
(where the regex parser fails). Collapsed-tie when both paths emit
identical operations on overlapping shapes; no disagreement.

## Test plan

- tests/test_adr_0170_w2_dcs_acquisition_verbs.py: 22 passed (new)
- tests/test_adr_0163_d2_discrete_count_injection.py: ~30 passed
  (existing tests updated for new schema fields)
- tests/test_adr_0170_w1_injector_type_widening.py: 6 passed
- tests/test_recognizer_skip_wrong_zero.py + brief_11b + brief_11 +
  candidate_graph_wiring + candidate_domain_partition: passed
- evals/gsm8k_math/train_sample/v1: counts=correct=3 refused=47 wrong=0
  unchanged (case 0023 still has S2/S3 downstream blockers; W2's value
  is infrastructure, not direct lift)

## Hard invariants

- `wrong == 0` preserved (case 0050 hazard pin + deliberate verb
  exclusions + roundtrip_admissible gate)
- ADR-0166: no new eval lanes
- No teaching-store / pack mutation
- ADR-0131.G.1 branch-disagreement discipline preserved (acquisition →
  operation, not initial)
- Five-layer wrong=0 safety net (ADR-0163.D.2) intact and extended

## W3 NOT in this PR — honest skip

Initial plan was to bundle W2 + W3 (A1 currency_amount injector).
Inspection of the 4 actual `currency_amount` GSM8K refusals showed
none match A1's narrow form (`<ProperNoun> earns|charges $<amount>`):

| Case | Statement | Reason narrow form doesn't fit |
|---|---|---|
| 0019 | "this requires 3 vet appointments, which cost $400 each" | anaphoric subject + multi-quantity |
| 0026 | "Aaron and his brother Carson each saved up $40" | multi-subject + "each" |
| 0028 | "It cost $100,000 to open initially" | pronoun subject |
| 0043 | "Her mother gave her an additional $4, and her father twice as much" | multi-clause + comparative + transfer |

Shipping W3 as-designed would have re-introduced the dead-code pattern
#373 just cleaned up. Skipped honestly; ADR-0172 Tier 1's decomposer
(the next wave) will surface category-shape mismatches like this
programmatically.
2026-05-27 12:07:54 -07:00
Shay
eeeec80127
docs(ADR-0172): math-domain corpus-decomposition mechanism (Learning Arc analog) (#376)
* docs(ADR-0172): math-domain corpus-decomposition mechanism (Learning Arc analog)

Scoping ADR for the math-domain analog of cognition's
`teaching/contemplation.py` corpus-decomposition loop (Learning Arc
milestone 2026-05-25).

## What this ADR scopes

A mechanism that reads the math audit corpus and emits
`MathReaderRefusalShapeProposal` records — structural commonalities
across N refusal cases, paired with the candidate mechanism change
that would resolve them (matcher extension, injector sub-shape,
vocabulary addition, frame reclassification).

Today the operator does this decomposition by hand (reads
audit_brief_11.md, identifies the commonality across 21 DCS
refusals, scopes the matcher/injector extension, files a focused PR).
ADR-0172 shifts the decomposition to the engine, with HITL
ratification preserved.

## Sequencing — explicit

ADR-0172 ships AFTER ADR-0170 (injector contract widening),
ADR-0168 (FrameClaim handler), and ADR-0169 (CompositionClaim
handler — reserved). Without those substrates, the decomposer can
identify patterns but cannot route them to a ratification handler
that knows how to materialize them. Cognition's learning arc
followed this same sequencing: substrate first, then decomposer.

## Why this matters

ADR-0167 LexicalClaim shipped the math-domain wire from refusal →
evidence → operator-ratification. ADR-0172 closes the gap to the
engine-decomposes loop — the moment cognition's learning arc
qualitatively shifted from "engine refuses + operator authors" to
"engine teaches itself through reviewed correction."

The Learning Arc memory entry (2026-05-25) names that moment as
when measurable progress accelerated. ADR-0172 makes the math-domain
trajectory toward the same loop explicit in the queue.

## Hard invariants preserved

- wrong=0 by construction (proposals are evidence-only)
- ADR-0166: no new eval lanes
- No teaching-store / pack mutation
- No non-deterministic mechanism (rule-based grouping, not learned
  classification)
- Cross-domain partition (ADR-0167 W2-C) preserves cognition
  contemplation behavior

No code, no test, no eval, no pack change in this PR.

## Cross-references

- ADR-0056/0057 — cognition contemplation/proposal substrate (template)
- ADR-0167 + FOLLOWUPS §1 — parent evidence wire
- ADR-0168 + ADR-0168.1 — FrameClaim (ratification target)
- ADR-0169 (reserved) — CompositionClaim (ratification target)
- ADR-0170 — injector contract widening (substrate prerequisite)
- Memory: Learning Arc Milestone 2026-05-25 — the moment to recreate
- Thesis: decoding, not generating — the principle preserved

* amend(ADR-0172): add Tier 2 — intensional contemplation with test-and-learn loop

Per operator feedback during ADR-0172 review: the corpus-decomposition
mechanism should not only emit explicit rules (extensional) but also
develop inference (intensional) — recognizing structural equivalence
classes across surface variations without enumerating them.

## Tier 2 — intensional contemplation

Engine recognizes that 'Sam has 5 apples' and 'Sam collected 5 apples'
carry the same canonical proposition structure, without an explicit
verb-list extension. Emits MathReaderInferenceProposal records that
name structural equivalence classes rather than enumerable rules.

This is the thesis word the original draft missed: rationalization.
Tier 1 ratifies rules; Tier 2 ratifies inference.

## Test-and-learn loop

Tier 2 proposals carry held-out test evidence:
1. Decomposer surfaces hypothesis
2. Held-out subset of corpus reserved
3. Bridge applied to held-out cases; admissibility gates run
4. Outcome scored (positive / negative / neutral)
5. Negative-evidence proposals auto-rejected before HITL
6. Operator reviews proposal + test result, not bare claim

This makes Tier 2 thesis-coherent: engine decodes a structural
pattern, tests it against unseen corpus cases, surfaces the test
result. Wrong=0 cannot leak through — held-out test failures reject
internally.

## Updated implementation outline

Tier 1 wave: W1-W4 (schema, decomposer, CLI, workbench integration)
Tier 2 wave: W5-W9 (schema, equivalence-class recognizer, test-and-learn
loop, HITL integration, bridge application path)

## Hard invariants preserved at both tiers

- wrong=0 by construction (Tier 1: evidence-only proposals; Tier 2:
  held-out test rejects wrong-admitting bridges internally)
- ADR-0166: no new eval lanes
- No non-deterministic mechanism (rule-based grouping + deterministic
  test-and-learn, not learned classification)
- Cross-domain partition preserves cognition contemplation behavior

* amend(ADR-0172): split Tier 2 test-and-learn into two-arm confirmation

Per operator feedback during ADR-0172 review: 'confirm against known
facts/prior solutions' is the missing arm. The Tier 2 test-and-learn
loop now has BOTH:

- Arm 1 (negative / wrong=0 on held-out refusals) — already drafted
- Arm 2 (positive / known-good preservation) — NEW

Arm 2 inherits ADR-0057's replay-equivalence contract: any
inferential bridge that would change a currently-correct outcome is
REJECTED INTERNALLY before reaching HITL, even if the new outcome is
defensible. Existing truth survives; new truth is gated.

Both arms must PASS or be neutral. Either arm rejecting → proposal
does not reach the operator. This makes the engine's reasoning
provably conservative: it confirms against truth it already knows AND
truth it hasn't yet decided.

The 5-step proposal lifecycle is updated to reflect both arms +
test-set partition + per-case verdict tables in the emitted proposal.

No code change. No runtime effect.

* amend(ADR-0172): add foundational reasoning-articulation substrate

Per operator feedback: for the engine to infer/test/learn from
feedback, it must first be able to ARTICULATE its own reasoning in a
structured, persistent, replayable form.

Articulation is the project thesis's 5th anchor ("listen → comprehend
→ recall → think → articulate → learn from reviewed correction →
replay"). Today CORE articulates SURFACE (templated realizer output)
but does not articulate REASONING — the inference chain that took the
engine from refusal corpus to hypothesis to proposal.

Without reasoning-articulation, none of the three loops can work:
- Loop 1 (self-test) has nothing to record about what it tested or why
- Loop 2 (HITL review) sees a black-box conclusion, not inference chain
- Loop 3 (feedback) has no specific step the operator can target with
  a rejection rationale

## Substrate: ReasoningTrace schema

Every proposal carries a typed, content-addressable
ReasoningTrace recording each inference step:

  ReasoningStep:
    step_kind: observation | grouping | abstraction | hypothesis |
               test_design | test_application | test_result | conclusion
    input_pointers: prior steps + evidence rows
    claim: human-readable assertion at this step
    justification: why the engine made the claim
    output_payload: type-discriminated by step_kind

The trace is byte-identical across replays of the same corpus +
verdict history. Inherits CORE's existing determinism discipline.

## Sequencing

Articulation ships FIRST (new W0 wave) — it is the prerequisite for
Tier 1 and Tier 2 and Loop 3. Each downstream wave emits or consumes
ReasoningTraces.

## Hard invariants preserved

- Deterministic-replay (trace byte-identical under same inputs)
- ADR-0057 replay-equivalence (trace IDs stable across reruns)
- No non-determinism added (rule-based step emission, not learning)
- ADR-0166: no new eval lanes

No code, no test, no eval, no pack change in this PR.
2026-05-27 11:43:53 -07:00
Shay
35a29ed2de
fix(tests): G2 comparative-counter excludes recognizer-path refusals + refresh report.json (#375)
The G.2 test \`_comparative_clause_refusal_count\` reads \`report.json\`
and counts refusals whose reason quotes a statement clause containing
comparative anchors ("more/less than", "twice as many", etc.). After
#359's wrong=0 fix, the candidate-graph emits two refusal-reason
families that both quote a statement:

1. "no admissible candidate for statement: '...'" — parser-path
   refusal (the comparative-parse-failure family this metric tracks).
2. "recognizer matched but produced no injection for statement:
   '...'" — recognizer-path refusal; the quoted statement may
   incidentally contain comparative anchors but the refusal cause is
   the missing injector, NOT the comparative parse.

The pre-#359 counter only saw family (1) reasons; post-#359 it
over-counts whenever a recognizer-path refusal quotes a statement
containing comparative anchors. This was the test failure A2's PR
(#369) and the cleanup PR (#373) both surfaced.

## Fix

Filter the counter to exclude family (2) explicitly. Recognizer-path
refusals are tracked separately by the recognizer-wiring test suite;
they don't belong in the G.2 metric.

Result on current main:
- total statements with comparative anchors in refusal reasons: 2
- parser-path: 1 (case 0009, the legitimate G.2-tracked refusal)
- recognizer-path: 1 (filtered out — incidental anchor in #359-format reason)
- G.2 metric correctly reports 1 < baseline 2 → assertion passes

## Also: refresh report.json

The checked-in \`report.json\` was generated pre-#359 with the legacy
refusal-reason format. The runner now emits the new format on every
run; checking in the current output makes the baseline reproducible
and clears the CI friction that A2 originally flagged.

## Test plan

- tests/test_adr_0131_G2_comparatives.py: 25 passed (was 24 pass / 1 fail)
- tests/test_adr_0131_G4_multi_clause.py + G5_aggregate + S1_rate_events: 105 passed
- tests/test_brief_11b_audit_artifact + step2_lexicon + recognizer_skip + brief_11_audit + wiring + partition + adr_0163_d2: 89 passed
- Total: 219 passed

## Hard invariants

- No runtime change
- wrong=0 invariant preserved
- ADR-0166: no new eval lanes
- No teaching-store / pack mutation
2026-05-27 11:26:25 -07:00
Shay
eb452da9be
feat(ADR-0170/W1): widen inject_from_match return type — no behavior change (#374)
First implementation PR of the ADR-0170 wave. Type-level widening only:
the recognizer-injector dispatch now returns
``tuple[InjectorEmission, ...]`` where
``InjectorEmission = CandidateInitial | CandidateOperation``.

The existing ``inject_discrete_count_statement`` continues to emit only
``CandidateInitial`` — the widening unlocks but does not exercise
operation emission. Subsequent W2-W5 PRs ship the per-injector emission
shapes:

- W2 — DCS-S1 acquisition verbs (CandidateOperation(add))
- W3 — A1 currency_amount (CandidateInitial reimplementation)
- W4 — A3 multiplicative_aggregation (CandidateInitial(product))
- W5 — A4 temporal_aggregation (deferred until apply_rate primitive)

## Changes

### `generate/recognizer_anchor_inject.py`
- New `InjectorEmission = Union[CandidateInitial, CandidateOperation]`
- `inject_from_match` return type widened to
  `tuple[InjectorEmission, ...]`
- `__all__` exports `InjectorEmission`
- Documentation comment names ADR-0170 §"Implementation outline"

### `generate/math_candidate_graph.py` (admissibility dispatch)
The per-statement admission loop now dispatches admissibility on the
concrete candidate type:

  if isinstance(c, CandidateInitial):
      if _initial_admissible(c): admitted.append(c)
  elif isinstance(c, CandidateOperation):
      if roundtrip_admissible(c): admitted.append(c)

No new admission semantics — each type is gated by the predicate it was
already gated by elsewhere in the codebase. The dispatch unifies the
injector path with the parser path.

### `tests/test_adr_0170_w1_injector_type_widening.py` (new)
- Pin: `InjectorEmission` union members are exactly the two candidate types
- Pin: `inject_from_match` return type is widened
- Pin: `inject_discrete_count_statement` still emits CandidateInitial (W1
  is type-level only)
- Hazard pin: case 0050 remains refused
- Hazard pin: unparseable-verb refusal path (#359) unchanged
- Anti-regression: canonical DCS narrow-form extraction still works

## Test plan

- tests/test_adr_0170_w1_injector_type_widening.py: 6 passed (new)
- tests/test_adr_0163_d2_discrete_count_injection.py: 21 passed
  (existing D.2 v1 injector regression)
- tests/test_brief_11b_audit_artifact.py + step2_lexicon +
  recognizer_skip_wrong_zero + brief_11_audit: 55 passed
- tests/test_candidate_graph_recognizer_wiring.py: 7 passed
- tests/test_candidate_domain_partition.py: 5 passed
- tests/test_adr_0131_G2_comparatives + G4 + G5 + S1_rate_events:
  130 passed
- Total: 225 passed
- evals/gsm8k_math/train_sample/v1: counts=correct=3 refused=47 wrong=0
  (unchanged; verified no behavioral regression)

## Hard invariants

- `wrong == 0` preserved (admissibility dispatch is type-aware but
  semantically identical to the parser path's gating)
- ADR-0166: no new eval lanes
- No teaching-store / pack mutation
- Five-layer wrong=0 safety net (ADR-0163.D.2) intact
- Reader path unchanged
2026-05-27 11:23:08 -07:00
Shay
ecc0072ea1
chore: remove stub injector + superseded docs (cleanup-as-you-find) (#373)
Three concrete cleanup items from the day's work, per the
cleanup-as-you-find memory principle.

## 1. Remove inject_rate_with_currency stub

PR #369 (A2 rate_with_currency) shipped a function that always returns
() with an extensive docstring documenting the Rate-not-in-SentenceChoice
schema gap. The function is dead at runtime — `_INJECTORS.get(category)`
returning None has the same downstream behavior as the function
returning (). The 16 tests pinned the empty-tuple return; the case-0050
hazard pin is duplicated in test_recognizer_skip_wrong_zero.py and
test_brief_11b_step2_lexicon.py.

The schema gap is now properly documented in ADR-0170 (PR #372). A
dispatch-table comment at the removal site retains the at-code pointer
to that ADR for anyone wiring a new injector.

Removed:
- `inject_rate_with_currency` function in generate/recognizer_anchor_inject.py
- Its `_INJECTORS` dispatch table entry
- Its `__all__` export
- tests/test_injector_rate_with_currency.py (371 lines, 16 tests)

## 2. Remove docs/handoff/GPT55-MOBILE-DISPATCH.md

Single-session travel-time scaffolding. The 5 tasks it named are
complete or superseded by ADR-0170's findings. Pure historical artifact.

## 3. Remove docs/handoff/WAVE-NEXT-INJECTORS.md

Superseded by docs/handoff/WAVE-NEXT-REVISED.md, which captures
everything load-bearing from the original brief in its A1–A4 findings
table. The "kept for history" justification didn't survive scrutiny:
the document was misframed (over-promised lift; misframed schema work
as injector work). Lessons captured in REVISED + ADR-0170.

Updated cross-references:
- WAVE-NEXT-REVISED.md: removed the "supersedes ... kept for history"
  pointer; tightened cross-reference list
- ADR-0167-FOLLOWUPS.md §7: rewrote pointer to name ADR-0170 + REVISED
  as the live plan rather than "the original is retained"

## Test plan

- 219 tests passed across G.2/G.4/G.5/S1/Brief 11/B1/B11A/wiring/partition/DCS-D.2
- evals/gsm8k_math/train_sample/v1/report.json untouched (regen
  surfaces a separate stale-baseline test issue — out of cleanup scope)
- No runtime behavior change

## Net impact

- 5 files removed (~1200 lines)
- 1 file modified for explanatory comment (~30 lines)
- 2 doc files updated to remove dangling cross-references
- 0 behavioral change
2026-05-27 11:08:14 -07:00
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
Shay
abf2fd6bc0
docs(wave-next): revised plan after A1-A4 findings + schema-gap backlog (#371)
The Wave-Next injector dispatch (A1-A4) surfaced findings that
invalidate three of the four briefs' lift assumptions:

- A1 currency_amount — sandbox-blocked; real lift potential intact
- A2 rate_with_currency — schema gap (Rate not in SentenceChoice
  union); PR #369 documents the gap with concrete 4-step plan
- A3 multiplicative_aggregation — Operation(multiply) spec wrong;
  correct emission is CandidateInitial(outer×inner); zero GSM8K cases
  match canonical narrow form anyway
- A4 temporal_aggregation — needs apply_rate primitive not in algebra

Three of four are schema-extension ADRs masquerading as injector work.
Only A1 is a true injector + sandbox-fix scenario.

Deliverables:

1. `docs/handoff/WAVE-NEXT-REVISED.md` (new) — supersedes
   WAVE-NEXT-INJECTORS.md. Pivots to DCS sub-shape expansion as the
   actually-tractable next wave (21-case bucket, existing v1 injector,
   #366 spec on main). Captures the three schema gaps + A1's
   preserved lift potential for separate ADR work.

2. `docs/handoff/ADR-0167-FOLLOWUPS.md` §7 (new) — points to the
   revised plan and summarises the four findings inline.

WAVE-NEXT-INJECTORS.md retained for history.

No code change. No runtime effect. Docs-only.
2026-05-27 10:28:15 -07:00
Shay
f3e0e694b8
fix(tests): update wiring test assertions to post-#359 refusal-reason semantics (#370)
The wrong=0 fix in #359 changed the candidate-graph's refusal-reason
format when a ratified recognizer matches but its v1 injector returns
():

- Pre-#359: silently drop the recognized statement and admit a partial
  graph from the rest — a wrong>0 hazard analogous to case 0050.
- Post-#359: refuse explicitly with reason "recognizer matched but
  produced no injection" naming the statement and recognizer category.

Three tests in `test_candidate_graph_recognizer_wiring.py` were written
against the pre-#359 silent-drop behavior:

1. `test_empty_registry_preserves_existing_refusal_reason` — asserted
   the old "no admissible candidate" was the only valid format. Updated
   to accept either the legacy format OR the new explicit-refusal
   format.

2. `test_recognized_rate_statement_no_longer_triggers_per_statement_refusal`
   — asserted that recognized statements should NOT cause a per-statement
   refusal (encoding the silent-drop premise). Inverted to assert the
   correct post-#359 behavior: recognized-but-uninjectable statements
   refuse EXPLICITLY, and the statement IS named in the diagnostic.
   Renamed to `_refuses_explicitly_post_wrong_zero_fix`.

3. `test_recognized_descriptive_statement_no_longer_triggers_per_statement_refusal`
   — same inversion + rename.

Renames preserve the original sites for git-blame continuity while
making the post-#359 contract the documented behavior.

No runtime change. wrong=0 invariant preserved.

Test plan:
- tests/test_candidate_graph_recognizer_wiring.py: 7 passed (was 3 fail / 4 pass)
- tests/test_candidate_domain_partition.py: 5 passed (no cognition regression)
- tests/test_brief_11b_audit_artifact.py + step2_lexicon + recognizer_skip_wrong_zero + brief_11_audit: 55 passed
- Total: 62 passed
2026-05-27 10:25:22 -07:00
Shay
b288c2fc5c
feat(injector/A2): rate_with_currency — explicit schema-refusal (#369)
Wave-Next A2 brief outcome: the Rate type (ADR-0122) DOES structurally
model a per-unit rate, but it is not a member of the per-sentence
injector contract's SentenceChoice union (CandidateInitial |
CandidateOperation). The injector therefore returns () and documents
the schema gap inline plus in audit_brief_11.md.

Lift count: 0 (expected — the brief explicitly anticipates this
outcome when the schema decision is "no"). Documenting the gap is
the deliverable.

- generate/recognizer_anchor_inject.py: new inject_rate_with_currency
  + dispatch-table entry routing ShapeCategory.RATE_WITH_CURRENCY.
- tests/test_injector_rate_with_currency.py: 16 tests pinning schema
  evidence, schema refusal, dispatch wiring, case 0050 hazard,
  determinism, and wrong=0 invariant.
- evals/gsm8k_math/train_sample/v1/audit_brief_11.md: appended
  Wave-Next A2 section documenting the schema decision, eval delta
  (3/0/47 unchanged), case 0050 hazard verification, and the
  CandidateRate follow-up sequencing.

Case 0050 hazard pin: sentence 0 ("Mark does a gig every other day
for 2 weeks.") carries no currency symbol — rate_with_currency
never matches it; case stays refused at sentence_index=0.
2026-05-27 10:16:53 -07:00
Shay
9792f66f90
feat(brief-B1): lexicon closure wave 3 — unknown_word 5→3, wrong=0 preserved (#368)
Adds 3 drain_token lemmas to en_core_math_v1 closing 2 of 3 remaining
lexicon_entry refusals from the prior wave:

- path (case 0049, new lemma)
- journey (case 0049 follow-on after path resolved)
- sees → alias of existing "see" lemma (case 0040)

The third remaining lexicon_entry refusal (case 0001, '+') is
deliberately NOT closed: '+' is an arithmetic operator literal, not a
lexical token. Adding it as drain_token would silently drop arithmetic
content from problems like "5 + 3 apples", a wrong=0 hazard. Documented
in the PR body and audit artifact.

Refusal taxonomy shift:
- unknown_word: 5 → 3 (-2)
- unresolved_pronoun: 3 → 4 (+1) — case 0049's pronoun barrier surfaced
- incomplete_operation: 20 → 21 (+1) — case 0049's quantity gap surfaced

Hard invariants:
- wrong == 0 (admitted=0, verified)
- case 0050 hazard pinned (refused at sentence_index=0)
- manifest checksum unchanged (per-category source file edit)
- no teaching-store mutation; no reader runtime change
2026-05-27 10:13:09 -07:00
Shay
4beda0bfd7
docs(handoff): Wave-Next injector briefs — A1-A4 + B1 + D1 + optional audit (#367)
Six dispatchable briefs covering the next-progress path on GSM8K
(correct from 3 → 10+, ADR-0163 Round-1 gate).

Wave A — four parallel recognizer-injector PRs:
- A1 currency_amount → Sonnet (2-4 cases lift)
- A2 rate_with_currency → Opus (schema decision required)
- A3 multiplicative_aggregation → Sonnet (first CandidateOperation)
- A4 temporal_aggregation → Sonnet (structural sanity)

Wave B — orchestrator-handled inline:
- B1 lexical-entry closure for 3 remaining cases

Wave D — sequential after A2 lands:
- D1 ADR-0169 CompositionClaim scoping → Opus

Optional background:
- Gemini recognizer registry audit (GPT-5.5 Task 3 unclaimed)

Dispatch gated on the #362→#366 cascade fully merging. Codex is
offline (rate limits); allocation reflects Sonnet + Opus + Gemini.

Each brief carries explicit case 0050 hazard pins, narrow-form
constraints, wrong=0 verification commands, and report-back questions.
2026-05-27 09:51:40 -07:00
Shay
81423df85f
docs(handoff): reconcile discrete_count_statement injector spec (#366) 2026-05-27 09:51:30 -07:00
Shay
fd9c36049c
docs(ADR-0168.1): choose math FrameClaim proposal adapter (#365) 2026-05-27 09:50:33 -07:00
Shay
cb94049679
docs(ADR-0168): scope FrameClaim ratification doctrine after compatibility audit (#364)
* docs(ADR-0168): scope FrameClaim ratification doctrine

* docs(ADR-0168): add prior ADR compatibility audit
2026-05-27 09:48:51 -07:00
Shay
00c3968937
fix(ADR-0167): route contemplation and proposal replay by candidate domain (#363)
* fix(teaching): select proposal replay gate from candidate domain

* test(teaching): pin domain-selected proposal replay gates

* fix(teaching): make contemplation probes domain-aware

* test(teaching): pin domain-aware contemplation partition
2026-05-27 09:43:16 -07:00
Shay
dbeb1b2f00
fix(ADR-0167): replace brittle partition git-status assertion with behavioral invariant (#362)
* fix(tests): replace brittle git-status partition assertion with behavioral invariant

* docs(ADR-0167): record closure of brittle partition git-status assertion

* fixup: restore FOLLOWUPS §6 (holonomy ablation) — unresolved, just shipped in #360
2026-05-27 09:31:13 -07:00
Shay
1534046638
docs(handoff): GPT-5.5 mobile/connector dispatch — 5 in-flight spec tasks (#361)
Five docs-only tasks GPT-5.5 can pick up via the GitHub connector while
the operator travels. Every task is read-only execution: spec / audit /
ADR drafting, never code or test changes. Risk profile: zero.

Tasks (each opens one PR):

1. ADR-0168 FrameClaim scoping draft (highest priority — next gate
   after the LexicalClaim slice)
2. discrete_count_statement injector specification audit (highest-
   leverage: 21/47 GSM8K refusals are this category)
3. Ratified-recognizer registry audit (informs Task 2 and future
   injector work)
4. FOLLOWUPS §6 holonomy ablation test specification
5. Cognition contemplation partition fix specification (FOLLOWUPS §5a)

Each task carries:
- Files to read first (with paths)
- Deliverable shape (what the output doc must contain)
- PR body requirements
- Explicit out-of-scope list

Hard constraints enforced for the mobile-connector mode:
- One PR per task; explicit file staging; never git add -A
- Markdown-only (CLAUDE.md §Documentation Discipline)
- No code edits — flag in PR body if a task starts needing one
- No engine_state/ commits
- Cite filenames + line numbers; verify before committing

If all five complete, GPT-5.5 opens a meta-PR linking them into
ADR-0167-FOLLOWUPS.

No code change. No runtime effect. Docs-only.
2026-05-27 09:12:13 -07:00
Shay
1cc05d9cce
docs: schema-defined proof obligations doctrine + convergence-site invariant + FOLLOWUPS §6 (#360)
Three small surgical anchors capturing the verified architectural
insight surfaced during the ADR-0167 wave (no new ADR — the gap claim
that prompted this resolved on verification; what remains is a sharper
residual question worth memorialising).

1. CLAUDE.md — new "Schema-Defined Proof Obligations" section between
   Documentation Discipline and Validation Through CLI.  Generalises
   the wrong=0 invariant pattern: schema types that name structural
   properties are real only when an executing test can meaningfully
   fail under the violations it is written to catch.  Three-step rule
   for treating a schema as load-bearing.

2. language_packs/compiler.py — ARCHITECTURAL INVARIANT comment on
   _apply_mounted_primary_domain_resonance naming it as the single
   convergence-decision site for DEPTH_ROOT/DEPTH_RELATION packs.
   Anchors the doctrine at the code site so any future modification
   trips on the reference to the holonomy proof's coverage gap.

3. docs/handoff/ADR-0167-FOLLOWUPS.md §6 — captures the structural-vs-
   blend convergence isolation question.  HolonomyAlignmentCase IS
   executed today (we verified), but the existing test doesn't
   distinguish structurally-derived convergence from blend-induced
   convergence.  Ablation test or reframed claim — both acceptable
   resolutions.

Verified before commit:
- All 13 architectural references in the Gemini analysis resolve
  exactly: triliteral 0.30, root 0.40, prefix 0.03/(idx+1), stem 0.24,
  _INFLECTION_PRIORITY case-near-last, _apply_mounted_primary_domain_resonance
  with 40% English-prototype blend, HolonomyAlignmentCase defined
  AND executed
- tests/test_alignment_graph.py: 8 passed (no behavioural change)
- Documentation discipline (#355) honoured: pure Markdown, no HTML

No code behaviour changes.  No runtime effect.  Drops the larger
ADR-0168-PROPOSAL idea — the gap claim that prompted it dissolved
under verification.
2026-05-27 07:52:20 -07:00
Shay
97b0ee0e13
fix(wrong=0): refuse on recognized-but-uninjectable statements + audit taxonomy + 2 surfaced regressions (#359)
## Summary

Two test failures on origin/main both trace to PR #315 (ADR-0163.D.2 —
discrete_count_statement recognizer + admissibility-intent chain). Earlier
runs treated them as "pre-existing unrelated" — they are not unrelated.
The first is a real wrong>0 hazard.

## Failure 1: silent admission via recognized-but-uninjectable statement

The ratified `discrete_count_statement` recognizer over-matches: ANY
sentence containing a number + noun resolves it, irrespective of the verb.
When `inject_from_match` returns `()` (the round-2 default for v1
categories without an injector), the old code path used `continue` to
silently drop the statement — and the solver then answered from whatever
initial state remained.

Reproduction:
  parse_and_solve("Sam has 5 apples. Sam contemplates 3 apples. "
                  "How many apples does Sam have?")
  → is_admitted=True, answer=5.0  (silent admission of partial graph)

This is exactly the case-0050-class hazard wearing a different hat
(silently admitting an incomplete graph at the problem level).
ADR-0167 / Brief 11 §"correct-count greed" established the principle on
the reader path; this commit extends it to the recognizer path.

Fix: when a recognizer matches but produces no injection, REFUSE.

  generate/math_candidate_graph.py:
    - Replaced the skip-only `continue` with a CandidateGraphResult
      refusal carrying the recognizer category in the reason.

  tests/test_math_candidate_graph.py:
    - test_unparseable_statement now accepts either the legacy
      "no admissible candidate" reason or the new
      "recognizer matched but produced no injection" reason.
      Both legitimately refuse; what matters is is_admitted=False.

  tests/test_recognizer_skip_wrong_zero.py (NEW):
    - 5 regression tests pinning the wrong=0 invariant:
      * 3 parametrized verbs unknown to both regex parser and reader
        (contemplates / ponders / memorises) — must all refuse
      * Nonsense token — must refuse
      * Anti-regression: known initial + known operation still admits

## Failure 2: cognition audit drop-reason taxonomy

The audit test hardcoded `dropped.reason.startswith("superseded_by:")`
as the only valid drop-reason prefix.  Commit da70919 (ADR-0163.D.2)
ratified an admissibility-intent chain that the audit categorizes with
reason `unsupported_intent:admissibility`, which fails this assertion.

Fix: tests/test_teaching_audit.py — expand the allowed-prefix set to
include `unsupported_intent:` with a written rationale.  Future drop
classes extend the allowlist deliberately rather than silently
broadening the assertion to any non-empty reason.

## Surfaced regression: partition-test allowlist (ADR-0167 FOLLOWUPS §2)

This PR modifies three test files that the
test_existing_cognition_tests_untouched assertion would reject under
its named-allowlist scheme.  Added the three test paths to the allowlist
as the tactical fix; the architectural fix (retire / move to CI / move
to CODEOWNERS) is queued in docs/handoff/ADR-0167-FOLLOWUPS.md §2.

## Test plan

  uv run pytest tests/test_recognizer_skip_wrong_zero.py \
                tests/test_math_candidate_graph.py \
                tests/test_teaching_audit.py \
                tests/test_candidate_domain_partition.py \
                tests/test_math_evidence_e2e.py \
                tests/test_math_evidence_schema.py \
                tests/test_math_contemplation_adapter.py \
                tests/test_math_claim_signature.py \
                tests/test_math_lexical_ratification.py \
                tests/test_brief_11b_audit_artifact.py \
                tests/test_brief_11b_step2_lexicon.py \
                tests/test_brief_11_audit.py
  → 152 passed

## Hard invariants

- wrong == 0 — restored on the recognizer path (was silently violated on main)
- ADR-0166 — no new eval lanes
- No teaching-store mutation, no pack mutation
- The reader path was already correct (it refused these cases); this fix
  brings the regex/recognizer path back in line
2026-05-27 07:42:54 -07:00
Shay
a7d1547fbb
docs(ADR-0167): follow-ups queue — 5 named items deferred from LexicalClaim slice (#358)
Captures the named follow-ups that surfaced during the ADR-0167 wave so
they don't drift. Five items, each with scope / why-deferred /
breadcrumbs / acceptance criterion:

1. Frame-opener sub-types (FrameClaim / CompositionClaim / ReferenceClaim
   / SlotClaim) — four additional handlers, each its own ADR
2. Partition test architectural fix — current git-status-at-test-runtime
   assertion is structurally brittle (3 options outlined)
3. Two pre-existing main failures (test_unparseable_statement,
   test_audit_real_corpus_runs_clean) — fix or quarantine, don't ignore
4. Workbench v1 math-candidate rendering — ADR-0167 §Q4
5. Cross-domain partition risks Gemini flagged (contemplation pack
   indexing, replay gate default)

Includes leverage-based sequencing recommendation (no timelines per
project convention).

Docs-only. No code, no test, no eval, no pack change.
2026-05-27 07:39:22 -07:00
Shay
cc6f13a939
feat(ADR-0167/W3-A): e2e determinism + cognition regression — LexicalClaim slice closed (#357)
Wave 3, closes the LexicalClaim slice of ADR-0167.  After this PR the
math reader's refusal taxonomy is evidence, not terminus: lexical
refusals flow through audit row → typed evidence → dedup signature →
HITL ratification (W2-D) → pack write → next-audit-pass-resolves.

Deliverables
------------
- tests/test_math_evidence_e2e.py (new, 7 tests):
  * test_full_pipeline_from_audit_to_evidence
  * test_e2e_replay_equivalence
  * test_lexical_ratification_advances_unknown_word_row (case 0040 'sees')
  * test_e2e_determinism_across_processes
  * test_cognition_teaching_corridor_unaffected
  * test_evidence_dedup_via_claim_signature
  * test_audit_artifact_round_trip_with_signatures
- evals/gsm8k_math/train_sample/v1/audit_brief_11.md: Post-W2 baseline
  table + cognition regression line + case 0050 hazard status + pointer
  to the new e2e regression module.
- tests/test_candidate_domain_partition.py: minimal allowlist patch to
  test_existing_cognition_tests_untouched so that future ADR-0167 PRs
  can add their own evidence test files without tripping a structurally
  brittle hard-coded whitelist (W2-C partition risk; recorded in PR body).

Hard constraints held
---------------------
- wrong == 0: case 0050 hazard still refuses at sentence_index 0
  after the tmpdir-pack 'sees' ratification; no admission introduced.
- Cognition regression: zero modifications to cognition test bodies;
  only the W2-C whitelist assertion was loosened.
- Determinism: in-process and cross-process evidence_hash byte-identical.
- No real-pack mutation: a per-test digest fixture asserts
  language_packs/data/en_core_math_v1/ is byte-identical before and
  after each test.

Out of scope
------------
- Frame/Composition/Reference/Slot ratification handlers (follow-up ADRs).
- Workbench v1 wiring of math candidates (ADR-0167 §Q4).
- Auto-ratification — HITL only, forever.
- The two partition risks Gemini flagged in W2-C (cognition pack indexing,
  replay-gate default) remain follow-up.

With this PR merged the engine can ratify math-domain lexical claims
from its own refusal evidence through the existing HITL teaching
corridor — the thesis claim of ADR-0167 becomes a concrete green test.
2026-05-27 07:27:24 -07:00
Shay
94fca8ea5d
docs: Brief 11 EOD status footer + 2026-05-27 session narrative (#356)
Two docs-only updates capturing the day's work:

1. Appended a "Status update — 2026-05-27 EOD" footer to the Brief 11
   handoff doc with the completion table (11A/11B-step-1/11B-step-2
   docs+lexicon/11D merged; 11C absorbed into W3-A; 11D candidate E ADR
   merged) and the current post-#348 baseline taxonomy.

2. New session doc SESSION-2026-05-27-adr-0167-parallel-dispatch.md
   alongside the existing SESSION-2026-05-26-comprehension-reader.md.
   Captures the architectural pivot (audit-as-teaching-evidence vs the
   rejected refusal-class dispatch table), the parallel-dispatch
   experiment (5 operators / 3 waves / 6 PRs), what worked, what
   surfaced as load-bearing (case 0050 hazard), and what's deferred.

No code changes. No runtime effect.
2026-05-27 07:21:01 -07:00
Shay
7e6c4e81f7
docs(CLAUDE.md): documentation discipline — markdown-first, Mermaid + <details> sanctioned, no standalone HTML (#355)
Adds an explicit Documentation Discipline section between Semantic Pack
Discipline and Validation Through CLI. Encodes the stance that
text-diffable Markdown is the substrate for ADRs, session docs, audit
artifacts, and handoff briefs.

Sanctions two zero-cost adoptions inside the existing format:
- Mermaid fenced blocks for state machines / sequences / dependency
  graphs that genuinely benefit from a picture
- <details> / <summary> collapsibles for long proofs, large tables,
  generated logs

Out of scope (explicitly):
- Standalone HTML artifacts with embedded CSS / inline SVG / sidebars
- Dashboards / status pages / visualizers as substitute for a pinned
  data artifact

Rationale: the "open in browser" model breaks git diff, determinism
(CSS / SVG element ordering), and cross-agent legibility — all
load-bearing properties for a deterministic cognitive engine.

No code changes. No runtime effect.
2026-05-27 07:06:03 -07:00
Shay
e2e53362f5
feat(ADR-0167/W2-D): lexical ratification handler (#354) 2026-05-27 06:57:37 -07:00
Shay
85bfa188ed
feat(ADR-0167/W2-B): lexical claim signature + dedup (#353)
Adds `teaching/math_claim_signature.py` with `lexical_claim_signature()`:
sha256 hex of a normalised lexical token, collapsing two refusal cases on
the same surface token into one teaching-corpus candidate.

Normalisation pipeline (documented in module, breaking-change surface):
  1. Lowercase surface
  2. Strip string.punctuation from both ends (!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~)
  3. Extract token from refusal_detail via r"no primitive or lexicon match for '([^']+)'"
  4. Fallback: use stripped-lowercase surface if regex doesn't match
  5. Canonical: "lexical:" + extracted_token
  6. sha256 hex of UTF-8 bytes → 64-char lowercase hex

Also adds `teaching/math_contemplation.py` (W2-A adapter included as
union-merge; W2-A worktree was not yet dispatched):
  - `audit_to_evidence()`: AuditRow iterable → MathReaderRefusalEvidence tuple
  - `audit_problem_to_evidence()`: convenience wrapper for tests and W3-A
  - Lexical evidence: claim_signature filled; evidence_hash recomputed to include it
  - Non-lexical sub_types: claim_signature stays "" (deferred per ADR-0167 §Q1)

Real-data result on audit_brief_11.json:
  - 14 distinct lexical tokens → 14 distinct signatures (no false collisions)
  - No duplicate tokens in the 50-case sample; dedup logic verified deterministic

Wave 2, parallel with W2-C/D; depends on W1-A branch.
wrong=0 verified by passing regression suite.
2026-05-27 06:56:36 -07:00
Shay
9da61b96a0
feat(ADR-0167/W2-A): audit-to-evidence adapter (#352)
Wave 2, parallel with W2-B/C/D. Implements the type-A→type-B converter
from AuditRow to MathReaderRefusalEvidence per ADR-0167 W2-A brief.

Deliverables:
- teaching/math_contemplation.py:
  - audit_to_evidence(audit_rows): pure deterministic adapter, uses
    SUB_TYPE_FOR_OPERATOR for subtype assignment, skips rows where
    missing_operator is None, leaves claim_signature="" (W2-B will fill)
  - audit_problem_to_evidence(problem_text, case_id): convenience wrapper
    that runs the reader and adapts the output
- tests/test_math_contemplation_adapter.py: 8 tests covering
  determinism, input-order preservation, sub-type mapping
  exhaustiveness, distinct hashes across cases, empty input handling,
  None-operator skip, and round-trip from problem text

Invariants:
- Deterministic across reruns (verified by determinism rerun)
- No I/O in adapter path
- Input order preserved (no internal sort)
- claim_signature == "" for all W2-A records (W2-B coordination)

Validation:
- tests/test_math_contemplation_adapter.py: 8 passed
- tests/test_math_evidence_schema.py: 11 passed (W1-A regression)
- tests/test_brief_11b_audit_artifact.py + step2_lexicon + brief_11_audit:
  45 passed (regression)
- Determinism rerun: identical results
2026-05-27 06:44:46 -07:00
Shay
05aaff224e
feat(ADR-0167/W2-C): domain discriminator + cross-domain audit (#351)
* feat(ADR-0167/W1-A): MathReaderRefusalEvidence schema + canonical-bytes

Foundation type for routing comprehension-reader refusals into the
teaching corridor.  Frozen dataclass with sha256 evidence_hash computed
from deterministic canonical bytes (mirrors state.to_canonical_bytes
pattern).  Includes SUB_TYPE_FOR_OPERATOR mapping table covering all 13
missing_operator values in the current audit artifact.

Wave 1 only — no runtime mutation, no teaching-store integration, no
admission path.  Downstream W2-A/B/C/D type-import from this module.

* feat(ADR-0167/W2-C): domain discriminator + cross-domain audit

- Links to the audit doc: docs/handoff/ADR-0167-W2C-cross-domain-audit.md
- Inventory details: 5 construction sites, 8 consumption sites
- Verification: 0 cognition test files were modified; all tests are green
- Downstream partition work flagged: contemplation indexing (in teaching/contemplation.py) and replay gate (in teaching/proposals.py)
2026-05-27 06:44:29 -07:00
Shay
99c11d160a
feat(ADR-0167/W1-A): MathReaderRefusalEvidence schema + canonical-bytes (#350)
Foundation type for routing comprehension-reader refusals into the
teaching corridor.  Frozen dataclass with sha256 evidence_hash computed
from deterministic canonical bytes (mirrors state.to_canonical_bytes
pattern).  Includes SUB_TYPE_FOR_OPERATOR mapping table covering all 13
missing_operator values in the current audit artifact.

Wave 1 only — no runtime mutation, no teaching-store integration, no
admission path.  Downstream W2-A/B/C/D type-import from this module.
2026-05-27 06:30:21 -07:00
Shay
4f0815ef9a
docs(ADR-0167): audit-as-teaching-evidence (math reader → contemplation wire) (#349)
* docs(ADR-0167): audit-as-teaching-evidence (math reader → contemplation wire)

Scoping ADR for Brief 11D Candidate E. Routes math-reader refusal audit
rows into the existing contemplation/HITL teaching corridor as a new
candidate source (`MathReaderRefusalEvidence`).

Key decisions:
- Evidence-only — never directly admits a math fact; only ratification
  through HITL queue can change runtime behaviour
- Five sub-types proposed (Lexical / Frame / Composition / Reference /
  Slot claims) mapping to the audit taxonomy
- Scope first to LexicalClaim — lowest-risk, highest-count
- Six open questions called out for the implementation ADR

ADR-0166 three-question test passes; implementation passes only when
the six open questions are answered with LexicalClaim-first scope.

No code in this PR.

* docs(ADR-0167): parallel work plan — 6-PR/3-wave dispatch across 5 model operators
2026-05-27 06:21:43 -07:00
Shay
66ef4ad07c
feat(brief-11/11B-step-2): lexicon closure — unknown_word 11→5, wrong=0 preserved (#348)
## Summary

Lexicon-entry closure track per Brief 11D recommendation (Candidate A,
sub-PR 1). Adds 12 drain_token lemmas + 1 alias to `en_core_math_v1`.

`unknown_word` row strictly decreases: **11 → 5** (-6 cases moved past
the first-pass vocabulary gap). `wrong == 0` preserved. `correct` does
not move because admitted=0 (the unblocked cases now refuse at
downstream frames — real new work becoming visible, not regression, per
Brief 11 §Gate 1).

## Additions (all category=drain_token)

| Lemma     | Surfaced from              |
|-----------|----------------------------|
| along     | case 0049 (3rd-wave)       |
| animals   | case 0040 (3rd-wave)       |
| decrease  | case 0005                  |
| jacks     | case 0024 (jumping jacks)  |
| length    | case 0006 (3rd-wave)       |
| previous  | case 0006                  |
| reach     | case 0015                  |
| stray     | case 0040                  |
| too       | case 0039                  |
| uphill    | case 0049                  |
| which     | case 0001                  |
| your      | case 0001 (3rd-wave)       |
| weight → weights (alias) | case 0021     |

All classified as `drain_token` (the only category that cannot open a
frame and therefore cannot create wrong admissions per Brief 11
§"correct-count greed" doctrine). Reclassifying any as
accumulation/depletion/transfer verbs would risk wrong>0 by opening a
malformed operation_frame.

## wrong=0 verification

- `assert audit_problem(case_0050)` returns `ReaderRefusal` at
  sentence_index 0 (pinned by `test_hazard_case_0050_remains_refused_pre_frame`)
- 50-case audit: `admitted=0, refused=50` (pinned by
  `test_no_case_admits_after_lexicon_closure`)
- No reader runtime changes; pack-only mutation in a single
  per-category source file
- Manifest checksum unchanged: source-file edit doesn't regenerate the
  compiled `lexicon.jsonl`; loader reads per-category sources for
  alias-aware entries (see `generate/comprehension/lexicon.py:127`)

## Test plan

- 11 new tests in `tests/test_brief_11b_step2_lexicon.py`:
  - 4 pack-additions pinning (categories, provenance, aliases, sort order)
  - 4 reader-effect / hazard tests (admitted=0, case 0050 refused,
    unknown_word row strictly decreased, manifest checksum unchanged)
  - 2 loader-integrity tests (new lemmas + aliases resolve through
    `load_lexicon` → `lookup`)
- 12 existing tests in `tests/test_brief_11b_audit_artifact.py` pass
  (taxonomy counts updated to post-step-2 values)
- 23 existing tests in `tests/test_brief_11_audit.py` pass

## Hard invariants preserved

- `wrong == 0` — no admissions, no frame-opener miscategorisation
- ADR-0166 — no new canonical eval lanes; existing
  `gsm8k_math/train_sample/v1/` artifact updated in-place
- No teaching-store mutation; pack mutation is explicit, single-file,
  reviewed
- Manifest checksum unchanged (compiled lexicon.jsonl byte-identical)

## Follow-up

- 3 lexicon_entry refusals remain (case 0001 '+', case 0040 'sees',
  case 0049 'path'). Not addressed in this PR: '+' is an arithmetic
  literal (would change semantics of drain), 'sees' and 'path' have
  many other downstream barriers. Address with next-bottleneck PR.
- The 6 cases now refusing at later frames feed directly into Brief
  11D Candidate A sub-PR 2 (which bottleneck class to attack next).
2026-05-27 06:06:41 -07:00
Shay
40ccefeaa8
docs(brief-11/11B-step-2): verb-classification analysis for pre_frame_filler_sentence (#347)
Per Brief 11B-step-2 §Hard constraints: no safe runtime/pack change lifts
any of the 8 pre_frame_filler_sentence cases without violating wrong=0.
This PR publishes the verb-classification analysis as documentation and
leaves the reader runtime and en_core_math_v1 pack unchanged.

Per-case classification:
- 0002 (splits): drain_token; honest blocker is compound_numeric_literal
- 0016 (traveled): drain_token; honest blocker is multi_quantity_composition
- 0025 (go/picking): drain_token; no quantity in sentence (true filler)
- 0028 (opens): drain_token; no quantity (true filler)
- 0030 (decides/go): drain_token; no quantity (true filler)
- 0035 (decided/split): drain_token; no quantity (true filler)
- 0036 (studying): drain_token; no quantity (true filler)
- 0050 (does): modal_aux; HAZARD — naive drain produces wrong>0
              because next sentence admits Operation(mark, add, 3, songs)
              while the answer requires frequency-by-duration aggregation
              (every other day for 2 weeks); blocker is out of scope.

Post-skip simulation: even with the offending sentence elided, every
case still refuses on a downstream bottleneck (lexicon_entry,
pronoun_resolution, unit_binding, fraction_percentage_literal). Zero
lifts are available in Brief 11B-step-2 scope.

wrong=0 verification: no change to lifecycle.py / lexicon.py / audit.py /
en_core_math_v1/**; parent invariants from test_brief_11b_audit_artifact
continue to hold (admitted=0, refused=50, wrong_count=0).

Tests: 11 new tests in tests/test_brief_11b_step2_verb_classification.py
pinning the 8-case enumeration, post-skip refusal taxonomy per case,
hazard case 0050 remaining refused pre-frame, and the 50-case
admitted=0/refused=50/wrong=0 invariant.
2026-05-27 05:59:14 -07:00
Shay
bb8ea3bbf9
docs(brief-11/11D): next-capability proposal — comparison + recommendation (#346)
Closes the Brief 11 sequence with a decision artifact (not a roadmap)
selecting the next capability after GSM8K Phase 2 reader closure.

Four candidates compared against ADR-0166's three-question test:
- A. Continued GSM8K operator closure
- B. Cross-domain reader generalization
- C. Tool-use trace integration
- D. Workbench demo hardening

Recommendation: continued GSM8K operator closure, starting with the
`lexicon_entry` row of the Brief 11B audit. The only candidate that
passes Q1/Q2/Q3 cleanly today and has an explicit Round-3 finish line.

Docs-only. No code, no test, no eval, no pack change.
2026-05-27 05:59:10 -07:00
Shay
9fc31eeaa4
feat(brief-11/11B): reader closure audit artifact — full taxonomy + rejected naive fix (#345)
## Summary

PR 11B in the Brief 11 sequence. Closes the missing-operator inference gap
left by 11A (#343) and ships the per-case audit artifact that Brief 11 §Gate 2
identifies as "the main Brief 11 artifact."

## Why this PR does NOT touch the reader runtime

The naive closure fix for `pre_frame_filler_sentence` (drain
`statement_terminator` at pre-frame) lifts 2 cases from refused → admitted
but creates a `wrong > 0` hazard on `gsm8k-train-sample-v1-0050`:

```
Mark does a gig every other day for 2 weeks.  For each gig, he plays 3 songs.
... How many minutes did he play?
```

With the drain enabled, the reader admits `Operation(mark, add, 3, songs)`
with unknown unit `minute` and would project to a wrong answer. The stricter
variant (`pending_entity_ref is None` + no quantities) fires on 0 of the 11
candidate cases. Per Brief 11 §"Failure modes to avoid §1 — Correct-count
greed," this PR rejects both variants and routes the closure fix to a
follow-up that adds the required verb vocabulary or sentence-intent
classifier.

## Deliverables

- `generate/comprehension/audit.py` — three new missing-operator labels:
  - `pre_frame_filler_sentence` (8 cases)
  - `descriptive_frame_question` (2 cases)
  - `question_frame_slot` (1 case)
  Closes the 11-case `None`-operator gap left by 11A.
- `evals/gsm8k_math/train_sample/v1/audit_brief_11.json` — per-case audit
  artifact pinned by tests.
- `evals/gsm8k_math/train_sample/v1/audit_brief_11.md` — narrative summary
  including the rejected-fix design tension and ranked Brief 11B-step-2
  backlog.
- `tests/test_brief_11b_audit_artifact.py` — 12 tests pinning the new labels,
  the per-case artifact, the wrong=0 invariant, and the refusal taxonomy.

## Bottleneck taxonomy (after Brief 11B labelling)

| missing_operator              | count | category               |
|-------------------------------|------:|------------------------|
| quantity_extraction           | 9     | incomplete_operation   |
| lexicon_entry                 | 9     | unknown_word           |
| multi_quantity_composition    | 8     | incomplete_operation   |
| pre_frame_filler_sentence     | 8     | unexpected_category    |
| pronoun_resolution            | 3     | unresolved_pronoun     |
| fraction_percentage_literal   | 3     | unexpected_category    |
| unit_binding                  | 3     | unattached_quantity    |
| descriptive_frame_question    | 2     | unexpected_category    |
| (others, 1 each)              | 5     | various                |

## Test plan

- 12 new tests in `tests/test_brief_11b_audit_artifact.py` pass
- 23 existing 11A tests in `tests/test_brief_11_audit.py` pass
- No runtime changes; reader byte-identical to main

## Hard invariants preserved

- `wrong == 0` — no runtime change, no new admissions
- ADR-0166 — no new canonical eval lanes added; existing
  `evals/gsm8k_math/train_sample/v1/` artifact set extended
- No teaching store / pack mutation

## Follow-up

- **11B-step-2** — verb-vocabulary expansion or sentence-intent classifier
  for `pre_frame_filler_sentence` (8 cases). See audit_brief_11.md §"design
  tension" for the rejected one-line variants and why they fail wrong=0.
- **11C** — existing-lane capability snapshot (still gated on 11B-step-2 or
  another closure pass).
2026-05-27 05:35:06 -07:00
Shay
aa53fcf78d
feat(brief-11/11A): reader closure audit — per-case refusal taxonomy, graph-completeness helpers, regression tests (#343) 2026-05-27 05:14:42 -07:00
Shay
60043973b0
feat(comprehension/10): Phase 2 statement-frame reader (ADR-0164.4) (#335)
Extend the comprehension reader from question-only scope to whole-
problem scope. Phase 1 (Brief 8 / #326) implemented question_frame;
this brief implements initial_state_frame, operation_frame, and
descriptive_frame, plus finalize() projection into a strict
ADR-0115 MathProblemGraph.

Architecturally correct under ADR-0164.3; not yet productive on
GSM8K train_sample. Below-floor measurement documented; specific
bottlenecks tabled for Phase 2.1 follow-up.

What landed

- Frame-opener dispatch in lifecycle.py for the three new statement
  frames, plus rule handlers (_rule_op_*, _rule_preframe_*,
  _rule_descriptive_*).
- finalize(state) -> MathProblemGraph | ReaderRefusal: pure
  projection with closure checks (entity registry non-empty,
  unknown target bound, every op/initial references a known entity,
  Decimal precision projects losslessly).
- _classify extended to 3-tuple (category, surface, decimal_value)
  with possessive strip retry. Brief 8.2's sentence-initial
  lookup-first + gender-skip preserved AND extended to mid-sentence
  (gender is enrichment everywhere, never admission).
- Whole-problem coexistence dispatch in math_candidate_graph.py
  (config.comprehension_reader_questions=True): reader attempts the
  whole problem; on any ReaderRefusal falls through to existing
  regex parser. All-or-nothing per the brief.
- Lexicon expansion (carried into renamed proper_noun_gender_*
  files): +2 accumulation_verb (adopt, invest), +2 currency_unit_noun
  (dollar, cent), +6 capacity_verb (fill, lift, play, work, finish,
  drive), +5 female names (allison, brooke, jan, marion, sidney),
  +14 male names (bart, fernando, georgie, jake, jed, jeremie, jose,
  orlando, rex, rudolph, steve, troy, xavier, yun), +numerous
  count_unit_noun, drain_token, time_unit_noun.
- ADR-0164.4-phase2-statement-frame-reader.md — the architectural
  rationale and acceptance contract.

Measurement (reader_phase2_delta.json):

  flag-OFF: correct=3 refused=47 wrong=0
  flag-ON:  correct=3 refused=47 wrong=0
  delta:    0/0/0

Below the brief's floor of correct >= 4. Architecture is sound — the
reader admits cases as graphs when the structure resolves, refuses
cleanly otherwise, preserves wrong=0 across both flag states.

Bottleneck table (from per-case attribution):

  count  refusal_class           dominant cause
  -----  ----------------------  ------------------------------------
  18     incomplete_operation    multi-quantity ops; no-quantity op
  11     unknown_word            "hundred", "presently", "one-hour",
                                 non-math verbs (compound numerics,
                                 lexicon gaps)
  6      unexpected_category     fraction / percentage literals;
                                 multi-subject sentences
  6      unresolved_pronoun      "them", "their", "his" with no
                                 compatible entity
  5      unattached_quantity     quantity never bound to a unit
  1      no_question_target     question parsed but slot never set

Closing the gate to mixed-bounded [4, 24] is Phase 2.1 scope: extend
composition rules for multi-quantity ops, add fraction/percentage
primitives (per ADR-0164.1 amendment), expand lexicon for the
remaining unknown_word cases, extend pronoun resolution.

Invariants preserved

- wrong = 0 in both flag states ✓
- flag-OFF byte-identical to today ✓
- determinism (50/50 identical runs) ✓
- Capability axes G1-G5, S1 unchanged ✓
- Reader tests: 19 (Phase 2) + 18 (Phase 1, post-update) + 53 (pack)
  + 76 (lexicon + primitives) = 166 specific to this change; all pass
- core test --suite smoke -q: 67 passed

Rebase note

This PR was authored against an older base; rebased onto current
main to incorporate #333 (Brief 8.2 universal proper_noun_token
primitive) and #334 (ADR-0166 measurement discipline). The rebase
required:
- Lexicon files renamed proper_noun_entity_* -> proper_noun_gender_*
  (with the Phase 2 additions merged into the gender_* files)
- Compiled lexicon.jsonl unchanged from #333's 207-entry state
  (Phase 2's per-category additions are runtime-visible via the
  source loader, not via the compiled file)
- _classify reconciled with Brief 8.2's sentence-initial dispatch +
  Phase 2's 3-tuple decimal-value return
- All dispatch tables and category checks updated to reference
  proper_noun_token (singular) instead of proper_noun_entity_{f,m}
- Three Phase 1 test expectations updated to reflect Phase 2
  behavior (proper noun at position 0 now opens statement pre-frame
  instead of refusing; pronoun resolution applies per ADR-0164.2)

Per ADR-0166's three-question test, this PR is honest measurement:
capability exists, at least one case admits, lane distinguishes
presence from absence — which the bottleneck table demonstrates.

Refs ADR-0164.3 §Phasing Phase 2, ADR-0164.1 amendment (Brief 8.2),
ADR-0166 §"Mixed (notable but not blocking)" — except here, below
floor.
2026-05-27 05:03:56 -07:00
Shay
33c2b10fbf
docs(handoff): Brief 11 — Phase 2 reader closure + capability snapshot sequencing (#342)
* docs(handoff): Brief 11 reader closure and snapshot plan

* docs(handoff): Brief 11 — Phase 2 reader closure + capability snapshot sequencing
2026-05-27 05:03:40 -07:00
Shay
834d1ae577
docs(ADR-0166): measurement-capability sequencing discipline (#334)
Add the fourth governing principle to the family of structural-
invariant ADRs (alongside ADR-0114a anti-overfitting, ADR-0165 regex
scope rule, CLAUDE.md versor invariant). The rule, stated negatively:
do not author eval lanes ahead of the operators those lanes test, and
do not expand the eval surface ahead of the capability that produces
signal on it.

Three-question test for new eval lanes:

  1. Does the capability this lane probes exist on main today?
  2. Has at least one case admitted end-to-end through that capability?
  3. Will running this lane distinguish capability-presence from
     capability-absence?

A "no" on any defers the lane until the capability lands. Tier 3 TBD
rows are data debt; running existing lanes to populate them is
permitted (snapshot of current capability) but is NOT a substitute for
capability work.

Why now: a strategic-analysis exchange this session proposed authoring
spatial_geometry_ood, historical_sequence_ood, and other new lanes
while GSM8K-math sits at 3/47/0 and the comprehension reader (ADR-0164)
is mid-build. The proposal's "most impactful next commit is to run all
Tier 3 lanes" framing would have generated noise (lanes refusing
uniformly because their underlying operators don't exist) rather than
the diagnostic signal that justifies prioritization. ADR-0166 mechanizes
the constraint that prevents that pattern.

Session log SESSION-2026-05-27-tier3-sequencing.md captures the
narrative: what the analysis got right (geometry-first as strategic
bet, sequencing instincts), what it missed (GSM8K-math treated as
solved; comprehension reader pivot not in context), and the honest
re-sequence (Brief 10 first; Tier 3 snapshot in parallel; cross-domain
transfer after verifying whether the reader IS the requested
structural-pattern recognizer under a different name).

The session also surfaced a mid-flight diagnostic from PR #332: the
actual GSM8K bottleneck is the ADR-0163 recognizer injector emitting
incomplete graphs, which the reader correctly refuses to admit
(wrong=0 by construction via the new guard). Brief 10 (Phase 2 reader)
dominates here because it replaces the inadequate injector surface
entirely.

No code changes. ADRs only.

Refs ADR-0114a, ADR-0165, CLAUDE.md §"Non-Negotiable Field Invariant".
2026-05-26 22:19:40 -07:00
Shay
b3dbde94b4
feat(comprehension/8.2): universal proper_noun_token primitive (#333)
ADR-0164.1 amendment: replace name-whitelist entity admission with a
universal lexeme primitive that recognizes any capitalized token as a
proper noun. The gender-coded name lists are demoted from admission
criterion to enrichment-only lookup. A name outside the curated lists
still admits cleanly with gender="unknown" — ADR-0164.2's pronoun
resolution rules handle the unknown case via single-salient fallback
or refuse with ambiguous_pronoun_referent.

Universal at the primitive layer: the new proper_noun_token primitive
is domain-agnostic. It sits in the shared PRIMITIVE_REGISTRY and is
available to every current and future reader (math, narrative,
code-comment, multi-lingual). The math reader is its first consumer.

Pattern: ^[A-Z][A-Za-z'-]*[a-z][A-Za-z'-]*$
- requires capitalized first letter
- requires ≥1 lowercase letter (rejects all-caps acronyms)
- allows internal apostrophes (O'Brien) and hyphens (Mary-Anne)
- matches "Tina", "Bob", "Marnie", "McDonald" — rejects "TINA",
  "123", "$5.00" (those go to their own primitives)

Sentence-initial lookup-first dispatch (lifecycle._classify):
- At token_index == 0: lookup() first, skipping proper_noun_gender_*
  categories (treated as not-found so the primitive can fire). If
  lookup misses, primitive scan picks up novel names. Inverts the
  question from "is this a name?" to "is this a known common word?"
- At token_index > 0: primitive-first with UNIT_CATEGORY_TOKEN ceding
  to operational lexicon for currency_unit_noun overrides.

Lexicon rename (per-category source files):
- proper_noun_entity_female.jsonl -> proper_noun_gender_female.jsonl
- proper_noun_entity_male.jsonl   -> proper_noun_gender_male.jsonl

Compiled lexicon.jsonl: rename the two semantic_domain tags; drop
"marnie" (was only in proper_noun_entity_female, now absent from
the gender-coded sources). Net: 208 -> 207 entries. New manifest
checksum: 1fb9b0d790258736267d528e8e8a2436ce88b9ce690805fe2813ba077861ba2a

New helper gender_of_proper_noun(surface, lexicon) returns
Literal["female","male","neuter","unknown"] — pure enrichment lookup,
never gates admission.

Measurement (reader_phase1_plus_proper_noun_delta.json):
- pre-primitive baseline: correct=3 refused=47 wrong=0
- post-primitive measurement: correct=3 refused=47 wrong=0
- No regression on wrong=0
- No net admission increase observed in this train-sample harness;
  the architectural value is for future text outside the curated
  gender lists (Sonnet's #332 expanded those to cover GSM8K names).

Tests:
- test_lexeme_primitives.py: registry count 8 -> 9, proper_noun_token
  fires + variants (Bob, Marnie, McDonald, O'Brien, Mary-Anne),
  numeric/all-caps refusals, numeric-literal still wins overlap on "123"
- test_reader_question_frame.py: 5 new tests for sentence-initial
  dispatch + unknown-gender pronoun resolution + novel-name admission
  via primitive (Zelda)
- test_en_core_math_v1_pack.py: category counts updated; mutual-exclusion
  between gender_female and gender_male preserved; total 208 -> 207
- test_lexicon.py: category list + lookup assertion updated to renamed
  proper_noun_gender_female
- test_proper_noun_primitive_universality.py: new test module asserting
  domain-agnostic property of the primitive

Validation:
- pack + lexicon + primitive tests: 147 passed
- reader + universality tests: 22 passed
- smoke lane: 67 passed

Closes the engine_state question by leaving those files untracked
(repo discipline: runtime artifacts never enter PRs).

Refs ADR-0164.1 amendment, ADR-0164.2 §EntityRegistry, ADR-0165
§Legitimate uses (the new primitive passes the three-question test).
2026-05-26 22:16:34 -07:00
Shay
800cf6591e
feat(ADR-0164.P1): reader/regex hybrid coexistence + Phase 1 measurement gate (#331)
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.
2026-05-26 21:14:11 -07:00
Shay
30972e184e
feat(workbench/W-029): proposal queue (#329) 2026-05-26 21:08:36 -07:00
Shay
19506e9f60
feat(workbench/W-031): replay theater (ADR-0160 §Phase 6, deterministic-evidence UX) (#328) 2026-05-26 20:56:31 -07:00
Shay
4ceb37b3b0
feat(comprehension): swap reader stubs for real primitive + lexicon (Brief 8.1) (#330)
Eliminates generate/comprehension/_interface_stubs.py and wires
lifecycle.py to the real modules landed in #324 (lexeme_primitives)
and #325 (lexicon/loader).

Changes:
- lifecycle.py: imports redirected to LexemeMatch/scan and
  Lexicon/LexiconEntry/load_lexicon/lookup; _classify reordered
  so lexicon lookup precedes primitive scan (ADR-0164.1 mass-noun-token
  boundary note); punctuation dispatch inlined as category (d)
- _interface_stubs.py: deleted
- en_core_math_v1 lexicon source files: added question_discrete_qty,
  question_continuous_qty, question_comparative, aggregate_modifier,
  modal_aux, copula_verb, count_unit_noun, time_unit_noun, drain_token;
  supplemental entries for accumulation_verb (+need, +want),
  proper_noun_entity_female (+monica), proper_noun_entity_male (+malcolm);
  total moved from currency_unit_noun to aggregate_modifier
- test_en_core_math_v1_pack.py: updated EXPECTED_CATEGORY_COUNTS for
  ADR-0164-ratified deltas; decoupled EXPECTED_COMPILED_TOTAL (208) from
  per-category sum; provenance check accepts both ported and supplemental tags

Gate: 15/15 reader tests, 137/137 primitive+lexicon+pack tests,
67/67 smoke, 13/13 packs — all green.
2026-05-26 20:48:33 -07:00
Shay
00f5056209
feat(workbench/W-030): eval center (safe lanes only, ADR-0160 §Phase 5) (#327) 2026-05-26 20:39:30 -07:00