Commit graph

386 commits

Author SHA1 Message Date
Shay
29111b7762
feat(ADR-0114a.5): reasoning-isolation perturbation suite — Obligation #5 wired for B3, PASSING 130/130 preserving, 68/68 breaking (#191)
Discharges ADR-0114a Obligation #5 for the B3 bounded-grammar lane.

Closed perturbation taxonomy (5 invariance-preserving, 3 invariance-breaking
transforms) operates on problem text only; parser, solver, and cases.jsonl
are untouched. Both rates are ε=0 per ADR-0120 §"Threshold rationale".

Results on main B3 (35 solved_correct cases):
  invariance_preserving: 130/130 = 1.0000
  invariance_breaking:    68/68  = 1.0000
  obligation_5_passed: True

Skipped transforms documented explicitly (not silently absent):
  commutative_reorder: all 35 — no single-entity multi-unit init state
  op_verb_flip:        15 — multiply/divide/compare/transfer cases
  value_replacement_op: 15 — no distinct numeric operand
  unit_synonym:         7 — rate-declaration $ syntax cases
  value_replacement_init: 7 — value cancels or not found
  entity_rename_v{1,2,3}: 1 each — b3-013 "Birds" collective is self-mapping

Ships:
  core/capability/perturbation_b3.py — generator + scorer + validate_perturbation_suite()
  tests/test_adr_0114a_5_perturbation.py — 15 tests (purity, preserving, breaking, determinism, snapshot, refusal, skip coverage)
  core/cli.py — core capability perturbation [--lane-id] [--json]
  evals/obligation_5_perturbation/B3_bounded_grammar.json — written by CLI
  docs/decisions/ADR-0114a.5-perturbation-suite.md — ADR with taxonomy tables
2026-05-23 16:07:59 -07:00
Shay
272c1e723a feat(ADR-0114a.10): pack-provenance auditor — Obligation #10 wired for B3, PASSING
Implements the external auditor ADR-0114a Obligation #10 requires:
"Every SolutionTrace.steps[*].pack_lemma_id resolves to a real
lexicon entry in the domain's operator pack." The solver enforces
this at solve time; this PR audits it from outside.

New module core/capability/pack_provenance.py:
  - _load_lexicon_lemmas(): independent re-read of pack lexicon
  - _parse_lemma_id(): <pack_id>:<lemma> shape parser
  - validate_lane(): re-runs candidate-graph pipeline on a B-lane's
    cases, walks every solver step, validates pack_lemma_id parses
    AND resolves to a lexicon entry. Per-case + per-lane verdict.
  - emit_provenance_report(): deterministic artifact emission.

CLI: core capability pack-provenance (added to core/cli.py).
Writes evals/obligation_10_pack_provenance/<lane_id>.json.

Empirical verdict on current main (post-PR #186):
  lane:                       B3_bounded_grammar
  cases_total:                50
  cases_validated:            25  (every expected-correct B3 case)
  cases_skipped_unsolved:     25  (refusal-expected probes — by design)
  cases_violated:             0
  obligation_10_passed:       True

5 distinct lemma_ids observed (add, subtract, transfer,
compare_additive, compare_multiplicative) — all resolve to
en_arithmetic_v1. The other 3 op kinds (multiply, divide,
apply_rate) ratify-at-solve-time via _resolve_pack_lemmas so the
obligation holds for them too if a future case exercises them.

Honest scope-limit: B3 only. B1 (symbolic equivalence) and B2
(teaching corpus) equivalents deferred to separate sub-ADRs —
B1 needs reframing (algebra normalization chain, not arithmetic
steps); B2 can use this same auditor signature once corpus
solver-trace exercise is confirmed case-by-case.

Composition with ADR-0131.4: orthogonal. Composite gate verdict
+ obligation #10 verdict + 4 other obligation auditors (when
they land) + reviewer signature → full ADR-0120 wire-up.

Trust boundary: read-only access to pack lexicon + B3 cases;
single deterministic write to artifact path. No dynamic imports,
no shell passthrough, no network. Pure deterministic auditor.

Tests: 19/19 in tests/test_adr_0114a_10_pack_provenance.py
covering lemma-id parser (well-formed + malformed), lexicon loader
(real pack + every failure mode), lane validator (passes on real
B3 + refuses on missing pack/cases + skips refusal-expected cases
without false violation), determinism (report identical across
calls + artifact byte-equal).
2026-05-23 15:44:53 -07:00
Shay
c996e39c98
Merge pull request #188 from AssetOverflow/feat/adr-0131-4-promotion
feat(ADR-0131.4): composite math-expert gate — PASSING on first evaluation (B1+B2+B3 all green, wrong==0)
2026-05-23 15:41:43 -07:00
Shay
d66e8ad625 feat(G1): verb-classes capability axis (ADR-0131.G.1)
Cognitive capability: extend bounded grammar to admit acquisition/action
verbs (buys, bought, collected, saved, saved-up, makes, sells) as
operation-kind entries, and pure-possession verbs (had, started, started-with)
as initial-possession anchors.

What invariant proves correctness:
- wrong == 0 across all G1 curated cases (20/20) and GSM8K probe (0 wrong/50).
- versor_condition and field invariants untouched — no algebra-path changes.
- Round-trip filter (math_roundtrip.roundtrip_admissible) unchanged.

Which CLI suite / eval proves the lane:
  pytest tests/test_adr_0131_G1_verb_classes.py — 15/15 pass
  pytest tests/test_adr_0126_runner_wiring.py — 9/9 pass (3 regressions fixed)
  pytest tests/test_adr_0131_{1,3}_*lane.py — 17/17 pass
  pytest tests/test_adr_0131_G_gsm8k_coverage_probe.py — 8/8 pass
  pytest tests/test_gsm8k_math_runner.py — 11/11 pass

Key architectural change:
  Acquisition verbs that also appear in ADD_VERBS/SUBTRACT_VERBS were
  previously listed in _INITIAL_HAS_RE, causing branch-disagreement refusals
  when a canonical 'has' initial preceded an acquisition sentence for the
  same entity.  Fix: narrow _INITIAL_HAS_RE to pure-possession anchors only
  (has/have/had/started); acquisition verbs remain exclusively in KIND_TO_VERBS.
  The solver's default-from-zero means 'Sam buys 5 apples. How many does
  Sam have?' resolves as 0+5=5 without any initial-possession candidate.
  Optional verb particle (up/down/out/...) added to _op_pattern to handle
  'saved up N', 'picked up N' etc.

No changes to binding graph, solver, verifier, or versor/CGA algebra.
No stochastic generation, approximate recall, or hidden normalization.
Trust boundaries unaffected — no new dynamic imports or user-input paths.
2026-05-23 15:39:14 -07:00
Shay
4b59f3daf7 feat(ADR-0131.4): composite math-expert promotion gate — wired, evaluated, PASSING
Implements ADR-0131's revision of the ADR-0120 expert-promotion
contract for mathematics_logic: replaces the single-benchmark
GSM8K-coverage check with a composite B1+B2+B3 requirement.

New module core/capability/composite_math_gate.py:
  - evaluate_composite_math_gate(): pure function over already-
    committed B-lane reports; handles heterogeneous report shapes
    (B1/B2 counts vs B3 metrics); applies pinned thresholds
    (correct_rate >= 0.95 AND wrong == 0); composes verdicts.
  - Reproducible SHA-256 claim_digest over canonical evidence bundle.
  - GSM8K honest-disclosure (admission/wrong/refused/substrate)
    embedded in artifact but never gates per ADR-0131.

CLI: core capability math-expert-gate (added to core/cli.py).
Writes evals/math_expert_claims/v1/expert_claims_math_v1.json.

Empirical verdict on current main (post-PR #182/#183/#184/#185):
  composite_gate_passed: True
  B1_public:          185/185 wrong=0 rate=1.0000
  B1_sealed:           14/14  wrong=0 rate=1.0000
  B2_teaching_corpus:  40/40  wrong=0 rate=1.0000
  B3_bounded_grammar:  50/50  wrong=0 rate=1.0000
  GSM8K disclosure:    0/50 admission, wrong=0, substrate=candidate_graph

The math expert is gate-passing under ADR-0131's revised composite
contract. The architectural bet ADR-0131 placed has paid off.

Honest scope-limit: this implements only the ADR-0131-specific
revision (composite benchmark portion). The full ADR-0120 10-
obligation contract still requires substrate for 5 missing
obligations (OOD ratio, perturbation, depth curve, adversarial,
operation-provenance-via-pack). Those are sequencing-wise *after*
ADR-0131.4, not bundled. Reviewer signature via ADR-0092 registry
is also reserved.

Trust boundary: read-only access to 5 committed lane reports;
single deterministic write to the artifact path. No dynamic
imports, no recomputation of lane verdicts.

Tests: 12/12 in tests/test_adr_0131_4_composite_math_gate.py
covering threshold pinning, heterogeneous shape handling, gate
logic (passing + every failure mode), GSM8K honest disclosure
(never gates), determinism (claim_digest + artifact byte-equality),
and a snapshot test confirming current main satisfies the gate.

ADR-0131.4 module note: the parent ADR-0131 plan named
formation/ratify.py + formation/promote.py as the wire-up site —
that was a misidentification (those govern teaching-example
SPECULATIVE→COHERENT bridging per ADR-0021, not domain-tier
promotion). Correct site is core/capability/, where audit-passed
gate already lives.
2026-05-23 15:23:14 -07:00
Shay
5853b189b2 feat(ADR-0131.G.3.1): numerics extensions — fractions + multi-currency + multi-token cardinals + word-num-adjective
Four axes deferred from ADR-0131.G.3 (PR #183):

1. Fractions end-to-end: new _INITIAL_FRACTION_OF_RE extractor handles
   `N/M of [a/an] <unit>` shape; _resolve_value already handles N/M arithmetic.

2. Multi-currency: _MONEY_SYMBOL widened to six symbols; _CURRENCY_SYMBOLS table
   + _resolve_currency dispatcher; ¢/€/¥/₱ wired end-to-end. £/pound sterling
   deferred to G.3.2 (question extractor's single-token unit slot cannot parse
   two-word surface "pounds sterling").

3. Multi-token cardinals: dedicated _MULTI_WORD_CARDINAL_RE extractor (approach a)
   delegates to parse_compound_cardinal; avoids greedy unit-slot boundary ambiguity
   from widening _VALUE.

4. Word-num-adjective: optional adjective group added to _INITIAL_HAS_RE and
   _MULTI_WORD_CARDINAL_RE; closed adjective list identical to _CONJ_OBJECT_RE.

Also fixes six pre-existing G4 type bugs where _resolve_value() result was used
directly as a numeric operand (TypeError: _ResolvedValue is not a number).

Axis lane v1_1: 20/20 solved_correct, 0 wrong, 8/8 refusals, overall_pass=True.
GSM8K probe: 0/50 admission_rate unchanged, admitted_wrong=0 (safety rail intact).
42/42 new tests pass; parent v1 lane (26/26) unaffected.
2026-05-23 15:16:46 -07:00
Shay
8187f3f385
Merge pull request #185 from AssetOverflow/feat/adr-0131-g4-multi-clause
feat(ADR-0131.G.4): multi-clause composition — admission 0/50 (Δ0), multi-clause refusals 2→1
2026-05-23 14:50:15 -07:00
Shay
34e9546e16
Merge pull request #183 from AssetOverflow/feat/adr-0131-g3-numerics
feat(ADR-0131.G.3): numeric literals (money + hyphenated cardinals) — axis lane 20/20, wrong==0
2026-05-23 14:49:42 -07:00
Shay
f55dc36e6f
Merge pull request #182 from AssetOverflow/feat/adr-0131-g2-comparatives
feat(ADR-0131.G.2): comparative operations (additive + multiplicative) — admission 0/50 (Δ0), comparative-clause refusals 2→1
2026-05-23 14:48:35 -07:00
Shay
e2227d7552
Merge pull request #175 from AssetOverflow/chore/main-test-rot
chore(tests): reconcile pre-existing main rot — 58 failures → 0
2026-05-23 14:48:06 -07:00
Shay
de26d7f792 feat(ADR-0131.G.4): multi-clause composition (conj subjects + conj objects + embedded quantifiers + conj embedded) — admission 0/50 (Δ0), multi-clause refusals 2→1
Highest-risk axis of the ADR-0131.G capability iteration: within-
sentence multi-clause composition. Four extractors land in the
candidate-emitting parser; no graph-side or solver changes.

Parser extension (generate/math_candidate_parser.py)
- _conj_subject_each_candidates: '<A> and [his/her/their <kin>] <B>
  each <verb> <N> <unit>' → 2 CandidateInitial (one per actor).
- _conj_object_candidates: '<E> has <N1> <unit1> and <N2> <unit2>' →
  2 CandidateInitial for the same entity; same-unit conjuncts refuse
  (would silently collide under solver overwrite-on-collision).
- _embedded_quantifier_candidates: '<E> has <N> <container> with <M>
  <unit> in each [<container>]' → 1 derived CandidateInitial
  (value=N*M).
- _embedded_quantifier_candidates (conj branch): '... <N1> <C> with
  <M1> <U> in each ... and <N2> <C> with <M2> <U> in each ...' → 1
  SUM CandidateInitial (value=N1*M1+N2*M2); mixed-unit refuses.
- CandidateInitial anchor whitelist widened to include
  saved/earned/got/received/bought/made/paid (and inflections) —
  narrow widening needed for the conjoined-subject-each shape.

Closed-set discipline
- Distributive 'each' only — 'each ... together/altogether' refuses.
- Two-way conjunction only — 3-way refuses by non-match.
- Cross-sentence coreference stays refused (within-sentence axis).
- Ambiguous 'each' scope refuses (container2 must agree).

Curated axis lane (32 cases)
- evals/math_capability_axes/G4_multi_clause/v1/cases.jsonl:
  conj_subject_each ×6, conj_object ×6, embedded_quantifier ×6,
  conj_embedded ×6, refusal ×8.
- evals/math_capability_axes/G4_multi_clause/v1/runner.py +
  report.json: deterministic; wrong==0 gate; byte-equal across runs.

Tests (26 new)
- tests/test_adr_0131_G4_multi_clause.py: per-shape emission,
  refusal probes (parametric), distributive-only policy,
  cross-sentence refusal, runner byte-equality, GSM8K-probe gate.

GSM8K-probe gate (chosen: multi-clause refusals ↓)
- evals/gsm8k_math/train_sample/v1/report.json (candidate-graph
  probe): multi-clause statement-refusal count 2 → 1. Case 0042
  ('Ella has 4 bags with 20 apples in each bag and six bags with 25
  apples in each bag.') moves from statement-clause refusal to
  question-layer refusal. Case 0026 ('Aaron and his brother Carson
  each saved up $40') stays refused on the '$' value slot
  (deferred to G.3 numeric-literals axis).
- evals/gsm8k_math/train_sample/v1/train_sample_coverage_report.json
  (legacy probe): refreshed, byte-identical (legacy parser
  untouched).

B3 + candidate-graph + GSM8K probe lanes all pass (95/95
regression). wrong==0 preserved everywhere — load-bearing for the
highest-risk axis.
2026-05-23 14:43:16 -07:00
Shay
3011fce268 feat(ADR-0131.G.3): numeric literals — money + hyphenated cardinals (axis lane 20/20, wrong==0)
First capability-axis iteration after ADR-0131.G baseline. Extends the
candidate-graph parser's <value> slot to recognize:

  - Money symbol literals: $N and $N.NN (1-2 decimals); $N.NNN refused
  - Money word forms: N dollars / N cents
  - Hyphenated multi-word cardinals: twenty-five, ninety-nine, ...

All money values normalize to integer cents, unit 'cents' — pack-aligned
with en_units_v1's canonical_unit='cent' for the money dimension.
en_numerics_v1's parse_compound_cardinal handles hyphenated cardinals.

Parser changes (generate/):
  - math_candidate_parser.py: _VALUE alternation widened; _resolve_value
    refactored to return _ResolvedValue|None carrying optional unit
    override; _INITIAL_HAS_RE unit slot made optional; dollar/dollars →
    cents normalization at candidate build.
  - math_roundtrip.py: new _unit_grounds helper (money-aware); _value_grounds
    widened for the three new literal shapes; roundtrip_admissible uses
    _unit_grounds for the unit check.
  - math_candidate_graph.py: _initial_admissible and _question_admissible
    use _unit_grounds.

New axis lane (evals/math_capability_axes/G3_numerics/v1/):
  - 26 curated cases (20 positive across 4 classes + 6 refusal probes)
  - runner.py wraps _score_one_candidate_graph; byte-equal report.json
  - 20/20 positive solved correct; 6/6 refusal probes refused typed;
    solved_wrong == 0; overall_pass == True

Tests: 27/27 in 0.19s. 420 existing candidate-parser/math-parser/pack
tests still green. GSM8K probe safety rail (admitted_wrong == 0)
preserved.

Honest scope-limit (documented in ADR): admission_rate on the GSM8K
probe stays at 0/50 because (a) the probe currently consults the legacy
parser path, not the candidate-graph pipeline G.3 extends, and (b) most
money-bearing GSM8K cases fail first on verb (G.1) or multi-clause (G.4)
shape, not on the money literal. The axis lane is the load-bearing
measurement for this iteration. Reserved follow-up: a small probe-
infra ADR to switch run_coverage_probe.py to the candidate-graph
pipeline.

Out of scope, deferred to G.3.1: fractions end-to-end (resolver supports
N/M but no axis cases), multi-currency (¢ € £ ¥ ₱), space-separated
multi-word cardinals (one hundred), word-number-adjective compositions
(five full boxes).
2026-05-23 14:23:05 -07:00
Shay
b891eb243c feat(ADR-0131.G.2): comparative operations (additive + multiplicative) — admission unchanged, comparative-clause refusals 2→1
Wire compare_additive / compare_multiplicative extractors into the
candidate-emitting sentence parser, closing the deferred phase flagged
at generate/math_candidate_parser.py:30.

Capability axis: comparatives (additive + multiplicative)
- generate/math_candidate_parser.py: new _compare_additive_candidates,
  _compare_multiplicative_candidates, _compare_nested_candidates
  emitting CandidateOperation records keyed to the four
  Comparison.direction literals registered in ADR-0123.
- Closed-set anchor alternation; 'less' admitted as surface synonym of
  'fewer'; reference slot widened to admit "the number/amount of <unit>"
  for nested forms.
- Nested 'A has N more <unit> than M times <REF>' emits two flat
  candidates (additive + multiplicative); binding-graph picks the
  admissible composition or refuses (no solver stub).

Curated axis lane (24 cases)
- evals/math_capability_axes/G2_comparatives/v1/cases.jsonl:
  8 additive / 8 multiplicative / 3 nested / 5 refusal
- evals/math_capability_axes/G2_comparatives/v1/runner.py +
  report.json: deterministic, wrong==0 gate, byte-equal across runs.

Tests (21 new)
- tests/test_adr_0131_G2_comparatives.py: per-direction at-least-one
  passing, nested-both-emitted, closed-set refusal, runner
  byte-equality, GSM8K-probe gate (comparative-clause refusals
  strictly decrease).

GSM8K-probe gate (chosen: comparative-clause refusals ↓)
- evals/gsm8k_math/train_sample/v1/report.json (candidate-graph
  probe): comparative-clause refusal count 2 → 1 (case 0009 'Jen has
  10 more ducks than four times the number of chickens' moves from
  statement-clause refusal to question-layer refusal). admitted_wrong
  remains 0; admission_rate unchanged (downstream composition is a
  follow-up ADR).
- evals/gsm8k_math/train_sample/v1/train_sample_coverage_report.json
  (legacy probe): refreshed, byte-identical (legacy parser untouched).

B3 + candidate-graph + GSM8K probe lanes all pass (90/90). Direction
vocab stays closed to {more, fewer, times, fraction}; wrong==0
preserved everywhere.
2026-05-23 14:15:25 -07:00
Shay
23c126ebe0 feat(ADR-0131.G): GSM8K coverage probe — honest baseline + capability-first iteration discipline
ADR-0131 deferred GSM8K because it rewards paraphrase flexibility,
which is the deterministic engine's structural weakness. This ADR
re-engages it on architecture-aligned terms: as a *coverage probe*
of the bounded grammar + binding graph, not a promotion gate.

The framing pinned by this ADR:

  GSM8K is not a target. The model's capability is the target.
  GSM8K passing is the symptom of capability, not the goal of
  the work.

Wrong mindset (rejected by ADR's iteration discipline):
  "Find templates that admit more GSM8K cases."

Right mindset (load-bearing):
  "Extend the model's NL-to-typed-graph capability along
  principled axes (verb classes, comparative structures, numeric
  forms, multi-clause grammar). GSM8K admission rises as a
  side effect alongside every other word-problem corpus."

Baseline pinned by this commit:

  admission_rate: 0/50 = 0.0%
  admitted_wrong: 0  (gate intact, safety rail bulletproof)
  refused:        50/50 = 100.0%

Every refusal is a typed parser error citing the specific clause
that did not match a template. Zero crashes, zero confabulations
— refusal-first works perfectly at admission rate zero.

What's in this PR:

- ``docs/decisions/ADR-0131.G-gsm8k-coverage-probe.md``: the ADR.
  Cites parents (ADR-0131, -0115/-0116/-0117, -0131.3, -0132..-0135).
  Documents the capability-first iteration discipline that every
  subsequent ADR-0131.G.<n> must follow:
    1. Name a single capability axis the iteration extends
    2. Add B3-style curated coverage cases (capability proves
       itself OUTSIDE GSM8K)
    3. Re-run both B3 lane + GSM8K probe; B3 must not regress
    4. Reject any expansion that only moves GSM8K admission

- ``evals/gsm8k_math/train_sample/v1/run_coverage_probe.py``:
  pure-adapter wrapper around the existing run_lane. Emits a
  deterministic train_sample_coverage_report.json with metrics,
  per-case outcomes, and the top refused-reason families (the
  work queue for capability extension).

- ``evals/gsm8k_math/train_sample/v1/train_sample_coverage_report.json``:
  the baseline report. Diff-able artifact every future iteration
  moves.

- ``tests/test_adr_0131_G_gsm8k_coverage_probe.py``: 8 contract
  tests pinning the safety rail (admitted_wrong == 0), typed
  refusal invariant (every refused case has non-empty reason),
  closed outcome vocabulary, deterministic replay, committed-
  report matches fresh-run.

The promotion-gate composite (B1 + B2 + B3) is unaffected.
ADR-0131.4 still consumes those three. The GSM8K probe is
empirical context for honest external claims, not a gate.
2026-05-23 13:17:04 -07:00
Shay
24f6a596fe
feat(ADR-0131.1.F): frontier-baseline comparison harness for B1 (#178)
* feat(ADR-0131.1.F): frontier-baseline comparison harness for B1

Adapts the ADR-0119.4 methodology (frozen citations + comparison JSON
with disclaimer) to B1, with three additions for the
architecture-aligned claim:

1. A provider-agnostic live head-to-head runner. Adapters for
   Anthropic / OpenAI / Google import their SDKs lazily so the
   package loads cleanly without them installed. Each provider has a
   documented FRONTIER_<VENDOR>_KEY env var; the runner refuses with
   a typed FrontierRunError when keys are absent and the cache cannot
   cover all cases. Every response is cached one-record-per-line at
   responses/<provider>/<model>.jsonl so subsequent runs replay
   byte-equally without re-calling the API.

2. A conservative free-text-to-closed-vocab verdict parser. Ambiguous
   or sentinel-free provider replies collapse to "refused" — a
   polarized verdict is never confabulated from prose. Chain-of-
   thought replies use last-token-wins (provider deliberates, then
   concludes). This is the load-bearing seam that prevents the
   runner from manufacturing scores the provider didn't deliver.

3. Architecture-aligned comparison metrics. accuracy is reported but
   foregrounded as the least-load-bearing; refusal_correctness
   (CORE 100% by lane-gate construction vs. frontier confabulation
   rate) and determinism (CORE byte-equal vs. frontier variance) are
   the differentiators.

Frozen adjacent-benchmark citations cover Anthropic
(claude-3-5-sonnet on MATH, claude-opus-4-1 on AIME), OpenAI
(gpt-4o on MATH), and Google (gemini-1.5-pro on MATH). The scope
disclaimer documents that these are adjacent, not head-to-head.
Head-to-head numbers, when run, land in the cache; the comparison
JSON joins them with CORE's existing lane result.

22 tests pin the methodology: citation shape (every field, https
URL, YYYY-MM-DD date), provider-registry shape, verdict-parser
conservatism (multiple chain-of-thought cases), runner caching
behavior (no double-invoke), comparison-JSON determinism (byte-equal
across runs).

No live API call at test time. The harness gates real runs behind
explicit env vars + CLI invocation.

Composes with ADR-0131.1 (B1 v1), ADR-0131.1.B (v1.B hardening,
#169), ADR-0131.1.S (sealed holdout, #173).

* feat(ADR-0131.1.F): live head-to-head — anthropic/claude-sonnet-4-6

First real frontier baseline on the full B1.B 185-case set
(curated + generated). Cached one-record-per-line at
responses/anthropic/claude-sonnet-4-6.jsonl. Re-runs replay from
disk; no further API calls.

Headline (after scoring fix):

  CORE                            185/185 = 100.0% accuracy
                                  3/3     = 100.0% refusal_correctness
                                  deterministic (byte-equal across runs)

  anthropic/claude-sonnet-4-6     182/185 = 98.4%  accuracy
                                  1/3     = 33.3%  refusal_correctness
                                  non-deterministic (temperature=0, but
                                  not byte-equal architecturally)

The 1.6pp accuracy gap is informative; the refusal-correctness gap
is the architecture-aligned story. Sonnet's three misses:

  sym-eq-v1-0016 [difference_of_squares]
    (x^2 + 1)*(x^2 - 1) vs x^4 - 1
    Sonnet: NOT_EQUIVALENT (math error on a textbook identity)

  sym-eq-gen-v1-0153 [generated_refusal_function]
    sin(x) vs x
    Sonnet: NOT_EQUIVALENT (confabulated — should refuse,
                            transcendental outside polynomial scope)

  sym-eq-gen-v1-0154 [generated_refusal_negative_exponent]
    x^-1 vs 1
    Sonnet: NOT_EQUIVALENT (confabulated — should refuse,
                            negative exponent outside scope)

Sonnet correctly refused only on syntactically malformed input
("x +"); on syntactically-valid-but-semantically-out-of-scope inputs
it confidently polarized rather than refusing. CORE refuses both
classes with typed reasons.

Scoring fix: comparison.py now composes curated + generated cases
(mirroring runner.py) so the head-to-head scores the full 185-case
lane, not just the 30 curated. The initial run scored only 30/185
because the generated set was not loaded into _load_cases().

22/22 frontier-methodology tests still pass.

* feat(ADR-0131.1.F): three more head-to-head runs + Ollama adapter

Three additional providers ran against the full B1.B 185-case set,
joining the prior claude-sonnet-4-6 result:

  CORE                           185/185 = 100.0% acc | 3/3 = 100%  refusal | 33 ms
  claude-sonnet-4-6              182/185 =  98.4% acc | 1/3 = 33.3% refusal | 294 s
  claude-opus-4-7                178/185 =  96.2% acc | 1/3 = 33.3% refusal | 309 s
  gpt-5                          134/185 =  72.4% acc | 1/3 = 33.3% refusal | 1153 s
  qwen3:8b (M1 local, partial)    91/91  = 100.0% acc | n/a  no refusal-class | killed

CORE is the only system at 100% on both axes, and runs ~9,000×
faster than the cheapest cloud frontier, ~35,000× faster than gpt-5,
and finishes in less wall time than a single API call to any of the
three frontier models.

Three distinct frontier brittleness modes, all rooted in
"not actually canonicalizing":

  - sonnet-4-6 confabulates polarized verdicts on out-of-scope
    inputs (sin(x), x^-1). Misses one in-scope difference-of-squares
    identity (x^2+1)*(x^2-1) vs x^4-1.
  - opus-4-7 pattern-shortcuts five near-miss-constant cases —
    accepts (-x+3)*(4x+1) == -4x^2+11x+4 (correct constant is 3,
    not 4) without expanding. Same two out-of-scope confabulations
    as sonnet.
  - gpt-5 over-refuses 50 in-scope cases — literally replies
    "REFUSED" to x*(x+1) == x^2+x and (x+1)*(x-1) == x^2-1. Same
    two out-of-scope confabulations as sonnet/opus.

The qwen3:8b partial is the surprise: on the 91 in-scope cases it
completed (spanning the categories where the frontier models failed),
it scored 100%. Refusal-class cases weren't reached before the run
was killed for being impractically slow (~22s/case on M1).

Changes in this commit:

  - frontier_runner.py: anthropic adapter now omits ``temperature``
    for claude-opus-4-x (the parameter is rejected by 4.x models);
    openai adapter switches to ``max_completion_tokens`` for the
    gpt-5 / o-series reasoning models; new ``_ollama_invoke`` that
    posts to localhost:11434 with no third-party dep; per-case
    ``latency_ms`` is now captured on every NEW cached response
    (future runs only — these four runs pre-date the patch).
  - comparison.py: ``_load_cases`` composes curated + generated
    (185 cases) instead of curated only; ``_score_provider``
    surfaces ``latency_summary`` when records carry latency_ms.
  - tests: provider-registry test relaxed to "cloud trio is a
    subset of PROVIDERS"; env-key test allows ``_KEY`` (cloud
    secret) or ``_URL`` (local endpoint).
2026-05-23 12:14:06 -07:00
Shay
22deaf02df
feat(ADR-0131.2.B): B2 teaching-corpus enrichment — load-bearing gate (#177) 2026-05-23 11:29:48 -07:00
Shay
eb5fb33252
feat(ADR-0131.3): bounded-grammar word-problem benchmark — lane PASSED 50/50 (#180) 2026-05-23 11:27:04 -07:00
Shay
3b30eb248a
feat(binding-graph): Phase 4 question-target binding (ADR-0135) (#179)
Refines BoundUnknown from "the symbol whose value the solver determines"
to "the symbol at a specific temporal/state index with a specific
question-form". Two new required fields on BoundUnknown — state_index
(initial/terminal/Operation(operation_index)) and question_form
(count/rate/total/difference/ratio/identity) — populated by the new
pure-function resolver in generate/binding_graph/question_target.py.

The adapter (ADR-0133) now delegates Unknown -> BoundUnknown construction
to bound_unknown_from_math_problem_graph. No runtime wiring, no solver
invocation. Phase 5 (bounded-grammar / B3 integration) remains deferred.

Refusal-first via the new QuestionTargetError (sibling of AdapterError /
AdmissibilityError). Closed reason vocab: not_a_math_problem_graph,
unknown_entity_not_in_entities, apply_rate_unit_mismatch,
unmappable_question_form. Closed precedence rule on question_form
documented in ADR-0135 (compare_multiplicative > compare_additive >
apply_rate{numerator|denominator unit-match} > count); ambiguity refuses.

SemanticSymbolicBindingGraph.__post_init__ gains a cross-collection
guard: Operation(operation_index) must satisfy operation_index <
len(equations). canonical_string emission widened to include
state=... form=... tokens (hash differs from Phase 3 main by design —
not a regression; byte-equal across runs preserved).

Parents: ADR-0132 / ADR-0133 / ADR-0134.

Tests: +70 new (45 unit in test_binding_graph_question_target.py +
25 integration in test_binding_graph_adapter_question_target.py); 5
Phase 1+3 BoundUnknown fixtures migrated. Total binding-graph lane
295/1 pass (1 pre-existing test_symbol_binding_uses_slots failure on
Python 3.14, unrelated to Phase 4 — exists on origin/main). Pyright
clean on new and modified files. No edits to algebra/, chat/, core/,
or runtime hot path. Field invariant untouched.
2026-05-23 11:24:49 -07:00
Shay
6cbaa74076
feat(binding-graph): Phase 3 unit-aware admissibility (ADR-0134) (#176)
Wires deterministic, refusal-first dimensional analysis into the
binding-graph adapter. Every BoundEquation emitted by
bind_math_problem_graph now carries either admissibility_status='admitted'
+ populated unit_proof or admissibility_status='refused' + typed
refusal_reason. No silent coercion; no invented units; no solver.

Adds:
- generate/binding_graph/units.py — pure unit algebra over a 6-dim
  integer exponent vector (length, time, mass, money, count,
  temperature). Closed vocabulary loaded once from en_units_v1
  (ADR-0127) and memoized; composite "<num>_per_<denom>" resolved
  recursively; conservative depluralization; refusal-first.
- generate/binding_graph/admissibility.py — check_admissibility with
  per-operation-kind dispatch over the closed 8-string vocab, typed
  AdmissibilityError (closed reason set), frozen UnitProof.
- ADR-0134 documenting the contract, invariants, and Phase 4-5
  deferrals.

Adapter changes are surgical: synthesizes operand-literal symbols where
the verifier needs them (op<NNN>__multiplicand / __divisor / __rate),
then stamps each equation via check_admissibility. Input/output types
unchanged; bind_math_problem_graph still byte-equal across runs.

Tests: 226 total in the binding-graph lane (110 Phase 1+2 still pass; 47
units + 40 admissibility + 29 adapter-units new). Pyright clean on all
new files. No runtime wiring outside generate/binding_graph/.

Phase 4 (question-target binding) and Phase 5 (B3 / bounded grammar)
remain deferred per the brief.
2026-05-23 11:07:05 -07:00
Shay
5da8988a63 chore(tests): reconcile pre-existing main rot — 58 failures → 0
Tests on main had drifted from intentional substrate changes that
weren't propagated to their fixtures or pinned values. Categories:

1. PackMutationProposal missing source= arg (3 tests across
   test_mutation_proposal_type, test_provenance, test_expert_demo_runnable):
   add ProposalSource(kind="operator", source_id="", emitted_at_revision="test")
   to the shared fixture. test_expert_demo_runnable also retargets the
   "unpromoted domain" example from systems_software (now promoted) to
   arithmetic (real but unpromoted).

2. Pack content grew (test_en_core_meta_v1_pack 73→77 entries, 49→53 verbs;
   test_en_core_spatial_v1_pack 24→25 entries adding "places" plural surface):
   bump expected counts; allow new provenance shapes from the
   adr-0085-style-v2 review (including the seed:core_meta/seed:core_spatial
   author-time typos on two entries each — documented inline rather than
   masked).

3. Registry self-documenting "add names to the set" failures
   (test_lane_sha_verifier: add curriculum_loop_closure;
   test_register_runtime_threading: add gloss_aware_cause_surface,
   pack_grounded_unknown_surface, teaching_grounded_surface_transitive).

4. Gloss content was seeded where tests pinned None
   (test_pack_resolver_glosses TestMissingGlossesIsBackCompat): switch
   the no-glosses pack from en_core_relations_v1 (since glossed) to
   en_minimal_v1 (still gloss-free); narrow resolve_gloss probe to that
   pack so other packs' glosses can't shadow.

5. Entry-id renumber from cognition-pack expansion
   (test_language_pack_cache): en-core-cog-085 → en-core-cog-091.

6. Holdout tests fail without CORE_HOLDOUT_KEY or local plaintext
   (test_eval_holdout_split + test_transitive_surface): add
   _requires_holdout skip-marker mirroring _decrypt_holdout's contract;
   gate the transitive_surface holdout iteration on the same check.

7. Byte-identity surface guards regressed after the gloss-aware
   composer landed (test_realizer_guard_holdout, test_prompt_diversity_runner,
   test_register_substantive_consumption): re-pin to current surfaces
   ("Light is a visible medium that reveals truth." replaces "Light is a
   source of revelation that makes things knowable.", etc.). The guard's
   regression-catching role is preserved by pinning current output going
   forward; the new gloss-driven phrasings are visibly more grounded.

Touched 14 test files: 176 passed, 4 skipped (holdout-gated), 0 failed
on a targeted re-run. No production code touched.
2026-05-23 11:04:55 -07:00
Shay
169cec710e
feat(ADR-0131.1.B): harden symbolic equivalence lane with generated corpus + exact algebra (#169)
* feat(evals): add deterministic symbolic equivalence generated corpus

* feat(evals): add symbolic equivalence replay helpers

* feat(evals): load generated symbolic equivalence corpus

* feat(evals): emit symbolic equivalence replay manifest

* feat(symbolic): support multivariable integer polynomials

* feat(symbolic): support exact rational polynomial coefficients

* feat(symbolic): align equivalence API with multivariable normalization

* test(ADR-0131.1.B): reconcile v1 expectations to v1.B scope expansion

The v1.B refactor (univariate int → sparse multivariable Fraction) deliberately
admits multivariable polynomials and constant-denominator division. The v1
dataset and tests pinned the old refusal behavior, so the lane runner reported
wrong=4 and 10 unit tests failed.

Reconcile:

- cases.jsonl: flip sym-eq-v1-0029 ('x+y' vs 'x+1') and sym-eq-v1-0030
  ('x/2' vs 'x') from expected=refused to expected=not_equivalent; rename
  categories to multivariable_distinct / constant_denominator_distinct;
  extend provenance with adr-0131.1b:scope-expanded.
- generated_cases.py: split _refusal_cases into scope_expanded (admits)
  and templates (still refused); the first two adversarial cases move to
  the scope-expanded list with expected=not_equivalent.
- test_math_symbolic_normalizer.py: replace test_undefined_variable and
  test_unknown_operator_division with positive scope-expansion tests +
  symbolic-denominator refusal; rewrite TestPolynomialInvariants for the
  new terms/variables constructor (Polynomial(terms={...}, variables=(...)))
  with float-rejection and zero-coef-collapse invariants.
- test_math_symbolic_equivalence.py: TestRefused.test_empty_left reason
  string matches new normalizer error; flip multivariable + constant-
  denominator cases to NOT_EQUIVALENT; add symbolic-denominator-refused
  case; relax canonical_a assertion in test_a_normalizes_b_refuses (engine
  now zeroes both on either-side refusal).
- report.json + manifest.json: regenerated; lane PASS 185/185 wrong=0.

Lane invariants reaffirmed by the new tests: wrong==0, refusal-first for
truly out-of-scope inputs (symbolic denominator, transcendental, malformed,
negative exponent), determinism via byte-equal report.
2026-05-23 10:47:57 -07:00
Shay
5b668cc866
feat(binding-graph): Phase 2 adapter from MathProblemGraph (ADR-0133) (#174)
Pure-function adapter `bind_math_problem_graph(g) ->
SemanticSymbolicBindingGraph` translating ADR-0115 `MathProblemGraph`
into the ADR-0132 binding-graph data model. Structural translation
only — no I/O, no parser/solver calls, no algebra, no numpy, no
runtime wiring.

Mapping discipline locked as module-level constants:
- each entity      -> SymbolBinding(semantic_role="entity")
- each possession  -> SymbolBinding(quantity) + BoundFact
- each Operation   -> fresh result SymbolBinding + BoundEquation
                      (operation_kind verbatim passthrough on the
                       shared closed vocab)
- Unknown          -> synthesized SymbolBinding(unknown) + BoundUnknown

Refusal-first: `g: object` boundary accepts any caller input and
refuses non-MathProblemGraph with typed AdapterError (sibling of
BindingGraphError). Cross-collection invariant failures (defensive,
should be unreachable) are re-raised as AdapterError so callers see a
single refusal type.

Phase 2 placeholders (closed in Phase 3+):
- BoundEquation.unit_proof = "deferred_to_phase_3"
- BoundEquation.admissibility_status = "pending"

Phase 3 (ADR-0134 unit-aware admissibility), Phase 4 (question-target
binding refinement), and Phase 5 (bounded-grammar / B3 integration)
explicitly deferred — see ADR.

Evidence:
- generate/binding_graph/adapter.py (pure functions)
- generate/binding_graph/__init__.py (public surface)
- tests/test_binding_graph_adapter.py — 41 tests (refusal-first, all
  8 VALID_OPERATION_KINDS round-trip, dep wiring, introduction order,
  hash-stability, frozen output, input immutability, placeholder
  constants, cross-collection invariants)
- docs/decisions/ADR-0133-binding-graph-adapter.md

Lane: tests/test_binding_graph_model.py + tests/test_binding_graph_adapter.py
      -> 110 passed, 0 failed. pyright clean on new files. Runtime
      byte-identical to main (no runtime integration yet, by design).
2026-05-23 10:45:15 -07:00
Shay
ed759d1b43
feat(ADR-0131.2): teaching-corpus math eval — lane PASSED 30/30 (#172) 2026-05-23 10:44:25 -07:00
Shay
ca3b6011d4
feat(ADR-0131.1.S): sealed holdout for symbolic equivalence v1 (#173) 2026-05-23 10:44:23 -07:00
Shay
980213ed62
feat(binding-graph): Phase 1 data model (ADR-0132) (#171)
Frozen dataclasses + deterministic allocator + invariants for the
Semantic-Symbolic Binding Graph proposed in PR #170. Pure data layer:
no parser, no solver, no adapter, no runtime wiring. Phases 2-5
deferred to follow-up PRs.

- generate/binding_graph/model.py: SourceSpanLink, SymbolBinding,
  BoundFact, BoundEquation, BoundUnknown, BoundConstraint, and the
  top-level SemanticSymbolicBindingGraph container. All
  @dataclass(frozen=True, slots=True). Refusal-first construction via
  typed BindingGraphError. Cross-collection referential integrity
  enforced at __post_init__.
- generate/binding_graph/allocation.py: pure deterministic
  allocate_symbols() — same input order yields byte-equal output.
- generate/binding_graph/__init__.py: public API surface.
- tests/test_binding_graph_model.py: 69 tests covering frozen
  invariants, slots enforcement, refusal paths, allocation
  determinism, canonical-string round-trip, cross-collection
  integrity.
- docs/decisions/ADR-0132-binding-graph-data-model.md: ratifies
  Phase 1 only; explicit Phase 2-5 deferred section citing #170.
2026-05-23 10:29:59 -07:00
Shay
a76834cd3f
feat(ADR-0131.1): symbolic equivalence benchmark v1 + lane PASSED (#167)
ADR-0131 Benchmark 1 substrate — the primary discriminator for the
mathematics_logic expert promotion under the architecture-aligned
benchmark composite proposed in ADR-0131.

WHAT LANDED:

generate/math_symbolic_normalizer.py
  Deterministic univariate polynomial normalizer. Scope: single
  variable, integer coefficients, +/-/*/** operators, parens, no
  division, no transcendentals. Pipeline: tokenize -> recursive-
  descent parse -> expand-and-collect -> canonical string. Refusal
  is first-class via SymbolicError; out-of-scope inputs refuse
  rather than guess (preserves wrong == 0).

generate/math_symbolic_equivalence.py
  check_equivalence(a, b) -> EquivalenceVerdict
  Returns EQUIVALENT / NOT_EQUIVALENT / REFUSED with canonical
  strings + reason. Compares byte-equal canonical forms.

evals/math_symbolic_equivalence/v1/
  cases.jsonl   — 30 hand-curated cases across 18 algebraic
                  identity categories + 2 out-of-scope refusals.
                  Coverage: commutative, distributive, square +
                  cube of binomial, difference of squares, FOIL,
                  collect like terms, zero cancellation, factoring,
                  exponent combination, unary negation.
  runner.py     — CLI entry point. Loads cases, builds report,
                  writes JSON, exits 0/1 on gate pass/fail.
  README.md     — methodology, scope, dataset categorization,
                  exit criterion, baseline result.

tests/
  test_math_symbolic_normalizer.py     — 44 tests covering parser,
                                          algebra primitives,
                                          canonical-form invariants,
                                          and every refusal path.
  test_math_symbolic_equivalence.py    — 16 tests on the public
                                          check_equivalence API.
  test_adr_0131_1_symbolic_equivalence_lane.py
                                       — 8 tests gating the lane:
                                          dataset integrity, exit
                                          criterion, wrong == 0,
                                          determinism (byte-equal
                                          report across runs).

EMPIRICAL RESULT (the lane PASSED):

  correct       = 30 / 30   (100.0%)
  wrong         =  0 / 30   (wrong == 0 invariant satisfied)
  refused       =  0 / 30   (refusals all matched expected)
  correct_rate  = 1.00
  exit_criterion: PASSED  (>= 0.95 required)

CONTRAST WITH ADR-0127-0128 GSM8K TRAIN-SAMPLE RESULT (0/0/50):
  This is the first benchmark on the mathematics_logic lane where
  the architecture's structural strengths fully express. The result
  is the empirical inverse of the GSM8K result — and that's
  exactly the architecture-benchmark fit ADR-0131 was written to
  re-target toward.

REGRESSION: 1033/1033 existing tests green across math + ADR-0126
+ pack ratification + runner. Zero regressions.

SCOPE DISCIPLINE (per ADR-0131.1 v1 plan):
  v1 deliberately narrow (univariate, integer, polynomial). Future
  ADR-0131.1.B expansions documented in README: multi-variable,
  rationals, larger dataset (~500), sealed holdout per ADR-0119.7
  pattern.

PARALLEL WORK (per ADR-0131 plan to run all 3 sub-phases concurrently):
  - ADR-0131.2: CORE-native teaching-corpus eval (separate PR)
  - ADR-0131.3: bounded-grammar word-problem set (separate PR)

  These are independent of ADR-0131.1; no shared files, no
  cross-PR coordination required beyond final composite gate.
2026-05-23 09:58:26 -07:00
Shay
46c734b7aa
Merge pull request #163 from AssetOverflow/opus2/adr-0128-en-numerics-v1
feat(packs): ADR-0128.1+0128.2 — en_numerics_v1 pack + loader
2026-05-23 07:09:37 -07:00
Shay
04eb5626ea feat(packs): ADR-0127.1+0127.2 — en_units_v1 + loader
Exhaustive units pack: 13 dimensions (7 base + 6 derived), ~150 unit
lemmas, ~25 containers, ~80 conversion edges (within-dimension
exhaustive, NIST/ISO sourced), affine temperature offsets, multi-
word structural rules.

Loader API: lookup_unit, lookup_container, lookup_dimension,
get_conversion_graph, canonical_unit_for.

Ratification invariants gated: round-trip identity, connectivity,
path consistency, canonical unit per dimension, exhaustive coverage,
NIST/ISO provenance, dimension algebra closure.

No parser/solver changes (deferred to 0127.3-0127.7).
2026-05-23 07:04:06 -07:00
Shay
452e3bb9f5 feat(packs): ADR-0128.1+0128.2 — en_numerics_v1 + loader
Exhaustive English linguistic-form ontology for quantities:
cardinals (0..20 + tens + magnitudes + compound rule), ordinals
(1st..31st + decade/magnitude forms), named fractions (1/2..1/10
+ sixteenth/thirty-second) + symbol forms (½ ¼ ¾ ⅓ ⅔ ⅛ ⅜ ⅝ ⅞),
multipliers (double/triple/twice/half), quantifiers with
semantic_type (indefinite triggers refusal at parse time —
preserves wrong==0), comparison anchors migrated for
ratifiability, number-format regexes with positive/negative
corpora.

Loader API in language_packs/numerics_loader.py (sibling module
to be merged into main loader after Gemini's ADR-0127 loader
lands, to avoid concurrent merge conflict).

Ratification invariants gated: cardinal/ordinal/fraction
exhaustiveness, quantifier semantic-type closed set, format-regex
test corpora (10+ positive/negative per format, ambiguity
refused), manifest checksums = SHA-256 of bytes-on-disk,
self-sealing mastery report.

Cross-references en_units_v1 (Gemini ADR-0127): fraction symbols
authoritative here; en_units_v1 symbol-affix table will point to
these entries.

No parser changes (deferred to 0128.3-0128.6). No train-sample
re-run (joint exit gate with ADR-0127 runs after both packs land).

Total: 130 lexicon entries across 7 kinds.
Lanes: smoke 67/0/0, packs 6/0/0, ADR-0128 suite 243/0/0.
2026-05-23 07:02:09 -07:00
Shay
feeb64818c feat(ADR-0126 P3+P4): graph assembly + decision rule + runner wiring
P3 — generate/math_candidate_graph.py:
  Branch enumeration over per-sentence candidate choices (Cartesian
  product, cap=64). Per-sentence ambiguity tiebreaker via most-grounded-
  slots-wins (transfer beats subtract when 'to Tom' grounds). Decision
  rule: 0 admissible -> refuse; 1 -> emit; >=2 same answer -> emit;
  >=2 different answers -> refuse (preserves wrong==0 on genuine
  ambiguity). End-to-end parse_and_solve(text) -> CandidateGraphResult.

  Question extractor added to math_candidate_parser.py (CandidateUnknown,
  total + entity question shapes mirroring math_parser).

  22 new tests. Permissive verbs ('bought', 'ate', 'bakes') now produce
  correct answers via the candidate-graph path; ambiguous 'gives to Tom'
  resolves to transfer reading (Tom gets the apples) deterministically.

P4 — evals/gsm8k_math/runner.py:
  New sibling function _score_one_candidate_graph(case) -> CaseOutcome.
  Identical shape to _score_one; swaps parse_problem for parse_and_solve;
  preserves verifier/realizer/expected-answer stages. Callers (e.g.
  PR #160's train_sample/v1/runner.py) substitute the new function in
  one line to evaluate the candidate-graph topology.

  9 new wiring tests. Three groups:
    - No regression: cases legacy solves, new also solves.
    - Lift: cases legacy refuses, new solves (the architectural payoff).
    - Wrong==0: out-of-grammar refuses, never wrong.

Regression: 714/714 existing math + runner tests still green.
ADR-0126 total: 74/74 tests green across P1+P2+P3+P4.
2026-05-23 06:36:13 -07:00
Shay
e8894f7a70 feat(ADR-0126 P2): candidate-emitting sentence parser + 17 tests
Sibling to math_parser.py — pure candidate-extraction functions that
emit list[CandidateOperation] per sentence without mutating any state.
State threading defers to P3 (per-branch graph assembly).

Topology change vs legacy:
  - No first-match-wins; every verb-kind regex runs independently.
  - Ambiguous verbs ('gives', 'returns') emit multiple candidates;
    P1's round-trip filter + P3's decision rule resolve.
  - Out-of-grammar sentences return [], NOT ParseError. Empty list
    is the deterministic 'no candidate' signal.

Permissive verb tables (imported from math_roundtrip.KIND_TO_VERBS)
mean past-tense and production verbs ('bought', 'ate', 'bakes')
that the legacy parser refused are now admissible — the round-trip
filter is the safety mechanism, not regex narrowness.

P2 scope (canonical Subject-verb-Value-Unit-[to-Target] shape only):
  - extract_initial_candidates(sentence) for 'X has N units'
  - extract_operation_candidates(sentence) for add/subtract/transfer

Out of scope (deferred to later sub-phases):
  - Pronoun resolution / unit inheritance (needs per-branch state)
  - Multiply / divide / rate / comparison (same machinery, more matchers)

Regression: existing math suite 701/701 green. Zero changes to
math_parser.py, math_solver.py, math_verifier.py, math_realizer.py.
2026-05-23 06:36:13 -07:00
Shay
661d67002e feat(ADR-0126 P1): round-trip admissibility primitive + 26 tests
The wrong-answer firewall for the candidate-graph parser topology.

A CandidateOperation carries an Operation plus source-span provenance
for every content slot the parser claimed (verb, value, unit, actor,
transfer target, comparison reference). roundtrip_admissible() checks
each slot grounds in the source span AND the matched verb is
registered for the claimed kind.

Two consequences:
- A regex that mis-reads 'loses' as add fails (loses not in ADD_VERBS).
- A regex that hallucinates a number/unit not in source fails to ground.

KIND_TO_VERBS is the new single source of truth for {kind -> verbs};
P2 will refactor math_parser to consume it. Verb tables are
permissive by design (much wider than current narrow regex tables)
because the filter rejects wrong candidates downstream — narrowness
is no longer the safety mechanism.

Deterministic: pure byte/regex containment. No randomness, no
learning, no approximation. Preserves wrong==0, trace_hash byte-
equality, replay determinism.
2026-05-23 06:36:13 -07:00
Shay
9d19b8176f feat(gsm8k): ADR-0126 P6 — train-sample runner + exit-criterion gate
Wraps existing math pipeline (parser -> solver -> verifier) against
PR #159's 50-case train sample. Emits deterministic report.json with
per-case verdicts. CLI exit code reflects exit criterion
(correct >= 10 AND wrong == 0).

Baseline against current parser: 0 correct / 0 wrong / 50 refused.
This baseline is the inner-loop gradient signal for ADR-0126's
candidate-graph parser (in flight on feat/adr-0126-candidate-graph).

Registers tests/test_adr_0126_train_sample_runner.py under
'core test --suite math' so the wrong == 0 invariant becomes a hard
CI gate per ADR-0114a Obligation #4 (refuse rather than confabulate).

Depends on PR #159 (gemini/adr-0126-train-sample). Rebase onto main
after #159 lands.
2026-05-23 06:33:06 -07:00
Shay
ec1dcf6e78 feat(realizer): ADR-0123 comparison-phrasing surface (closes substrate)
ADR-0123-parser-comparison-phrasing as the **surface increment** on
PR #155's substrate (commit c9bd5d4). Closes the last architectural
gap in the comparison-phrasing class: before this commit, the
substrate's solver evaluated comparison problems successfully but
realize() crashed with `unknown operation_kind 'compare_additive'`
when asked for show-your-work prose.

Substrate (PR #155) already shipped:
- `Comparison` typed graph operand
- `compare_additive` / `compare_multiplicative` operation kinds
- parser patterns for the four canonical surfaces
  (N more / N fewer / twice / N times / half)
- solver + verifier wiring + pack lemmas
  (en-arith-006 compare_additive, en-arith-007 compare_multiplicative)

This surface adds:
- `_compare_additive_sentence(step)` rendering `direction='more'|'fewer'`
- `_compare_multiplicative_sentence(step, entity_units)` rendering
  `direction='times'|'fraction'`
- two new branches in `_step_sentence` dispatch
- `_step_sentence` signature widened with optional `entity_units` map
  (derived once-per-trace in `realize()` from `graph_initial_state`)
- ADR-0123-parser-comparison-phrasing.md (~15 invariants, substrate
  + surface decomposition rationale, multi-construction barrier
  inheritance)
- 26 invariants pinned across canonical surfaces, plurality
  independence, byte-determinism, refusal discipline, and
  backwards-compatibility with the pre-comparison realizer templates

End-to-end pipeline now operates on all four canonical comparison
shapes:

  parse_problem(
    "Alice has 5 apples. Bob has 3 more apples than Alice. "
    "How many apples does Bob have?"
  ) -> solve() -> realize().as_prose() ->
  "Alice has 5 apples. Bob has 3 more apples than Alice, giving Bob
   a total of 8 apples. Bob has 8 apples."

Measurement (this PR):
- 26/28 direct ADR-0123 tests pass; 2 skipped (CORE_HOLDOUT_KEY)
- `core eval cognition` byte-identical: 100/100/100/100
- ADR-0118 stepped-realizer templates re-render byte-identically
- Substrate measurements continue to hold

Honest non-result: sealed `correct_rate` stays at 0/1319. The
realizer cannot create matches the parser refuses; the multi-
construction barrier the substrate ADR documented holds at the
surface layer too. Cumulative lift signal expected only after the
3rd/4th foundational class lands (per ADR-0121's revised
sequencing). `wrong == 0` holds by construction — realizer only
renders successful traces.

Pre-existing failure noted (not introduced by this PR):
`tests/test_adr_0085_gloss_aware_cause.py::test_flag_off_metrics_byte_identical`
fails on substrate base (c9bd5d4) without these changes — an
ADR-0085 cognition baseline drift unrelated to the realizer.
2026-05-23 02:03:49 -07:00
Shay
6582df3bae feat(parser): ADR-0122 rate/per-unit grammar (substrate-only; lift deferred)
First parser-expansion ADR after ADR-0121's deferral. Adds the rate
algebra substrate (Rate dataclass + apply_rate operation kind + parser
pattern + solver/verifier/realizer + en_arithmetic_v1 pack lemma)
mirroring the deferral pattern that ADR-0121 demonstrated for
capability promotion: substrate complete, gate refuses honestly.

Substrate
- Rate(value, numerator_unit, denominator_unit) frozen dataclass with
  strict positive-value + non-empty-distinct-unit refusal at construction
- apply_rate operation kind admitted in VALID_OPERATION_KINDS;
  Operation.operand widened to Quantity | Rate with kind-discriminated
  type enforcement
- Parser: _RATE_COST_EACH_RE + _RATE_COST_EACH_TRAILING_RE +
  _Q_RATE_AGGREGATE_RE patterns; actor_units state tracking;
  first-declaration-wins on redeclaration (ParseError); orphan-rate
  refusal at end of parse; three refusal paths in rate-aggregate question
- Solver: _apply_rate() reads denominator-unit state, multiplies by
  rate.value, writes numerator-unit state (denom preserved)
- Verifier: _verify_apply_rate_step() byte-equal replay
- Realizer: 'At {N} {numer} per {denom_singular}, {actor} spends ...'
  template containing required tokens
- Pack: en-arith-006 apply_rate lemma + gloss; SHA-256 checksums
  refreshed; manifest version 1.0.0 -> 1.1.0; provenance tagged
  adr-0122:rate_extension:2026-05-22

Measurement (honest)
- Sealed GSM8K correct_rate: 0/1319 (substrate matches zero real cases
  alone). Multi-construction barrier documented in the ADR: all 14 sealed
  cases matching 'each \w+ costs?' combine rate with at least one other
  class (aggregation 6, comparison 3, unit conversion 2, multi-actor 2,
  conditional 1)
- Sealed GSM8K wrong: 0 (load-bearing positive claim; grammar adds zero
  misparses across 1,319 real test problems)
- Anti-overfit lanes unchanged: OOD ratio, perturbation invariance
  preserving/breaking 1.0, adversarial wrong 0
- ADR-0121 invariants byte-equal preserved (6/6)
- 41 new ADR-0122 invariants pinned in tests/test_adr_0122_rate_per_unit.py
- 670 existing math + pack regression tests pass

Roadmap update
- Per-ADR lift expectation corrected: no single parser-expansion ADR
  will move sealed correct_rate alone. First lift signal will come
  from cumulative composition after 3rd or 4th class lands (rate +
  comparison + aggregation foundational set)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 21:24:28 -07:00
Shay
4f473a5aa8 docs: ADR-0121 — mathematics_logic expert promotion DEFERRED (first attempt)
First worked attempt at promoting a domain under the ADR-0120
expert promotion contract. The contract refuses honestly.

Gate evaluation against live state:
  ADR-0114a obligations: 10 of 10 pass
  ADR-0120 contract-level gates:
    audit_passed_holds         ✓
    correct_rate (public)      ✓  150/150 = 1.0
    correct_rate (sealed)      ✗  0/1319 = 0.0 < 0.60 floor
    signed_expert_claim        ✗  (no entry, downstream of correct_rate)

Decision: mathematics_logic NOT promoted; stays at audit-passed.
Substantive blocker: parser grammar covers 0/1319 of real GSM8K.

What this proves
  - The contract is genuinely falsifiable. ADR-0120 §"Threshold
    rationale" deliberately set the floor above current measurement
    so the first attempt would defer honestly. Same load-bearing
    pattern as ADR-0107 → ADR-0110 for audit-passed.
  - Wrong-zero discipline holds against real GSM8K (the load-
    bearing positive claim). CORE refuses every problem outside
    its grammar without confabulating on a single one.

What unlocks the promotion
  Multi-ADR parser-expansion arc lifting sealed-GSM8K correct_rate
  from 0.0 to ≥ 0.60. Each construction class (rate/comparison/
  percentage/time-modal/etc.) ships as its own scoped ADR with:
    - parser+solver+verifier+realizer extensions
    - re-measurement on sealed holdout
    - ADR-0118a OOD re-measurement (no surface-feature regression)
    - ADR-0125 perturbation re-measurement (no invariance regression)
    - ADR-0119.5 adversarial re-measurement (no new misparses)
  Honest-fitting discipline: every lift is graded on the anti-
  overfit obligations BEFORE the correct_rate change counts.

Tests: 6/6 with CORE_HOLDOUT_KEY; 4/6 + 2 skipped without (matches
ADR-0119.7 seal discipline).

This deferral demonstrates the expert tier's promotion machinery
is load-bearing — the gate has refused at least once before any
domain reaches it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:31:06 -07:00
Shay
38872f825a feat: ADR-0119.7 — seal GSM8K test as gsm8k_math holdout (Phase 5 substrate complete)
The 1,319 GSM8K test cases are now sealed at
evals/gsm8k_math/holdouts/v1/cases.jsonl.age, age-encrypted to the
ADR-0119.1 recipient. Plaintext never touched disk in the working
tree; only ciphertext is committed.

First honest CORE-vs-real-GSM8K measurement
  cases_total: 1319
  correct:     0
  wrong:       0   ← ADR-0114a Obligation #4 holds against external corpus
  refused:     1319
  overall_pass: True

Zero confabulation. Parser refuses what it can't grammar-handle; the
"wrong == 0" discipline survives the move from CORE-original cases
to a real public benchmark. The 0/1319 correct rate is the truthful
gap that ADR-0120's threshold work will quantify.

What landed

scripts/seal_gsm8k_test.py
  - Loads GSM8K via datasets.load_dataset("openai/gsm8k", "main")
  - Strips worked-solution prose; extracts final-answer integer/float
    after "####" (handles "2,125" → 2125 thousands-separator)
  - Reads recipient from docs/holdout_recipients.txt (single repo key
    per ADR-0119.1)
  - Encrypts via pyrage; writes only ciphertext
  - Refuses to overwrite test path with train-derived seal

evals/gsm8k_math/runner.py
  - Empty expected_unit (sentinel) skips unit-comparison; grades on
    answer value alone. Required because GSM8K answers carry no unit
    structurally. wrong-zero discipline preserved.

tests/test_adr_0119_7_sealed_gsm8k.py — 6 invariants:
  1. sealed file present + age-formatted
  2. no plaintext companion files (sibling-leak guard)
  3. decrypted JSONL matches documented schema
  4. runner against decrypted suite produces wrong==0
  5. tests skip (not fail) when CORE_HOLDOUT_KEY unset
  6. case ids match "gsm8k-test-NNNN" pattern

Defensive gitignore: plaintext patterns under
evals/gsm8k_math/holdouts/v1/ are explicitly excluded.

ADR-0114a obligation roll-up
  10/10 discharged for the gsm8k_math lane:
    #1 ✓ sealed-holdout (fab_control + GSM8K test)
    #2..#10 ✓ as before

Phase 5 status: 5.1..5.7 done; 5.8 in flight (PR #149). After 5.8
merges, ADR-0120 (first expert promotion contract) becomes
feasible.

Test plan
  - pytest tests/test_adr_0119_7_sealed_gsm8k.py with CORE_HOLDOUT_KEY → 6/6
  - pytest without CORE_HOLDOUT_KEY → 3 pass + 3 skip
  - core test --suite smoke -q → 67/67
  - CLAIMS.md regenerated (no diff)
  - HF token NEVER in repo (saved at ~/.cache/huggingface/token, mode 600)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 20:08:35 -07:00
Shay
a13df6f370 feat: ADR-0119.8 — gsm8k_math overall lane gate (gsm8k_capability_shape)
Phase 5.8 of ADR-0119. Composes the per-sub-phase substrate
(5.1..5.6) into a single per-split lane verdict the eventual
ADR-0120 expert promotion contract can consume.

LANE_SHAPE_REGISTRY adds:
  "gsm8k_math": "gsm8k_capability_shape"

_check_gsm8k_capability_shape refuses on any of:
  - missing cases_total / correct / wrong / refused fields
  - cases_total <= 0
  - wrong != 0                          (ADR-0114a Obligation #4)
  - correct + refused != cases_total    (accounting incomplete)
  - overall_pass present and false

Accepts otherwise. Edge: all-refused passes the shape gate (runner
self-consistency). Capability bar (min correct-rate, depth-curve
ε) lives in ADR-0120.

Live measurement on main:
  dev    50/50 correct, 0 wrong, 0 refused  → gate ✓
  public 150/150 correct, 0 wrong, 0 refused → gate ✓

21 invariant tests pin: registry mapping, shape checker presence,
live runner passes, nonzero wrong refuses, incomplete accounting
refuses, missing field refuses, clean metrics pass, all-refused
edge passes, all Phase 5.1..5.6 substrate artifacts exist on disk.

Phase 5 status: 5.1..5.6 + 5.8 ✓. Only 5.7 (sealed real GSM8K
test) remains before ADR-0120 (first expert promotion contract)
becomes feasible.

ADR-0114a roll-up unchanged: 10/10 obligations discharged on main
(modulo Phase 5.7's lane-specific GSM8K test sealing).

Tests: 21 new + 80 prior across Phase 5 + adjacent suites = 101
green; 67/67 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 19:45:44 -07:00
Shay
5cbd782e7b
Merge pull request #148 from AssetOverflow/feat/adr-0119.5-adversarial
feat: ADR-0119.5 — adversarial generation (closes ADR-0114a Obligation #8)
2026-05-22 19:39:00 -07:00
Shay
3bda4313c9 feat: ADR-0119.5 — adversarial generation (closes ADR-0114a Obligation #8)
Phase 5.5 of ADR-0119. Adversarial case generator + scoring CLI;
discharges the last remaining ADR-0114a obligation.

Numbers
  adversarial suite: 38 cases × 12 families
  per-family: every family produces wrong == 0
  overall: correct 5, wrong 0, refused 33

Families
  conditional_phrasing       (4)  "If/When/Suppose ..."
  compound_questions         (3)  multiple ?
  undefined_entity_question  (3)  question references unknown entity
  unknown_verb               (5)  "polishes", "admires", etc.
  empty_or_whitespace        (3)  empty input
  no_question                (3)  statement-only
  numbers_spelled_out        (3)  "five", "ten"
  passive_voice              (3)  "X are bought by Y"
  red_herring_numbers        (3)  digits in name positions, mid-quantity
  question_only              (2)  no preceding statements
  mid_sentence_punctuation   (2)  embedded ? or !
  subtle_in_grammar          (4)  IN-grammar; runner must produce correct
                                  (gate-sanity: not trivially "refuse all")

The subtle_in_grammar family is the load-bearing sanity check —
proves the gate isn't trivially satisfied by refusing everything.

ADR-0114a obligation status

  10 of 10 discharged on main:
    #1  fab_control lane (0119.1); GSM8K test pending (0119.7)
    #2  ADR-0118a
    #3  ADR-0117
    #4  ADR-0116 + ADR-0119.3
    #5  ADR-0125
    #6  ADR-0119.6 harness; ε threshold to ADR-0120
    #7  ADR-0119.4
    #8  THIS ADR
    #9  ADR-0116/0117/0118/0119.3
    #10 ADR-0116

Phase 5 remaining: 5.7 (sealed GSM8K test, real corpus) and 5.8
(overall lane gate). After those, ADR-0120 (first expert promotion
contract) can compose all ten obligations.

Tests: 18 new + 25 prior Phase 5 = 43 green; 67/67 smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 18:11:36 -07:00
Shay
78312b3151 chore: ADR-0119.4 + ADR-0119.6 cleanup — typed refusals + numeric/freshness asserts
Audit follow-ups from #145/#146 merge review. Five small fixes; no
behavior change on the green path, but failure modes are now explicit
rather than silent.

ADR-0119.6 depth_curve.py
  - Add DepthCurveError typed exception
  - Raise on case_id missing from lane_report (was: silent → "refused")
  - Raise on depth >= 9 (was: silent new bucket key)
  - Two new tests pin both refusals
  - Removed stale sys.path hack at module top

ADR-0119.4 frontier-baseline tests
  - Assert comparison_v1.json's core_measurement reports wrong == 0
    (the load-bearing differentiator named in the disclaimer; a
    tampered file with wrong > 0 was previously syntactically valid
    and would have passed all old assertions)
  - Assert frontier citations are dated 2023 or later (freshness
    guard; older citations should be refreshed before ADR-0120
    gates anything for `expert` promotion)

Tests
  - tests/test_adr_0119_6_depth_curve.py: 7 → 9
  - tests/test_adr_0119_4_frontier_baseline.py: 5 → 7
  - 29/29 across runner + depth-curve + frontier suites; 67/67 smoke

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:47:42 -07:00
Shay
0ffbd5c40a merge origin/main and resolve README conflicts 2026-05-22 17:38:21 -07:00
Shay
9288688640
feat: ADR-0119.3 — gsm8k_math lane runner (Phase 5.3) (#145)
Composes the Phases 1-4 pipeline (parser → solver → verifier →
realizer) into a per-case scoring decision: correct / wrong /
refused.

Outcome categorization (ADR-0114a Obligation #4):
  parser ParseError       → refused
  solver SolveError       → refused
  verifier verdict failed → wrong
  realizer error          → wrong
  answer/unit mismatch    → wrong
  all match               → correct

`wrong == 0` is the load-bearing gate. The lane's overall_pass
holds only if wrong == 0 AND correct + refused == total.

Initial measurement on the Phase 5.2 corpus:
  dev    (50)  : 50 correct, 0 wrong, 0 refused, overall_pass=True
  public (150) : 150 correct, 0 wrong, 0 refused, overall_pass=True

Every correct case carries a trace_hash (64-char SHA-256) and
realized prose — full audit trail per case, consumable by ADR-0119.4
(frontier comparison), ADR-0119.6 (depth curve), and ADR-0120
(eventual expert-tier gate).

Tests: 13/13 green; 443 total green across runner + realizer +
solver + verifier; 67/67 smoke green.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:37:54 -07:00
Shay
a65040cb73 merge origin/main and resolve conflicts 2026-05-22 17:37:13 -07:00
Shay
51d3a73589 feat: ADR-0119.6 — depth-curve measurement harness (ADR-0114a Obligation #6) 2026-05-22 17:33:58 -07:00
Shay
c21068ed3e feat: ADR-0119.4 — frontier-baseline comparison (ADR-0114a Obligation #7) 2026-05-22 17:33:28 -07:00
Shay
f9dd650df0 Merge remote-tracking branch 'origin/main' into feat/adr-0119.1-sealed-holdout-fabrication-control
# Conflicts:
#	docs/decisions/README.md
2026-05-22 17:24:32 -07:00
Shay
32c0a90ad9 feat: ADR-0119.1 — seal fabrication_control holdout with age encryption (Obligation #1) 2026-05-22 17:22:46 -07:00
Shay
98eb4d9f75
Merge pull request #141 from AssetOverflow/feat/adr-0118-stepped-realizer
feat: ADR-0118 — stepped realizer (SolutionTrace → show-your-work prose)
2026-05-22 17:20:31 -07:00
Shay
c1d726179a feat: add ADR-0125 perturbation suite 2026-05-22 17:12:33 -07:00
Shay
7ad3f72cb4 feat: ADR-0118 — stepped realizer (SolutionTrace → show-your-work prose)
Phase 4 of the ADR-0114 GSM8K-math roadmap. Consumes a SolutionTrace
and emits one sentence per step plus setup + answer sentences. Pure
function; same trace → byte-equal RealizedTrace.

What landed

generate/math_realizer.py
  - realize(initial_state, trace) -> RealizedTrace
  - Frozen RealizedTrace dataclass with canonical_bytes() + as_prose()
  - Per-kind sentence rules (add / subtract / transfer / multiply×2 /
    multiply×3 / multiply-general / divide)
  - Singular/plural surface rule matches parser canonicalization
  - Typed RealizerError on unrecognized step kinds

tests/test_math_realizer.py — 60 cases pinning five invariants:
  1. All 50 dev-set cases realize without error
  2. Determinism: byte-equal RealizedTrace across two calls
  3. Setup sentence count == initial_state count
  4. Step sentence count == operation count
  5. Answer sentence contains the resolved value + unit

ADR-0114a obligation discharge update

ADR-0118 hardens determinism (#9) across a third layer (realizer)
and makes #3 / #10 human-inspectable via the prose surface. No
obligation is directly newly discharged by ADR-0118; it's substrate
for ADR-0119 GSM8K eval lane.

Round-trippability of the prose through the parser is explicitly
out of scope for this phase. The trace is the verifiable artifact
(ADR-0117); the prose is human-readable documentation.

Tests: 60 new realizer cases; 546 total green across realizer +
parser + solver + verifier + OOD; 67/67 smoke green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:11:10 -07:00
Shay
a8d019d8a0 docs: ADR-0123a — document all_three_pass_rate synonym in inference_shape 2026-05-22 17:00:51 -07:00
Shay
1a929a4e83 feat: ADR-0124 — systems_software audit-passed promotion (third successful) 2026-05-22 16:55:41 -07:00
Shay
9d2a5f22e3 feat: ADR-0118a OOD surface generator 2026-05-22 16:49:40 -07:00
Shay
5e52cd4547
Merge pull request #136 from AssetOverflow/feat/adr-0123-lane-shape-symbolic-logic-fix
feat: ADR-0123 — re-map symbolic_logic to inference_shape (unblocks ADR-0122)
2026-05-22 16:45:52 -07:00
Shay
4336490731 feat: ADR-0117 — SolutionTrace verifier (solver-independent)
Phase 3 of the ADR-0114 expert-capability roadmap. Re-applies every
step of a SolutionTrace from the input graph's initial state and
asserts byte-equal reproduction of answer_value. Pure function; same
(graph, trace) → byte-equal VerifierVerdict.

Why this is distinct from the solver

ADR-0116's solver enforces correctness at construction. ADR-0117's
verifier is a SECOND, INDEPENDENT implementation that re-derives
every value the trace claims. The verifier does NOT call solve(). It
re-implements the operation semantics from ADR-0116 directly inside
_verify_step. If the solver had a bug or was tampered with after the
fact, the verifier catches it.

Six checks per verdict (named, ordered, audit-logged):
  1. graph_canonical_hash_matches
  2. pack_id_matches
  3. pack_lemmas_resolve
  4. step_pack_lemma_ids_match_bindings
  5. step_replay_matches_before_after
  6. answer_value_reproduces

Seven named tamper classes all caught:
  - mutated before_value / after_value / operand of any step
  - mutated pack_lemma_id of any step
  - mutated graph_canonical_hash
  - mutated answer_value
  - mutated pack_id
  - mutated target_before / target_after of transfer step

ADR-0114a obligation update

  #3 Replay-equal trace — now discharged at VERIFIER FIDELITY
     (was solver-only under ADR-0116). A third party with only
     (graph, trace, pack) can reproduce the answer byte-equal.

Five of ten obligations now load-bearing: #3, #4, #9, #10 plus
in-flight #2 (Codex's ADR-0118a OOD generator).

Tests: 62/62 verifier suite green; 67/67 smoke green; existing
solver + parser + schema suites unaffected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:40:38 -07:00
Shay
71321a5058 feat: ADR-0123 — re-map symbolic_logic to inference_shape (unblocks ADR-0122) 2026-05-22 16:39:53 -07:00
Shay
a0e9833851 feat: ADR-0122 systems_software audit-passed deferred (lane-shape mismatch) 2026-05-22 16:31:59 -07:00
Shay
d2f5607167 feat: ADR-0116 — deterministic solver + en_arithmetic_v1 operator pack
Phase 2 of the ADR-0114 expert-capability roadmap. Consumes the
MathProblemGraph from Phase 1 and emits a SolutionTrace — ordered
operation applications ending at a numeric answer, byte-deterministic
across runs, with each step's operation bound to a pack-resolved
lemma identifier.

What landed

generate/math_solver.py
  - solve(graph) -> SolutionTrace; pure function, no I/O, no globals
  - SolutionStep dataclass with before/after values per step (for
    verifier replay; ADR-0117 hardens)
  - SolutionTrace with canonical_bytes() byte-deterministic JSON
  - SolveError typed refusal: missing pack, division by zero,
    unknown-references-nothing

language_packs/data/en_arithmetic_v1/
  - 5 operator lemmas: add / subtract / multiply / divide / transfer
  - role=operational_base (vocabulary-only; no domain claim)
  - SHA-256-anchored lexicon + glosses; manifest carries
    provenance=adr-0116:operator_seed:2026-05-22

tests/test_math_solver.py — 109 cases pinning five invariants:
  1. Phase 2 exit criterion: ≥ 0.80 on parser-correct dev set
     (current: 50/50 = 1.00)
  2. Determinism: two solves produce byte-equal trace
  3. Trace replay reproduces answer_value (verifier rehearsal)
  4. Typed refusal on under-determined inputs
  5. Every step.pack_lemma_id resolves to a real lexicon entry
     in en_arithmetic_v1

ADR-0114a obligation discharge

Four of ten anti-overfitting obligations now have load-bearing
implementations in code:

  #3  replay-equal trace                 — discharged (solver-layer)
  #4  typed refusal                      — discharged (solver-layer)
  #9  determinism                        — discharged (solver-layer)
  #10 operation provenance via pack      — DISCHARGED IN FULL

Removing the en_arithmetic_v1 pack now breaks every solve loudly.
The "operations bind to concepts, not hardcoded strings" claim is
architecturally true, not rhetorical.

Tests: 109/109 green on solver suite; 67/67 smoke suite green;
parser + schema suites still green from prior phases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:28:04 -07:00
Shay
18503f3d6e feat: ADR-0115 Phase 1.3 — deterministic math word-problem parser
Closes Phase 1.3 of the ADR-0114 expert-capability roadmap. Turns a
grade-school word problem into a typed MathProblemGraph deterministically
(no LLM, no sampling). Same input string always produces the same
graph; unsupported constructions raise ParseError rather than guessing.

What the parser handles

  Initial possession:    "<E> has <N> <unit>."
  Add verbs:             buys, gets, finds, receives, earns, adds
                         (+ "<N> more" / unit elision via state.last_unit)
  Subtract verbs:        eats, loses, sells, donates, uses, spends, drops, removes
  Transfer verbs:        gives, sends, hands, passes, mails  (with target)
  Multiply (scalar):     "X doubles <obj>" / "X triples <obj>"
  Divide (split):        "X splits {them|his Y|N Y} evenly into M groups [and keeps one]"

  Compound sentences:    "X buys 5, then donates 3."
  Sentence opener:       "Then X eats 1."  (inherits subject + unit)
  Pronoun anaphora:      he/she/it → last-introduced singular subject
  Object pronoun:        them/these/those → state.last_unit
  Trailing PP:           "finds 7 buttons on the floor" — discarded
  Singular→plural:       "Iris has 1 coin" → canonical unit "coins"

  Questions:
    "How many <unit> does <E> have [left|now|in total|altogether]?"
    "How many <unit> do they have [in total|altogether|left|now]?"

What it explicitly rejects

  - Conditional / time-modal ("If X had ...")
  - Compound questions (two unknowns)
  - Multiple "?" sentences
  - Questions referencing entities never introduced
  - Empty / whitespace-only input

Verification

  - tests/test_math_parser.py: 20 cases (5 byte-equal parametrized
    + 5 determinism parametrized + 1 exit-criterion gate + 6 typed-
    refusal + 2 purity + 1 type check)
  - tests/test_math_problem_graph.py: 26 schema cases still green
  - On the 5 seed cases:  5/5 = 100% byte-equal
  - On Codex's PR #128 50-case dev set (locally tested):
    49/50 = 98% byte-equal. Single failure (gpd-021) is a case-
    quality issue, not a parser limit; feedback filed on #128 to
    rewrite (mixed units + metaphor not in pattern registry).
  - Phase 1.3 exit criterion (≥ 0.90): met.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 16:03:31 -07:00
Shay
57b257ca1d feat: ADR-0115 Phase 1.1 — math problem graph schema + 5 seed cases
First Phase of ADR-0114's expert-capability roadmap. Decomposed into four
sub-phases so each lands as its own auditable step:

  1.1  schema + 5 seed cases + invariants   ← this commit
  1.2  45 more dev-set cases                 ← delegated (Codex)
  1.3  the parser itself                     ← exit: ≥0.90 on dev set
  1.4  runtime binding                       ← if non-trivial

What landed

- generate/math_problem_graph.py — typed dataclasses (Quantity,
  InitialPossession, Operation, Unknown, MathProblemGraph) + frozen
  validation + canonical_bytes() byte-deterministic serialization +
  graph_from_dict roundtrip.

- evals/gsm8k_parser_dev/cases.jsonl — 5 seed cases (gpd-001..005)
  covering single-add, single-subtract, multi-step, two-entity
  transfer, and multi-entity sum constructions. Every case carries a
  ground_truth_graph and the documented patterns it exercises.

- evals/gsm8k_parser_dev/README.md — authoring contract: schema,
  pattern registry, canonicalization rules, Phase 1.1 scope boundary,
  hand-solving rubric, distribution target for the remaining 45
  cases. This is the spec Phase 1.2 authors work against.

- tests/test_math_problem_graph.py — 26 cases pinning four invariants:
  round-trip byte equality, canonical_bytes() determinism, schema
  rejection of malformed graphs, and ground_truth_graph ↔
  expected_answer agreement (a hand-solver inside the test module
  falsifies mis-authored cases).

Why this is sticky

The Phase 1.1 schema is load-bearing for Phase 1.2 (the 45 authored
cases will be written against it) AND Phase 1.3 (the parser will be
graded byte-equal against ground-truth graphs in this schema). Changing
the schema after Phase 1.2 lands requires an amendment ADR + rewriting
authored cases. The schema choices here are intentionally conservative.

Tests: 26/26 new; 67/67 smoke green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:50:34 -07:00
Shay
696f62abdd feat: ADR-0113 rename expert-demoaudit-passed; reserve expert namespace (ADR-0114 GSM8K roadmap)
The word "expert" in the previous status name implied raw-capability parity
with frontier LLMs on the same benchmark — which the gate does NOT verify.
What the gate actually verifies is CORE *claim-shape compliance*:

  * signed digest (replay-reproducible from on-disk lane results)
  * replay determinism (same inputs → byte-equal trace_hash)
  * typed refusal (fabrication refused, not paraphrased)
  * exact recall (no ANN, no cosine, no attention bottleneck)
  * grounding-source provenance

These are claim shapes a transformer LLM cannot structurally produce
regardless of raw accuracy. A frontier LLM might score higher on the
same benchmark but cannot pass this contract.

Rename scope (semantics only, per ADR-0113):

  status string         "expert-demo"        → "audit-passed"
  predicate key         predicates.expert_demo → predicates.audit_passed
  reason key            expert_demo_reason   → audit_passed_reason
  YAML key              expert_demo_claims   → audit_passed_claims
  CLI command           core demo expert     → core demo audit-passed
  output dir            evals/expert_demos/  → evals/audit_passed/
  artifact filenames    expert_demo.{json,html} → audit_passed.{json,html}
  HTML title            CORE Expert-Demo: X  → CORE Audit-Passed: X

Internal Python identifiers (module/file/function/class names like
`expert_demo.py`, `evaluate_expert_demo`, `ExpertDemoClaim`,
`expert_demo_claim_for`) are deliberately kept to minimize churn. ADR
file titles (ADR-0106..0112) preserved as historical record.

`expert` namespace reserved for ADR-0114+: an actual capability tier
above `audit-passed` backed by a public benchmark with a stated
threshold. ADR-0114 proposes the first such target — GSM8K-math —
laying out a falsifiable 7-phase arc (parser → solver → verifier →
stepped-realizer → eval lane → first `expert` ledger tier promotion).

Tests: 184 directly-affected tests green (140 capability/expert-demo
suite + 34 demo/audit-tour + 10 correction-cue). Smoke suite 67/67.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:36:10 -07:00
Shay
bd7005c786 feat: ADR-0112 runnable expert-demo showcase (core demo expert --domain <id>)
Closes the asymmetry between the `expert-demo` ledger status (audit
artifact only) and the actual `core demo` surface (runnable
walkthroughs producing HTML + JSON). Until this commit the word
"demo" in `expert-demo` was aspirational; now it corresponds to
something a reader can open.

What it does

- Reads the signed expert_demo_claims entry from docs/reviewers.yaml
- Loads latest on-disk result files for each attached lane × split
- Re-derives the evidence-bundle digest and asserts byte-for-byte
  match against the signed claim_digest — this is the load-bearing
  audit step, now exercised at two independent enforcement points
  (ledger gate + showcase)
- Runs each lane's metrics through the ADR-0109 lane-shape registry
  and surfaces the verdict
- Picks the first three cases from each split verbatim (deterministic
  by file order) and renders them as HTML for inspection
- Emits expert_demo.json (canonical bytes, deterministic) + expert_demo.html

Surface

  core demo expert --domain mathematics_logic
  core demo expert --domain physics
  # → evals/expert_demos/<domain>/latest/expert_demo.{json,html}

Read-only by construction: cannot mutate docs/reviewers.yaml or any
lane result file. Tested. Unpromoted domains raise ValueError —
no silent fallback, no "preview" mode that fakes a showcase.

Generated artifacts are gitignored — the inputs they derive from are
already committed, so duplicating the renders would just churn the
tree.

Tests: 16 new cases pinning all five ADR-0112 invariants. Smoke suite
still 67/67 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 14:59:27 -07:00
Shay
45272a7bb2 feat: ADR-0111 physics expert-demo promotion (second successful)
Second worked promotion exercising the ADR-0106 + ADR-0109 contract
on a domain distinct from mathematics_logic. No contract change.

Evidence:
- foundational_physics_ood: accuracy=1.0 (117/117 public, 39/39 holdout)
- inference_closure: all_pass_rate=1.0 (shared with math, distinct digest via domain_id)
- fabrication_control: refused=n, fabricated=0 across all classes (shared)

Signed claim digest: a104cad136f3219df05dc7ce6a78437c02f7b5827cd3cdce568db3acda6a43ed

Bridge landed: cases_plaintext.jsonl dev-mode fallback for
foundational_physics_ood (matches ADR-0105 convention; analogous to the
math/inference bridges in ADR-0110). One small file, not a contract change.

Tests:
- tests/test_adr_0111_physics_expert_demo.py — 4 invariants, 6 cases
- tests/test_adr_0110_math_expert_demo.py — relaxed "only math promoted"
  to "math stays promoted" (load-bearing for ADR-0110 is persistence)
- tests/test_capability_reports.py — physics row now expert-demo

Retires the "first promotion was math-specific" objection: the bridges
ADR-0110 landed were correctly scoped, and the contract holds across
two distinct domains using shared lane infrastructure with distinct
digests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 14:37:36 -07:00
Shay
5f149340cc
feat(contemplation): land ADR-0080 phase 1 (#119) 2026-05-22 13:10:03 -07:00
Shay
5cad0a4b72
feat(capability): ADR-0110 promote mathematics_logic to expert_demo (#118)
First worked expert-demo promotion under the ADR-0106 + ADR-0109
contract. Math is now the first domain at expert_demo=true.

Signed claim (docs/reviewers.yaml):
  domain_id: mathematics_logic
  evidence_lanes: [elementary_mathematics_ood, inference_closure,
                   fabrication_control]
  evidence_revision: adr-0110:reviewed:2026-05-22
  signed_by: shay-j
  claim_digest: 94d74781e103854230c1a71590e4df2287f5d2e87832f1c29b8ec4618853c04b

Evidence (all three lanes, public + holdout):
  elementary_mathematics_ood: accuracy=1.0 (117/117 public, 39/39 holdout)
  inference_closure: all_pass_rate=1.0, replay_determinism=1.0,
                     overall_pass=True (20 public, 12 holdout)
  fabrication_control: by-class refusals 3/3/3, fabricated=0
                       (9 public, 9 holdout)

Infrastructure bridges (not contract changes):
- cases_plaintext.jsonl dev-mode fallback files for
  elementary_mathematics_ood + inference_closure (ADR-0105 pattern)
- 9 new holdout cases for fabrication_control across all three
  refusal classes (phantom_endpoint / cross_pack_non_bridge /
  sibling_collapse)
- core/capability/reporting.py: _fetch_lane_split folds top-level
  by_class into metrics so refusal_shape sees a canonical layout

Tests:
- tests/test_adr_0110_math_expert_demo.py: 4 invariant tests
  (math_expert_demo_holds, signed_claim_present, replay_digest_
  byte_equality, other_domains_unaffected)
- tests/test_adr_0107_deferral.py retired (deferral resolved)
- tests/test_expert_demo_contract.py: production-ledger test
  rewritten as 'every promoted domain has signed claim' (load-
  bearing invariant preserved)
- tests/test_capability_reports.py: math row asserted at
  expert-demo (was reasoning-capable)

Ledger state:
  systems_software: reasoning-capable
  mathematics_logic: EXPERT-DEMO   <- new
  physics: reasoning-capable
  hebrew_greek_textual_reasoning: reasoning-capable
  philosophy_theology: reasoning-capable

README updated. ADR-0107 referenced as resolved by this ADR.
CLAIMS.md regenerated. ADR-0106 / ADR-0109 contract unchanged.
2026-05-22 12:59:23 -07:00
Shay
360905db4d
fix(intent): route 'Actually X R Y' premises to CORRECTION (inference_closure) (#117)
Between 2026-05-17 and 2026-05-22 the inference_closure lane regressed
from all_pass_rate=1.0 to 0.4 on public. Root cause: the
_DECLARATIVE_RELATION_RE branch in generate/intent.py runs ahead of the
_RULES loop and swallowed sentences beginning with 'Actually' into the
subject phrase, routing them to VERIFICATION. The lane's premise emit
path is gated on CORRECTION intent, so PackMutationProposal records
stopped being emitted for any non-'is' relation (precedes / grounds /
causes / reveals). Only the four transitive_is cases passed because
'is' is not in the declarative-relation verb list.

Fix: _CORRECTION_CUE_PREFIX_RE guard. When the text begins with a
correction cue ('Actually', 'Incorrect, ', 'No, ', 'Correction'), the
declarative-match branch is skipped and the sentence falls through to
the _RULES CORRECTION rule. Plain declarative-relation assertions still
route to VERIFICATION unchanged.

Lane on 2026-05-22 post-fix:
  dev/v1:    all_pass_rate=1.0, overall_pass=True (5 cases)
  public/v1: all_pass_rate=1.0, overall_pass=True (20 cases)

- tests/test_correction_cue_prefix_routing.py pins both halves of the
  guard (10 new tests).
- evals/inference_closure/gaps.md documents the regression + fix in a
  new section, preserving the 2026-05-17 resolution narrative.
- evals/inference_closure/results/ now carries canonical v1_dev and
  v1_public reports (the lane had no checked-in results before; ADR-0110
  will reference these).

This unblocks the second of ADR-0107's two named blockers. ADR-0110
(math expert-demo re-attempt) now becomes feasible once the math
domain's three lanes have signed-and-digested evidence.
2026-05-22 12:33:56 -07:00
Shay
36053317be
feat(capability): implement ADR-0109 lane-shape-aware thresholds (#116)
Replaces the cognition-shape-uniform threshold dispatch in
core/capability/expert_demo.py with an explicit LANE_SHAPE_REGISTRY
mapping 8 ratified lane ids to 5 shapes:

  cognition           -> cognition_shape
  elementary_math_ood -> accuracy_shape
  foundational_physics_ood -> accuracy_shape
  symbolic_logic      -> symbolic_logic_shape
  hebrew_fluency      -> accuracy_shape
  koine_greek_fluency -> accuracy_shape
  inference_closure   -> inference_shape
  fabrication_control -> refusal_shape

Each shape has a documented threshold checker. Unknown lane ids
fail-closed with a named reason. ADR-0106 \xc2\xa71.1/\xc2\xa71.3/\xc2\xa71.4/\xc2\xa71.5
unchanged; only \xc2\xa71.2 (threshold rules) dispatches by shape.

tests/test_lane_shape_thresholds.py pins all four ADR-0109 invariants
plus dead-shape and threshold-value gates (13 new tests).
tests/test_expert_demo_contract.py fixtures updated to provide
shape-appropriate metrics (no semantic change to those tests; same
12 cases still pin the ADR-0106 contract).

ADR-0109 status: Proposed -> Accepted. README sequencing updated
(ADR-0110 now only blocked by inference_closure, not by metric-shape
amendment).

Ledger: all five domains remain reasoning-capable, expert_demo=false.
2026-05-22 12:11:58 -07:00
Shay
7cc2f7b422
feat(adr): ADR-0107 mathematics_logic expert-demo promotion deferred (#114)
The ADR-0106 contract correctly refused promotion. ADR-0107 records the
deferral and reserves two follow-up ADRs:

- ADR-0109 (lane-shape-aware threshold amendment): ADR-0106 \xc2\xa71.2
  prescribes cognition-pack-shape metrics uniformly, but math /
  physics / systems / hebrew-greek lanes carry native shapes
  (accuracy, passed_rate, all_pass_rate). Prerequisite for any future
  expert-demo promotion.
- ADR-0110 (math re-attempt): conditional on ADR-0109 landing and
  inference_closure substantively passing (currently all_pass_rate=0.4
  on public).

tests/test_adr_0107_deferral.py pins adr_0107_no_silent_promotion: math
stays at reasoning-capable, has no expert_demo_claims entry, and the
ledger row carries a named refusal reason.

No change to core/capability/expert_demo.py or reporting.py -- the
contract is honored, not amended. README sequencing updated to reflect
ADR-0107 acceptance and the new ADR-0109/0110 prerequisites.
2026-05-22 11:49:37 -07:00
Shay
0493808215
feat(capability): implement ADR-0106 expert-demo promotion contract (#113)
Closes ADR-0106 acceptance evidence:

- ExpertDemoClaim dataclass + additive expert_demo_claims block on
  ReviewerRegistry (schema_version stays at 1; backward-compatible).
- New core/capability/expert_demo.py with derive_evidence_digest,
  evaluate_expert_demo, collect_domain_lanes, materialise_lane_results.
- core/capability/reporting.py: replaces the cognition-lane-only
  predicate (previous lines 418-433) with a domain-aware,
  reviewer-signed gate; ledger rows now also carry
  expert_demo_reason for operator legibility. Reviewer registry is
  fail-closed: an unloadable registry yields zero claims, so a broken
  registry never silently grants expert_demo=true.
- tests/test_expert_demo_contract.py covers all three ADR-0106
  invariants: requires_signature, domain_aware, replay_byte_equality;
  plus threshold + production-ledger-untouched gates. 12 new tests.
- tests/test_reviewer_registry.py extended with TestExpertDemoClaimsSchema
  covering omitted block, valid parse, unknown signer rejection,
  malformed digest rejection, duplicate domain rejection. 5 new tests.
- README index row + table preface updated to note expert_demo is
  contract-gated. Frontier list trimmed (ADR-0106 has landed).
- ADR-0106 Status flipped Proposed -> Accepted.

No domain row's expert_demo field flips by this PR -- only the contract
changes. Promotion of any ratified domain requires a follow-up ADR
(ADR-0107 reserved for mathematics_logic) plus a signed claim.
2026-05-22 11:39:09 -07:00
Shay
257fd4503d
feat(evals): ADR-0105 — sealed holdout encryption via age (#108)
* feat(evals): add pyrage dependency

* feat(evals): add sealed holdout path resolution

* feat(evals): implement sealed holdout decryption

* feat(evals): add sealed holdout CLI

* test(evals): add sealed holdout encryption tests

* docs(decisions): add ADR-0105 sealed holdout encryption

* feat(evals): route holdout split through sealed decryptor

* docs(decisions): add ADR-0105 index entry

* chore: restore project description

* fix(evals): use pyrage Identity.from_str and pin curriculum SHA

- holdout_runner: pyrage exposes Identity.from_str, not from_file; parse
  identity file by line and pass list[Identity] into decrypt(). Restores
  PR 108's sealed-holdout test suite to green.
- verify_lane_shas: realign curriculum_loop_closure pin with the actual
  deterministic runner output (carryover from PR 107).
2026-05-22 10:09:43 -07:00
Shay
f7680e96ea
feat(teaching): ADR-0104 — curriculum-sourced teaching proposals (#107)
* feat(teaching): add curriculum-sourced proposal builder

* test(teaching): cover curriculum proposal construction

* test(evals): add curriculum loop closure contract

* test(evals): add curriculum loop closure runner

* test(evals): add canonical curriculum loop closure report

* ci(lanes): pin curriculum loop closure lane

* docs(adr): add ADR-0104 curriculum sourced proposals

* docs(adr): register ADR-0104 and seven pinned lanes

* docs(teaching): mark curriculum source activation

* fix(ci): pin curriculum_loop_closure SHA to runner output

* fix(ci): register curriculum_loop_closure in CLAIMS.md generator
2026-05-22 10:05:14 -07:00
Shay
1395ec1354
feat(packs): ADR-0103 — attach hebrew_fluency + koine_greek_fluency lanes to ADR-0102 (#106)
* feat(evals): add Hebrew fluency holdout cases

* feat(evals): add Koine Greek fluency holdout cases

* feat(packs): attach fluency lanes to he_core_cognition_v1

* feat(packs): attach fluency lanes to he_logos_micro_v1

* feat(packs): attach fluency lanes to grc_logos_cognition_v1

* feat(packs): ADR-0103 fluency lane attachment

* test(packs): expect ADR-0103 fluency lanes on Hebrew Greek contracts

* docs(evals): add Hebrew fluency holdout split note

* docs(evals): add Koine Greek fluency holdout split note

* docs(evals): note Hebrew holdout attachment

* docs(evals): note Koine Greek holdout attachment

* docs: add ADR 0103 placeholder

* docs(adr): expand ADR-0103 fluency lane attachment

* docs: index ADR-0103 and refresh frontier
2026-05-22 09:43:46 -07:00
Shay
60da4f0cd0 feat(claims): auto-generate CLAIMS.md from ledger + pinned lane SHAs
CLAIMS.md is now mechanically derived from two ground-truth sources:
- core.capability.ledger_report (Tier 1: ratified domains)
- scripts/verify_lane_shas.PINNED_SHAS (Tier 2: pinned lane reports)

The generator is deterministic and gated by
tests/test_claims_md_is_current.py + the lane-shas CI workflow's new
'verify CLAIMS.md is current' step. Drift between in-tree state and
the published claims fails CI before merge.

Tier 1 (5 ratified domains) and Tier 2 (6 pinned lanes) cover every
ADR-0092..0102 invariant currently CI-pinned.
2026-05-21 21:02:36 -07:00
Shay
a8c12670ec fix(capability): correct discourse_planner flag catalog + commit-independent public_demo pin
Two pre-existing latent issues fixed:

1. discourse_planner flag catalog drift (test_flag_report failure)

   On 2026-05-21 the discourse_planner default was flipped to True
   after byte-equality verification (per inline comment in
   core/config.py:130-138), but the capability flag catalog at
   core/capability/reporting.py was not updated — it still claimed
   "flag_shipped_default_off". The test
   test_flag_report_tracks_default_off_flags_without_enabling_them
   correctly caught the inconsistency; it had been failing across
   every commit since ADR-0092 first ran the suite.

   Fix:
   - New "flag_shipped_default_on" state in _FLAG_CATALOG, added
     to flag_report() grouped output
   - discourse_planner moved from default_off → default_on
   - Test renamed to test_flag_report_classification_matches_actual_defaults,
     enforces BOTH directions of the contract (catalog claim must
     match DEFAULT_CONFIG value)
   - New test test_flag_catalog_state_is_consistent_with_default_config
     cross-checks every catalog entry against DEFAULT_CONFIG;
     catches future drift before it lands

2. public_demo lane SHA shifted every commit

   Each commit advances the showcase's generated_at_revision field
   (git HEAD SHA). _strip_volatile in the lane runner was stripping
   wall-clock and per-run paths but NOT generated_at_revision, so
   the byte-equality case's details.sha256 changed with every commit
   even when underlying demos produced identical content. That made
   the pin a "did this run today" check rather than a "did the code
   produce the right artifact" check — exactly the failure mode
   the verifier was supposed to prevent.

   Fix:
   - Add generated_at_revision to _VOLATILE_KEYS in the public_demo
     runner. Lane's invariant is "same code → same SHA," not
     "same HEAD → same SHA"; HEAD belongs in the showcase output
     (operators need it) but not in the lane's equality projection.
   - Pin refreshed once to capture the now-commit-independent SHA;
     subsequent commits won't shift it unless underlying demo content
     actually changes.

After fix:
- Capability tests: 6/6 passing (was 4/5 with discourse_planner failing)
- Lane SHAs: 6/6 match pinned values; public_demo pin will now survive
  routine code changes
- Smoke 67/67, cognition eval byte-identical 100/100/100/100

This is the single known pre-existing test failure cleaned up.
2026-05-21 20:53:15 -07:00
Shay
b9a6f2ddb5 feat(packs): ADR-0100/0101/0102 — three sibling domain ratifications
Ratifies the remaining three sibling domains as reasoning-capable
under ADR-0091's Domain Pack Contract v1, using the template
ADR-0097 established for mathematics_logic. The capability ledger
now has four reasoning-capable rows backed by validated contracts.

ADR-0100 physics (en_physics_v1):
  domain_id: physics
  claimed_operators: causal, modal
  teaching_chains: [physics_chains_v1]
  eval_lanes: foundational_physics_ood, inference_closure,
    fabrication_control
  9/9 predicates pass

ADR-0101 systems_software (en_systems_software_v1):
  domain_id: systems_software
  claimed_operators: transitive, causal
  teaching_chains: [systems_software_chains_v1]
  eval_lanes: symbolic_logic, inference_closure, fabrication_control
  9/9 predicates pass

ADR-0102 hebrew_greek_textual_reasoning (FIRST MULTI-PACK ratification):
  domain_id: hebrew_greek_textual_reasoning
  claimed_operators: causal, contradiction
  teaching_chains: [hebrew_greek_textual_reasoning_chains_v1]
  eval_lanes: inference_closure, fabrication_control
    (universal lanes only — language-specific fluency lanes lack
    holdout splits; a separate ADR adds those when holdouts ship)
  packs: grc_logos_micro_v1, grc_logos_cognition_v1,
    he_logos_micro_v1, he_core_cognition_v1
  all four pack contracts identical (uniformity invariant pinned);
  all four 9/9 predicates pass
  pre-existing gap: hebrew/greek manifests lacked a provenance field
  entirely; ratification fills that uniformly across the four packs

44 new ratification tests in test_adr_0100_0102_sibling_ratifications.py:
- 6 parametrized 9-predicate validation tests (one per pack)
- 21 per-domain ledger status assertions (status, reasoning_capable,
  expert_demo gated, no_open_gaps, provenance points at correct ADR,
  operator_chain_coverage, intent_shapes minimum) — 7 cases × 3 domains
- 15 per-domain contract field shape assertions (teaching_chains,
  eval_lanes, splits coverage, axioms/rules null, primary reviewer) —
  5 cases × 3 domains
- 2 ADR-0102 multi-pack uniformity invariants (all four packs carry
  the contract; contracts identical across packs)

Capability ledger after ratification:
  systems_software           : reasoning-capable
  mathematics_logic          : reasoning-capable
  physics                    : reasoning-capable
  hebrew_greek_textual_reasoning : reasoning-capable
  philosophy_theology        : reasoning-capable (no contract; pre-existing)

Lane SHA pin update:
- public_demo pin refreshed (21751aaf.. → 71090323..) — the
  ratification adds new manifest fields (provenance,
  domain_contract_*) that surface in pack-related demo paths;
  intentional ADR-tracked change per the verifier doctrine

Smoke 67/67, packs 6/6, sibling ratifications 44/44, cognition eval
byte-identical 100/100/100/100; all 6 lanes match pinned SHAs:
  reviewer_registry            681a2aab..
  miner_loop_closure           9f071733..
  domain_contract_validation   f9c06cde..
  fabrication_control_summary  01e1b6b7..
  demo_composition             27d83824..
  public_demo                  71090323..
2026-05-21 20:25:48 -07:00
Shay
a21d31a95c ci(lanes): pin ADR-0092..0099 lane SHAs and wire GitHub Actions verifier
Six lanes (reviewer_registry, miner_loop_closure,
domain_contract_validation, fabrication_control_summary,
demo_composition, public_demo) now have CI-enforced SHA-256 pins.
A failing job means a lane's deterministic output changed without
an explicit ADR-tracked pin update.

- new scripts/verify_lane_shas.py: single source of truth
  - PINNED_SHAS dict mapping lane_id → 64-char hex SHA
  - LANE_SPECS tuple wiring each lane to its runner module + canonical
    report path
  - accepts_report_flag handles the fabrication_control runner's
    different arg shape (--lane-dir not --report)
  - verify_all() runs each lane in subprocess isolation (clean Python
    state per lane — relevant for adapters that cache pack loads at
    module import)
  - --update flag refreshes pins after intentional ADR-tracked changes;
    diff is the audit trail
  - --json flag emits machine-readable report
  - exits non-zero on any mismatch

- new .github/workflows/lane-shas.yml:
  - triggers on push to main and pull_request to main
  - concurrency group cancels in-progress runs on new commits
  - Python 3.11 + pip-cached deps + editable install
  - runs verify_lane_shas.py; emits JSON report on failure
  - 12-minute timeout (lanes take ~30s in practice)

- new tests/test_lane_sha_verifier.py: cheap local-pytest pinning
  - every LaneSpec has a corresponding PINNED_SHAS entry
  - no orphan pins without a LaneSpec
  - every pin is a 64-char hex SHA-256
  - every runner module path exists on disk
  - canonical report paths are under repo root
  - all six expected lanes (ADR-0092/0093/0095/0096/0098/0099) covered;
    ADR-0094 and ADR-0097 are schema/ratification only, intentionally
    excluded from EXPECTED_LANES
  - 6 tests run in <100ms — catches drift before CI

- evals/public_demo/results/v1_dev.json: refreshed to match the new
  pin (21751aaf..) — earlier pin was generated under slightly different
  runner argparse defaults; --update produced the canonical bytes

Local verifier: 6/6 lanes match pinned SHAs. Smoke 67/67. Lane SHAs:
  reviewer_registry            681a2aab..
  miner_loop_closure           9f071733..
  domain_contract_validation   f9c06cde..
  fabrication_control_summary  01e1b6b7..
  demo_composition             27d83824..
  public_demo                  21751aaf..
2026-05-21 19:59:37 -07:00
Shay
bfb54fb015 feat(demos): implement ADR-0099 — Public Showcase Demo
Single 30-second artifact composing four CORE invariants
(determinism, honest unknown, reviewed learning, multi-hop with
trace) by delegating to existing DemoCommand adapters. **No new
mechanism** — every claim is backed by an already-shipped,
separately-tested adapter. Closes the 8-ADR scale-up slate.

- new core/demos/learning_loop_adapter.py: LearningLoopDemo wraps
  ADR-0056 reviewed-teaching loop; _strip_volatile_paths drops
  transient temp-dir paths from raw before serialization so the
  adapter's report_sha256 is content-stable across runs
- new core/demos/showcase_adapters.py:
  - FabricationControlPublicDemo: re-runs ADR-0096 public split,
    produces 3 claims (refusal_recall_meets_threshold,
    fabrication_rate_below_threshold, trace_evidence_present)
  - MultiHopTraceDemo: runs 'Does light reveal truth?' with
    transitive_surface=True + composed_surface=True against
    cognition pack; surfaces a 3-hop walk light→truth→knowledge→
    evidence; produces 3 claims (grounded_answer, depth_two_or_more,
    walk_evidence_present)
- new core/demos/showcase.py: run_showcase() composes 4 scenes,
  emits showcase.json + per-scene artifacts; render_html() produces
  presentation-only static HTML with no JS injection vector;
  ShowcaseScene dataclass; MAX_RUNTIME_SECONDS=30 hard ceiling
  with DemoContractError if exceeded
- CLI: 'showcase' added to demo target choices; --output-dir flag
  added; cmd_demo dispatch branch writes showcase.json + showcase.html
- new evals/public_demo/ lane with 4 cases:
  - all_claims_supported (each scene + composite)
  - determinism_run_to_run_byte_equality (two runs identical after
    stripping volatile keys: total_runtime_ms, json_path,
    transient_corpus)
  - runtime_under_budget (≤30s)
  - pure_composition_no_new_mechanism (grep gate over showcase
    imports — must come from core/chat/generate/language_packs/
    teaching/evals or allowed stdlib only)
- lane is itself byte-identical across runs (sha256 5707db8efc6a..);
  runtime case omits exact runtime_ms (it varies near bucket
  boundaries) but still asserts ≤ budget
- 8 unit tests with module-scoped fixture (showcase runs once,
  ~13s total) covering payload shape, scene order, runtime budget,
  HTML render absence of <script>, and the pure-composition import
  gate independently of the lane
- ADR-0099 measured: total_runtime_ms ~12.8s, well under 30s budget
- smoke 67/67, cognition eval byte-identical 100/100/100/100;
  all 6 ADR-0092..0099 lanes byte-identical:
    reviewer_registry        681a2aab..
    miner_loop_closure       9f071733..
    domain_contract_validation f9c06cde..
    fabrication_control sum  01e1b6b7..
    demo_composition         27d83824..
    public_demo              5707db8e..
2026-05-21 19:44:48 -07:00
Shay
4f640af40d feat(demos): implement ADR-0098 — Demo Composition Contract
DemoCommand Protocol + thin adapters retrofit shipped tours to a
typed composition contract. Composability becomes a structural
property: the ADR-0099 showcase will consume DemoResult through one
stable type rather than special-casing each tour. No demo behavior
changes — adapters wrap underlying run_tour() entry points.

- new core/demos/ package:
  - contract.py: frozen Claim / DemoResult dataclasses, runtime-checkable
    DemoCommand Protocol, canonical_json() sanctioned serializer
    (sorted keys, 2-space indent, trailing newline), CLAIM_CONTRACT_VERSION
  - audit_tour_adapter.py: AuditTourDemo (5 claims from ADR-0042 scenes
    1-4: identity_pack_swaps_visible, safety_typed_refusal,
    ethics_opt_in_deployment_fires, ethics_default_silent,
    replay_byte_identical)
  - tour_adapters.py: shared pattern for register/anchor-lens/orthogonality
    tours; _extract_claims walks the dict tree for *_supported booleans
    and builds Claim objects in deterministic sorted order

- global-state-mutation detector (ADR-0098 invariant #2):
  capture_state() snapshots a load-bearing subset of process state
  (CORE_* env vars + module identities for chat.telemetry,
  chat.runtime, language_packs.compiler);
  verify_no_global_state_mutation() ignores None→id transitions
  (benign lazy import) and only flags env-var changes or module
  identity rebindings

- new evals/demo_composition/ lane (ADR-0098 invariant proving):
  - 6 cases asserting byte-equality + no-state-mutation across the
    three fast adapters (audit-tour, register-tour, orthogonality-tour)
  - composition_read_only: confirms two adapter results compose into
    a composite claim set without mutating either
  - stateful_fixture_rejected: negative control — a deliberately
    stateful adapter MUST trigger divergence detection
  - anchor-lens-tour adapter is exercised by tests, not the lane,
    to keep wall time bounded
  - byte-identical across runs (sha256 27d838241bf3..)

- 26 unit tests covering Claim/DemoResult validation, canonical_json
  determinism, state-mutation detector (including the lazy-import
  benign case), Protocol conformance (isinstance check + claim
  contract version) for all four adapters, seed-rejection per
  adapter (all current adapters are fully deterministic), and an
  audit-tour integration smoke verifying 5 claims + byte-equality +
  no state mutation across two consecutive runs

- smoke 67/67, cognition eval byte-identical 100/100/100/100, all
  five lanes byte-identical (reviewer_registry 681a2aab..,
  miner_loop_closure 9f071733.., domain_contract_validation f9c06cde..,
  fabrication_control summary 01e1b6b7.., demo_composition 27d83824..)
2026-05-21 19:02:29 -07:00
Shay
0390491c93 feat(packs): implement ADR-0097 — Mathematics-Logic Reasoning-Capable
First concrete domain claim under ADR-0091's Domain Pack Contract v1.
en_mathematics_logic_v1 is now formally ratified as reasoning-capable
in the capability ledger: 9/9 ADR-0091 predicates pass.

ADR-0097 §"No code changes outside pack artifacts and corpus" relaxed
to include two latent bug fixes that ADR-0093's predicate enforcement
just exposed:

1. language_packs/schema.py: LanguageRole enum widened to include
   DOMAIN_SEED. Three in-tree packs (en_mathematics_logic_v1,
   en_physics_v1, en_systems_software_v1) have declared role="domain_seed"
   since landing but the enum was never updated; load_pack() always
   raised on them. ADR-0093's P1 predicate exposed the mismatch.

2. core/capability/domain_contract_predicates.py: P2 (gloss checksum)
   was reading manifest["checksums"]["glosses_sha256"]; the canonical
   in-tree location is manifest["glosses_checksum"] (top-level). Fixed
   to prefer the canonical key and fall back to the nested form for
   forward compatibility.

ADR-0097 manifest additions to en_mathematics_logic_v1:
- domain_contract_version: 1
- domain_id: "mathematics_logic"
- axioms: null  (rules in v1 — pack proves reasoning via chain
  composition, not declarative axioms)
- rules: null
- teaching_chains: ["mathematics_logic_chains_v1"]
- eval_lanes: three lanes with dev/public/holdout (elementary_mathematics_ood,
  inference_closure, fabrication_control)
- reviewers: ["shay-j"] (resolved via ADR-0092 registry)
- known_gaps: [] (all math/logic gaps in docs/gaps.md were [x])
- provenance: "adr-0097:reviewed:2026-05-21"

Verified evidence:
- core capability domain-contract --pack-id en_mathematics_logic_v1
  → all_passed=True (P1-P9 all pass)
- core capability ledger → mathematics_logic row shows
  status=reasoning-capable, predicates.reasoning_capable=True,
  predicates.expert_demo=False, open_gaps=[],
  operator_chain_coverage all ready=True (8 chains each),
  intent_shapes_present=5
- 14 ADR-0097 invariant tests in
  test_adr_0097_mathematics_logic_ratification.py pin
  status/provenance/expert-demo-gate/contract shape

Two pre-existing tests updated for the new CLI default
(predicate-running, non-zero on missing contract):
- test_capability_domain_contract_json_absent_contract_is_noop now
  uses --structural-only to assert legacy parse-only shape
- test_cli_returns_nonzero_on_missing_contract switched its fixture
  pack from en_mathematics_logic_v1 (now has a contract) to
  en_core_cognition_v1 (no contract)

The pre-existing test_flag_report_tracks_default_off_flags failure
(discourse_planner flag default mismatch, seen since ADR-0092) is
unchanged and unrelated.

Smoke 67/67, packs 6/6, capability tests 49/50, cognition eval
byte-identical 100/100/100/100; lanes byte-identical:
reviewer_registry 6/6, miner_loop_closure 6/6,
domain_contract_validation 9/9, fabrication_control dev 12/12 +
public 9/9.
2026-05-21 18:51:58 -07:00
Shay
d7713b07b1 feat(evals): implement ADR-0096 — Fabrication-Control Eval Lane
First negative-control measure. Proves the runtime refuses (or
honestly limits) on composable-looking but unsupported prompts
rather than synthesizing phantom answers. Mirrors the ADR-0022
forward-semantic-control structure: constrained run plus reported
coincidence rate.

- new evals/fabrication_control/ lane with three case classes:
  - Class A (phantom_endpoint): nonsense vocabulary outside the
    runtime's lexicon → expected grounding_source ∈ {none, oov}
  - Class B (cross_pack_non_bridge): English vocab spanning two
    mounted packs with no alignment/teaching_chains bridge →
    expected grounding_source = none
  - Class C (sibling_collapse): prompt conflating two distinguished
    lemmas → expected refusal of conflation, grounding_source = none
- pinned thresholds frozen at lane creation:
  fabrication_rate ≤ 0.01, refusal_recall ≥ 0.95,
  trace_evidence_present == 1.00,
  grounding_source_matches_expected == 1.00
- three-set discipline per docs/capability_roadmap.md Rule 1:
  cases/dev.jsonl (12 cases, 4/class), cases/public.jsonl (9 cases),
  cases/holdout.jsonl (empty — reserved for first version cut)
- runner.py drives each case through ChatRuntime.chat(), captures
  surface + grounding_source, computes the five metrics, and
  evaluates against pinned thresholds; public-split violations
  cause non-zero exit; dev/holdout always report but never block
- coincidence_rate reported as 0.0 with a note that unconstrained
  baseline is reserved for future comparison (the current runtime
  is fully constrained)
- 30 unit tests covering refusal/fabrication marker detection,
  metric computation, threshold evaluation, case loading, plus a
  one-case ChatRuntime integration smoke
- v1 results:
  dev:    n=12 refusal_recall=1.0 fabrication_rate=0.0 PASSED
  public: n=9  refusal_recall=1.0 fabrication_rate=0.0 PASSED
- byte-identical across runs (dev sha256=d6757e0e3f96..,
  public sha256=9b502878fcb7.., summary sha256=01e1b6b71114..)
- smoke 67/67, teaching 17/17, cognition 120/121 (pre-existing skip);
  cognition eval byte-identical 100/100/100/100
2026-05-21 18:44:25 -07:00
Shay
7784c39f9f feat(capability): implement ADR-0093 — Domain Pack Contract v1 wired in
Promotes ADR-0091 from proposed-but-unenforced to enforced. The CLI
command core capability domain-contract now runs the nine ADR-0091
predicates plus eval-lane artifact resolution; legacy structural-only
output remains available via --structural-only.

- new core/capability/domain_contract_predicates.py:
  evaluate_domain_contract(pack_id, *, data_root, chain_inventory,
  reviewer_registry) → DomainContractPredicateReport
- predicates wired:
  P1 manifest/checksum valid (via language_packs.compiler.load_pack)
  P2 gloss checksum (gloss-bearing packs only; otherwise vacuously pass)
  P3 domain_id ∈ DOMAIN_PACKS
  P4 teaching_chains entries ∈ TEACHING_CORPORA ∪ DOMAIN_CAPABILITY_CORPORA
  P5 ≥ 8 reviewed chains per claimed operator family from chain_report
  P6 ≥ 3 populated intent shapes per domain
  P7 every eval_lanes entry covers dev/public/holdout
  P8 reviewers resolve via ADR-0092 registry (consults can_review with
     scope='pack' and domain_id from contract)
  P9 known_gaps reference docs/gaps.md entries marked closed [x]
- _parse_gap_states reads docs/gaps.md format (- [x] / - [ ]) → {gap_id: closed?}
- _resolve_eval_lane_artifacts walks declared eval_lanes and surfaces
  per-split report path + SHA-256 (ADR-0093 item 4)
- CLI: cmd_capability_domain_contract now exits non-zero on any
  predicate failure; --structural-only preserves legacy behavior
- core.capability package re-exports new symbols (PredicateResult,
  DomainContractPredicateReport, evaluate_domain_contract)
- 24 unit tests covering contract presence/absence, each predicate
  positive + negative, gap parser, eval lane artifact surfacing,
  CLI default + structural-only paths, and determinism
- new evals/domain_contract_validation/ lane: 9 cases (positive +
  one negative per semantic predicate P3-P9 + determinism) passing
  9/9 byte-identical across runs (sha256 f9c06cde…)
- smoke 67/67, teaching 17/17, cognition 120/121 (pre-existing skip),
  ADR-0092..0095 tests 101/101; cognition eval byte-identical
  100/100/100/100
2026-05-21 18:33:23 -07:00
Shay
7dc7e9d5eb feat(teaching): implement ADR-0095 — Miner-Sourced Teaching Proposals
Closes the Phase-5 contemplation loop in code. Articulation-quality,
contradiction-detection, and frontier-compare miners (already shipping)
now have a route to file PackMutationProposal candidates that traverse
the single reviewed teaching path. Construction-only; never promotes
to coherent.

- new teaching/from_miner.py: from_finding() / from_findings() turn
  ContemplationFinding records (kind=PACK_MUTATION_CANDIDATE) into
  PackMutationProposal candidates with source.kind="miner",
  source.source_id=<miner_id>, status=SPECULATIVE
- proposal_id = SHA-256(canonical(miner_id, finding, revision))[:16]
  — same inputs → byte-identical proposal_id; different miner_id or
  revision → different id
- identity-pack defense AT CONSTRUCTION: reuses teaching.review.
  _is_identity_override() against finding.subject AND
  finding.proposed_action; miner-sourced identity-override attempts
  never reach the proposal log
- pluggable ReplayEquivalenceChecker Protocol with ReplayEquivalenceResult;
  NoOpReplayChecker default explicitly notes "deferred to production
  checker"; production checker integration is downstream of this ADR
- from_findings() batch path collects identity-override and
  replay-equivalence rejections in a typed rejection log rather than
  raising, so a mixed batch can proceed with audit evidence
- serialize_proposal_emitted_event() emits ADR-0040-compliant redacted
  telemetry shape: type, proposal_id, source.serialize(),
  epistemic_status only (no raw subject/correction_text)
- 22 unit tests covering positive construction, identity defense in
  subject+proposed_action, malformed input, determinism (same inputs,
  different revision, different miner_id, batch stream), replay
  pre-gate (single + batch), telemetry redaction, and the structural
  grep gate enforcing miner_proposal_single_review_path (only
  teaching/review.py and teaching/store.py may promote to COHERENT)
- new evals/miner_loop_closure/ lane: 6 case classes (positive_basic,
  identity_override_subject, identity_override_action,
  replay_equivalence_failed, wrong_finding_kind, determinism) passing
  6/6 with byte-identical SHA-256 across runs
- smoke 67/67, teaching 17/17, cognition 120/121 (1 pre-existing skip);
  cognition eval byte-identical 100/100/100/100
2026-05-21 18:18:51 -07:00
Shay
b24796386e feat(teaching): implement ADR-0094 — Proposal Source Provenance
Sealed ProposalSource type widening TeachingChainProposal and
PackMutationProposal schemas with typed (kind, source_id,
emitted_at_revision) provenance. Schema-only widening; no runtime
behavior changes. Unblocks ADR-0095 miner-sourced proposals.

- new teaching/source.py: frozen ProposalSource dataclass with sealed
  ProposalKind Literal["operator","miner","curriculum"], runtime
  invariants (operator → empty source_id; miner/curriculum → non-empty),
  serialize() ("operator" / "miner:<id>" / "curriculum:<id>"),
  as_dict/from_dict round-trip, ProposalSource.operator() helper
- TeachingChainProposal.source field added (proposals.py)
- PackMutationProposal.source field added (store.py)
- build_proposal() accepts optional source kwarg; default uses
  _default_operator_source() pinned at cached git HEAD SHA
- ProposalLog.current_state() now strictly requires source on every
  created event; raises ProposalError with migration pointer if missing;
  validates via ProposalSource.from_dict so malformed payloads reject
- teaching/migrate_proposals_source_field.py: deterministic one-shot
  migration script using PRE_MIGRATION_SENTINEL ("pre-adr-0094-migration")
  as the emitted_at_revision so re-runs across commits produce identical
  bytes
- migration applied to live proposals.jsonl: 11 created events gained
  source field; 33 non-created events untouched; idempotent verified
- 29 unit tests in test_proposal_source.py covering construction,
  serialization, exhaustive-match pattern with assert_never,
  migration determinism (3 idempotence/cross-run tests), strict-parse
  rejection, live-log loads
- 2 test fixes in test_epistemic_invariants.py for new required source param
- smoke 67/67, teaching 17/17, cognition 120/121 (1 pre-existing skip),
  runtime 19/19; cognition eval byte-identical 100/100/100/100
2026-05-21 18:11:09 -07:00
Shay
afdd2ee413 feat(capability): implement ADR-0092 — Reviewer Registry v1
Closes the load-bearing gap blocking every reasoning-capable claim
under ADR-0091: docs/reviewers.yaml was previously `reviewers: []` and
unparsed. Now schema-validated at v1, with a bootstrap shay-j entry
self-sealed via provenance.

- new core.capability.reviewers module: frozen Reviewer/ReviewerRegistry
  dataclasses, strict load_reviewer_registry parser, ReviewerRegistryError
- enforces ADR-0092 schema rules: schema_version==1, no unknown
  top-level keys, no unknown reviewer fields, role∈{primary,domain},
  primary must claim ["*"], domain must NOT claim "*", review_scope
  subset of {pack,proposal,chain,eval}, no duplicate reviewer_ids
- can_review(reviewer_id, domain_id, scope) helper implements
  ADR-0092 rules 2-4 for downstream use by ADR-0093 validator
- docs/reviewers.yaml updated to v1 schema with shay-j bootstrap
- ledger_report() evidence_counts now exposes structured
  reviewer_registry status (valid, schema_version, reviewer_count,
  reviewer_ids, error) alongside the legacy reviewers_present bool
- new evals/reviewer_registry/ lane: 6 cases (2 positive + 4 negative)
  covering empty-registry, wrong-version, domain-wildcard rejection,
  and unknown-field rejection
- runner emits deterministic JSON report; two runs produce byte-identical
  output (sha256 verified)
- 26 unit tests in tests/test_reviewer_registry.py
- capability ledger test extended to assert new reviewer_registry block
- smoke suite green (67/67); lane passes 6/6

The pre-existing test_flag_report_tracks_default_off_flags failure is
unrelated (discourse_planner flag default) and not introduced here.
2026-05-21 18:01:24 -07:00
Shay
327047ce26 feat(contemplation): Phase 5 — articulation-quality miner closes the loop
Final phase of the articulation arc.  Consumes the per-turn
``PlanMetrics`` + ``ContemplationFinding`` streams produced by
Phases 3 + 4 and aggregates across many turns to emit
SPECULATIVE ``PACK_MUTATION_CANDIDATE`` findings that the operator
reviews via the existing proposal-review-ratify chain.

This is the doctrine-aligned answer to the user's question:

  "Should we... realize a way to score whether it should use what
  it produced towards memory confidence for future use?"

Yes — and it stays inside ADR-0080: read-only, SPECULATIVE-only,
deterministic, no parallel learning path, no autonomous memory
mutation.

What it adds
------------

* New module ``chat/articulation_telemetry.py``:
    - ``ArticulationObservation`` frozen dataclass — per-turn
      bundle of (turn_id, anchor_subject, prompt_hash,
      plan_substrate_hash, metrics, findings).
    - ``format_articulation_observation_jsonl(...)`` — deterministic
      sort-keys JSONL line.
    - ``load_articulation_observations(lines)`` — schema-tolerant
      loader; malformed lines drop without aborting.
    - ``ArticulationObservationSink`` protocol — structurally
      identical to ``TurnEventSink`` but distinct named type so
      consumers can subscribe to one stream without the other.

* New module ``core/contemplation/miners/articulation_quality.py``:
    - ``mine_articulation_observations(observations, paths)`` —
      pure deterministic aggregator with three v1 rules.
    - **recurring_predicate_monotony** — when the same
      (subject, predicate) pair is flagged WEAK_SURFACE in
      >= _MIN_RECURRENCE (default 3) observations, propose
      substrate diversification with non-dominant predicates.
    - **recurring_planner_gap** — when the same subject is
      flagged PLANNER_GAP >= _MIN_RECURRENCE times across modes,
      propose substrate expansion.
    - **low_average_predicate_diversity** — when mean
      ``predicate_diversity_ratio`` < 0.5 across >= _MIN_RECURRENCE
      observations on the same anchor subject, propose
      diversification.

* Runtime wiring (``chat/runtime.py``):
    - New ``ChatRuntime.attach_articulation_sink(sink)`` method.
      Mirrors ``attach_telemetry_sink`` pattern.
    - Emission point at the end of
      ``_maybe_apply_discourse_planner``: when contemplation
      enabled + sink attached + plan engaged, builds an
      ``ArticulationObservation`` and emits one JSONL line.
      Sink errors propagate (fail-fast, no swallowing).
    - Per-runtime ``_articulation_turn_counter`` increments on
      every emission; gives downstream consumers a stable
      sequence index.

Tests
-----

* ``tests/test_articulation_quality_miner.py`` (11 tests):
    - Empty / sub-threshold cases yield no findings.
    - Each of the three rules fires at threshold.
    - Recurring_predicate_monotony separates by subject (no
      cross-subject merging).
    - Recurring_planner_gap collects distinct modes into a
      sorted comma-joined string.
    - Determinism — byte-equal finding IDs across two runs.
    - SPECULATIVE doctrine pin.
    - JSONL round-trip preserves observation identity.

* ``tests/test_articulation_quality_e2e.py`` (7 tests):
    - Sink-detached + contemplation-on → no emission.
    - Sink-attached + contemplation-off → no emission.
    - Engaged turn emits exactly one observation line.
    - BRIEF prompt emits nothing (fast-path).
    - **Full loop** — run compound prompt 3x → 3 observations →
      miner emits PACK_MUTATION_CANDIDATE with subject='truth',
      predicate='recurring_predicate_monotony', object='belongs_to'.
    - Full loop is deterministic (byte-equal finding IDs across
      two complete runs).
    - Every full-loop finding is SPECULATIVE.

Doctrine pins
-------------

| Claim                                | Pinned by                                                |
|--------------------------------------|----------------------------------------------------------|
| SPECULATIVE-only                     | test_all_findings_remain_speculative                     |
| Deterministic across runs            | test_miner_is_deterministic_across_runs                  |
| Full-loop determinism (e2e)          | test_full_loop_is_deterministic_byte_equal_finding_ids   |
| No autonomous mutation               | Sink is append-only; miner outputs ContemplationFinding  |
|                                      | objects only; nothing writes to packs/vault/teaching.    |
| Append-only stream                   | Sink protocol has emit(line: str) and nothing else.      |

Live demo (3 identical compound-prompt turns)
---------------------------------------------

Runtime emits 3 observations.  Offline miner aggregates and emits:

  [pack_mutation_candidate] subject='truth'
      predicate='recurring_predicate_monotony' object='belongs_to'
      evidence_refs: 3 observations
      proposed_action: "diversify substrate for 'truth': across 3
        observations the plan repeatedly over-concentrated on
        predicate 'belongs_to'. Candidates: add teaching chains
        rooted on 'truth' with relations OTHER than 'belongs_to'
        (grounds / requires / reveals / contrasts / precedes /
        follows) so the planner's RELATION selector has more
        variety to draw from."
      epistemic_status: speculative

The system observed its own articulation patterns across many
turns, identified the corpus expansion priority, and emitted a
specific reviewable proposal — without mutating anything.  The
operator decides whether to act on it via the existing review
chain.

Verification
------------

  pytest test_articulation_quality_miner.py       11/11 pass
  pytest test_articulation_quality_e2e.py          7/7 pass
  pytest test_plan_metrics*.py                    18/18 pass (Phase 4)
  pytest test_plan_contemplation*.py              17/17 pass (Phase 3)
  pytest test_discourse_planner_*.py              99/99 pass
  pytest test_articulation_demo.py                 all claims supported
  pytest test_narrative_example_intents.py         pass
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

The articulation arc is complete.  Future work documented in
``docs/sessions/SESSION-2026-05-21-articulation-arc.md`` §8:
connective rotation, generalised pronoun selection, doctrine-gated
plan revision, Phase 2.5 mid-sentence reflection.  None blocking.
2026-05-21 10:55:39 -07:00
Shay
b07fb0413c feat(contemplation): Phase 4 — per-plan articulation telemetry metrics
Quantitative companion to Phase 3 (commit 664e081).  Where Phase 3
emits SPECULATIVE *findings* about plan quality, Phase 4 emits
typed *measurements* — pure-function projection of a
``DiscoursePlan`` into a ``PlanMetrics`` dataclass.

Why this matters
----------------

The discourse planner now produces multi-clause grounded
articulations (Phase 1), the renderer pronominalizes across
consecutive same-subject moves (Phase 2), and the contemplation
pre-flight emits qualitative concerns about plan shape (Phase 3).
What was missing was the *aggregable* layer: per-turn structured
numbers that downstream consumers can stream across many turns
to score quality patterns the per-turn observer cannot see.

Phase 4 lands that layer.  Phase 5 (offline contemplation miner)
becomes possible because there's now structured signal to mine.

What it measures
----------------

  Structure
    * move_count                      — total moves in plan
    * fact_bearing_count              — moves with fact != None
  Move-kind distribution
    * anchor_count / support_count / relation_count
      / transition_count / closure_count
  Diversity
    * unique_predicates               — distinct predicates across
                                        fact-bearing moves
    * unique_subjects                 — distinct subject lemmas
    * unique_sources                  — distinct FactSources
  Topic dynamics
    * topic_shift_count               — consecutive pairs where
                                        subject changed
    * pronominalization_opportunities — consecutive pairs where
                                        subject held (= Phase 2's
                                        anaphora trigger count)
  Derived ratios
    * predicate_diversity_ratio       — unique_predicates /
                                        fact_bearing_count
    * subject_focus_ratio             — pronominalizations /
                                        (pronominalizations +
                                         topic_shifts)

Every field is a deterministic pure function of the plan: same
plan in → byte-equal ``PlanMetrics.as_dict()`` out.  This is the
load-bearing claim that lets Phase 5 aggregate across turns
without "is this the same metric?" ambiguity.

Doctrine alignment
------------------

Per ADR-0080 contemplation discipline:
  * Read-only — metrics are pure projections of the plan; no
    mutation of plan, runtime state, or memory tiers.
  * No autonomous learning — metrics are observations, not
    learned policy.  Promotion to memory still flows through
    the existing proposal-review-ratify chain.
  * Deterministic replay — pinned by test_metrics_are_deterministic_
    and_byte_equal_as_dict plus the runtime-level
    test_metrics_byte_equal_across_runs.

Wiring
------

* New ``ChatRuntime.last_plan_metrics`` property — read-only
  ``PlanMetrics`` from the most recent turn where the planner
  engaged (and ``discourse_contemplation`` was on); ``None``
  otherwise.  Reset between turns alongside ``last_plan_findings``
  via the existing top-of-call reset block.

* Same opt-in flag as Phase 3 (``discourse_contemplation``).
  When True, the runtime computes both findings AND metrics in
  the same block; when False (default), both stay at empty/None.

Demo (config: discourse_contemplation=True)
-------------------------------------------

  "What is knowledge?"          → metrics: None  (BRIEF fast-path)
  "Tell me about memory."       → moves=3 fact_bearing=3
                                  kinds=A:1/S:1/R:1/T:0/C:0
                                  unique_predicates=3 subjects=1
                                  pronominalization_ops=2 shifts=0
                                  predicate_diversity=1.000
                                  subject_focus=1.000
  "What is truth, and why does
   it matter?"                  → moves=7 fact_bearing=6
                                  kinds=A:2/S:2/R:2/T:1/C:0
                                  unique_predicates=4 subjects=1
                                  pronominalization_ops=4 shifts=1
                                  predicate_diversity=0.667  ← Phase 3
                                                                WEAK_SURFACE
                                                                quantified
                                  subject_focus=0.800
                                  + 1 finding (weak_surface)

The compound-prompt numbers are particularly informative:
``predicate_diversity=0.667`` is the algebraic expression of the
Phase 3 ``WEAK_SURFACE`` rule — the rule fires precisely because
6 fact-bearing moves used only 4 distinct predicates.
``subject_focus=0.800`` quantifies that 80% of consecutive pairs
held the same subject — high topic stickiness that Phase 2's
reflective renderer leveraged into 4 ``it`` substitutions.

Tests
-----

* ``tests/test_plan_metrics.py`` — 10 unit tests pinning each
  field, derived ratios, bridge-move handling (``fact=None``
  resets the focus channel), and determinism via ``as_dict()``
  byte-equality.

* ``tests/test_plan_metrics_runtime.py`` — 8 end-to-end tests
  proving the runtime wiring: disabled by default, populated
  when enabled, BRIEF prompts yield None, no cross-turn leak,
  byte-equal across runs, parametrized co-population check
  alongside findings.

Verification
------------

  pytest tests/test_plan_metrics*.py              18/18 pass
  pytest tests/test_plan_contemplation*.py        17/17 pass (Phase 3)
  pytest tests/test_discourse_planner_*.py        99/99 pass
  pytest tests/test_articulation_demo.py          all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py             pass
  cognition eval OFF vs ON                        45/45 surface byte-equal
                                                  45/45 trace_hash byte-equal
                                                  4/4 aggregate metrics
                                                      identical
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

Phase 5 (logged, not built)
---------------------------

Offline contemplation miner that consumes ``last_plan_findings``
+ ``last_plan_metrics`` streams across many turns and emits
reviewable pack-mutation candidates.  Still SPECULATIVE;
review-gated; never auto-promoted to memory.  Now unblocked by
the structured metric surface Phase 4 lands.
2026-05-21 10:39:39 -07:00
Shay
664e08150c feat(contemplation): Phase 3 — live plan contemplation pre-flight
Wires deterministic, read-only contemplation OVER a completed
``DiscoursePlan`` BEFORE the renderer fires.  This is the
"reasoning at meaningful checkpoints" capability — the system
now inspects the global shape of its own articulation plan and
emits SPECULATIVE findings about quality issues the move-by-move
planner couldn't see locally.

Doctrine alignment (ADR-0080)
-----------------------------

* **Read-only** — never mutates the plan, packs, vault, teaching
  corpus, or runtime state.  Returns findings as a tuple; the
  runtime stores them on a read-only property.
* **SPECULATIVE-only** — every finding is stamped
  ``EpistemicStatus.SPECULATIVE`` by the schema's ``__post_init__``;
  the doctrine pin ``test_findings_always_speculative`` keeps that
  invariant visible.
* **Deterministic replay** — same plan → byte-identical findings
  (same ``substrate_hash``, same ``finding_id``).
* **No parallel learning path** — findings flow to a read-only
  observation surface (``runtime.last_plan_findings``).  Promotion
  to memory still goes through the existing proposal → review →
  ratify chain.  The offline contemplation miner (Phase 5 target)
  is what eventually consumes the findings and emits reviewable
  pack-mutation candidates.

v1 rules (``core/contemplation/plan_preflight.py``)
----------------------------------------------------

* ``PLANNER_GAP`` — non-BRIEF mode produced anchor-only depth.
  Signals the teaching/cross-pack substrate for that lemma is too
  thin for the planner to expand.

* ``WEAK_SURFACE`` — three or more moves share a predicate.
  Signals the rendered surface will read mechanical (e.g. three
  ``belongs_to`` clauses in a row).  Fires on today's compound
  prompt ``"What is truth, and why does it matter?"`` — the
  6-sentence plan uses ``belongs_to`` 3 times.

* ``COVERAGE_GAP`` — every move in a multi-move plan draws from
  a single ``FactSource``.  Signals one-sided substrate (e.g.
  pack-only with no teaching enrichment).

Runtime wiring
--------------

* New ``RuntimeConfig.discourse_contemplation: bool = False`` —
  opt-in for now.  Default off keeps the cognition eval byte-
  identical to Phase 2 (verified 45/45 surface + 45/45 trace_hash).
* New ``ChatRuntime.last_plan_findings`` property — read-only tuple
  of ``ContemplationFinding`` records from the most recent turn.
  Reset to ``()`` at the start of every plan-engagement call so
  findings never leak across turns.
* Contemplation runs AFTER the planner produces a multi-move plan
  and BEFORE the renderer fires; the plan itself is not modified.

Demo (config: discourse_contemplation=True)
-------------------------------------------

  "What is knowledge?"          → planner fast-path; no findings
  "Tell me about memory."       → 3 moves, distinct predicates;
                                  no findings (good!)
  "What is truth, and why does
   it matter?"                  → 6 moves, ``belongs_to`` x 3:
                                  [WEAK_SURFACE] subject='truth'
                                    predicate='predicate_repeats_in_plan'
                                    object='belongs_to'
                                  proposed action: diversify the
                                  relation inventory for 'truth'
                                  (grounds / requires / reveals /
                                  contrasts) so the planner has
                                  more variety to draw from.
  "Explain truth."              → 3 moves, distinct predicates;
                                  no findings

Tests
-----

* ``tests/test_plan_contemplation.py`` — 11 unit tests pinning
  each rule, empty/trivial plans, determinism, and the
  SPECULATIVE-only doctrine.

* ``tests/test_plan_contemplation_runtime.py`` — 6 end-to-end
  tests proving the runtime wiring: disabled by default,
  populated when enabled, reset across turns, deterministic
  across runs, all findings SPECULATIVE.

Verification
------------

  pytest tests/test_plan_contemplation*.py        17/17 pass
  pytest tests/test_discourse_planner_*.py        99/99 pass
  pytest tests/test_articulation_demo.py          all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py             pass
  cognition eval OFF vs ON                        45/45 surface byte-equal
                                                  45/45 trace_hash byte-equal
                                                  4/4 aggregate metrics
                                                      identical
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

Phases roadmap (logged in commit, not built today)
--------------------------------------------------

* Phase 4 — articulation telemetry enrichment.  Emit per-turn
  metrics (grounding_ratio, anaphora_engagement, plan_completeness,
  novelty, focus_consistency) to the existing telemetry sink so
  the offline miner has structured signal.

* Phase 5 — offline contemplation miner.  Extend
  ``core/contemplation`` with a miner that consumes
  ``last_plan_findings`` streams and emits reviewable
  pack-mutation / teaching-corpus expansion proposals.  Still
  SPECULATIVE; review-gated.
2026-05-21 10:30:22 -07:00
Shay
9dfb505f06 feat(discourse): Phase 2 — reflective rendering pronominalizes focus subject
The Phase 1 multi-clause renderer (commit 63ffd88) produces grounded
content but reads mechanically because the subject lemma repeats in
every clause:

  "Truth is what is true. Furthermore, truth belongs to cognition.truth.
   In turn, truth grounds knowledge. Truth belongs to epistemic.ground.
   Furthermore, truth belongs to logos.core. In turn, truth requires
   evidence."

This is the literal articulation gap that motivated Phase 2 —
"reasoning at meaningful checkpoints during sentence construction
in order to have a stronger idea of what has come prior and is
already done to help better inform the next move."  Between move
``i`` and move ``i+1`` the renderer now reflects on what subject
has just been established (the "focus") and renders the next clause
with a pronoun when the focus carries forward:

  "Truth is what is true. Furthermore, it belongs to cognition.truth.
   In turn, it grounds knowledge. It belongs to epistemic.ground.
   Furthermore, it belongs to logos.core. In turn, it requires
   evidence."

Rules
-----

* Track ``focus_subject`` across moves (the lemma most recently used
  as a fact subject).
* When the next move's ``fact.subject`` is byte-equal to the current
  focus → swap subject token to ``"it"``.
* When the next move's subject differs → preserve the explicit lemma
  AND update focus.  Topic shifts (TRANSITION moves; compound bridge
  TRANSITION) thus reset the pronominalization channel naturally.
* Sentence-initial position (no connective): capitalised ``"It"``.
* Mid-sentence (after connective + comma): lowercase ``"it"``.

Doctrine alignment
------------------

Pure deterministic transformation of the existing plan; no new
content introduced, no LLM, no stochastic sampling.  Same plan in →
same surface out, always.  trace_hash invariance holds because:

  * BRIEF-mode prompts short-circuit the planner before render
    (commit 63ffd88's fast path) and are unaffected.
  * Multi-move plans render to a deterministically-different string
    that compute_trace_hash already folds in via ``surface``.

Wiring
------

* New ``reflective: bool = False`` parameter on ``render_plan``
  (back-compat default — every existing call site and test pinning
  Phase 1 output continues to work).
* ``_clause_for`` gains optional ``prior_focus_subject`` arg used by
  the reflective path; unchanged default behaviour.
* Runtime hook ``chat.runtime._maybe_apply_discourse_planner``
  passes ``reflective=True`` so the default chat path benefits.

Tests
-----

New ``tests/test_discourse_planner_reflective.py``:

* ``test_reflective_replaces_repeated_subject_with_it``
* ``test_reflective_handles_three_consecutive_same_subject_moves``
* ``test_reflective_capitalises_sentence_initial_pronoun``
* ``test_reflective_resets_focus_on_topic_shift``
* ``test_reflective_off_preserves_phase1_output``
* ``test_reflective_default_is_off_for_back_compat``
* ``test_reflective_is_deterministic``
* ``test_reflective_single_move_byte_identical_to_non_reflective``
  (load-bearing — pins that the cognition eval stays byte-equal
  across the Phase 2 flip because every cognition case is single-
  move).

Verification
------------

  pytest tests/test_discourse_planner_*.py        99/99 pass
                                                  (91 existing + 8 new)
  pytest tests/test_articulation_demo.py          all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py             pass
  cognition eval OFF vs ON                        45/45 surface byte-equal
                                                  45/45 trace_hash byte-equal
                                                  4/4 aggregate metrics
                                                      identical
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

Live demo (default config):

  "What is knowledge?"  → unchanged (BRIEF, fast-path)
  "Tell me about
    memory."            → "Memory is what a person recalls.
                          Furthermore, it belongs to cognition.memory.
                          In turn, it requires recall."
  "What is truth, and
    why does it matter?"→ "Truth is what is true. Furthermore, it
                          belongs to cognition.truth. In turn, it
                          grounds knowledge. It belongs to
                          epistemic.ground. Furthermore, it belongs
                          to logos.core. In turn, it requires
                          evidence."
  "Explain truth."      → "Truth is what is true. Furthermore, it
                          belongs to cognition.truth. In turn, it
                          grounds knowledge."

Out of scope for this commit (future Phase 2 follow-ons):

* Connective rotation ("Furthermore" → "Also" → "In addition"
  to break the repetitive cascade).
* Cross-clause de-duplication (skip moves whose ``new`` lemmas
  were already introduced by an earlier move).
* Generalised pronoun selection beyond ``it`` (requires gender /
  number / animacy signals the pack lexicon doesn't carry today).
2026-05-21 10:16:12 -07:00
Shay
63ffd88595 feat(runtime): default discourse_planner=True + fast-path BRIEF short-circuit
Flips ``RuntimeConfig.discourse_planner`` from ``False`` → ``True``
(the architectural intent the planner was designed for) AND adds a
fast-path early return so single-fact prompts pay no extra cost.

Why the flip
------------

The discourse planner apparatus has been fully wired in the codebase
for some time (``generate.discourse_planner.plan_discourse`` /
``plan_compound_discourse`` / ``render_plan``,
``generate.grounding_accessors.grounding_bundle_for``,
``chat.runtime._maybe_apply_discourse_planner``) but gated off behind
this flag.  Investigation surfaced that:

  * **Cognition eval (45 cases) is byte-identical OFF vs ON** across
    both surface and trace_hash projections — the planner's
    downstream ``len(plan.moves) <= 1`` gate correctly returns
    ``None`` for single-fact prompts, leaving them with the exact
    existing pack-grounded surface.

  * **NARRATIVE / EXAMPLE / EXPLAIN / PARAGRAPH and compound shapes
    visibly lift.**  ``"Tell me about memory."`` goes from a one-
    fragment disclosure to a 3-sentence grounded discourse.
    ``"What is truth, and why does it matter?"`` — currently refused
    as OOV because the flat classifier sees the polluted subject —
    becomes a 6-sentence grounded articulation via the compound
    bypass.

  * **No quality regression on existing benches.**  The full bench
    suite (determinism / latency / speedup / versor / convergence /
    realizer / teaching-loop / articulation) stays 8/8 PASS with
    the flag on.

Why the fast-path
-----------------

Default-on uncovered a perf trap: the gate ran
``grounding_bundle_for(lemma)`` (pack + teaching + cross-pack queries)
AND ``plan_discourse(...)`` on EVERY turn, then discarded the
result when ``len(plan.moves) <= 1``.  For BRIEF mode the budget
``_MODE_BUDGETS[BRIEF] = (1, 1)`` guarantees plans of length ≤ 1, so
the downstream gate is guaranteed to reject — pure waste.  The
register matrix test runtime went from ~30s → ~14 minutes (28x
slowdown) under the naive default-flip before the fast-path landed.

The new short-circuit:

  if mode is BRIEF and not compound.is_compound():
      return None

skips the bundle query + plan run entirely for the common case.
Compound prompts still flow through (they get auto-upgraded BRIEF
→ EXPLAIN on the line above).  Empirical post-fast-path
measurement on a 45-case eval (workers=1):

  OFF: 23.31s  (1.93 turns/sec)
  ON : 17.74s  (2.54 turns/sec)
  slowdown : 0.76x  (flag-ON is actually 24% FASTER — the bundle
                     work the OFF path also touches downstream is
                     short-circuited cleanly when not needed)
  surface byte-equal: True
  trace_hash byte-equal: True

Test updates
------------

* ``test_discourse_planner_render.py`` — invert
  ``test_default_runtime_config_has_flag_off`` →
  ``test_default_runtime_config_has_flag_on`` and rename
  ``test_flag_off_default_unchanged`` →
  ``test_flag_off_explicit_path_unchanged`` (the OFF path is still
  a load-bearing invariant, just no longer the default).

* ``test_narrative_example_intents.py`` — three tests that assert
  composer-level provenance tags (``narrative-grounded``,
  ``example-grounded``, ``relations_chains_v1``) now explicitly
  set ``RuntimeConfig(discourse_planner=False)`` so they continue
  to exercise the underlying composer.  The runtime-level
  multi-sentence behavior is pinned separately by
  ``tests/test_articulation_demo.py``.

Verified
--------

  cognition eval (45 cases)               OFF ≡ ON byte-identical
  pytest tests/test_discourse_planner_*   132/132 pass
  pytest tests/test_articulation_demo.py  all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py     pass
  core test --suite smoke                 67/67 pass
  core test --suite runtime               19/19 pass
  core test --suite packs                  6/6 pass

Live demo (default config):
  "What is knowledge?"          → single sentence (BRIEF, fast-path)
  "Tell me about memory."       → 3 grounded sentences
  "What is truth, and why does
   it matter?"                  → 6 grounded sentences (was: OOV)
  "Explain truth."              → 3 grounded sentences
2026-05-21 10:06:49 -07:00
Shay
c945b9a045 fix(intent): widen CORRECTION to catch fully-spoken `that is/was ...` forms
Follow-on to the word-boundary fix (commit 0dd30b8).  After tightening
``\bno\b`` etc. with word boundaries, an audit surfaced a separate
pre-existing gap in the CORRECTION trigger: the contracted-only
``that'?s\s+(?:not|wrong)`` slot silently dropped every fully-spoken
copula form to UNKNOWN.

Concrete gap (every one previously UNKNOWN):

  "That is not right."        → UNKNOWN
  "That is wrong."            → UNKNOWN
  "That was wrong."           → UNKNOWN
  "That is incorrect."        → UNKNOWN
  "That is false."            → UNKNOWN
  "That was not right."       → UNKNOWN
  "that is mistaken."         → UNKNOWN
  "That was incorrect."       → UNKNOWN

Root cause: the slot ``that'?s\s+(?:not|wrong)`` matches only

    that's  /  thats

— ``'?s`` makes the apostrophe optional but the literal ``s`` is
mandatory.  ``that is`` (full word ``is``) and ``that was`` (full
word ``was``) had no path.  And the predicate alternation only
accepted ``not`` or ``wrong``; ``incorrect``, ``false``, and
``mistaken`` were also missing.

Fix: widen both slots in one pattern revision.

    Before:
      that'?s\s+(?:not|wrong)
    After:
      that(?:'?s|\s+(?:is|was))\s+(?:not|wrong|incorrect|false|mistaken)

The full pattern now reads:

    \b(?:no
       |that(?:'?s|\s+(?:is|was))\s+(?:not|wrong|incorrect|false|mistaken)
       |incorrect
       |actually
       |correction)\b

Boundary discipline holds: the outer ``\b...\b`` still prevents the
predicate alternation from eating into longer words.  Verified:

  "That is correct."          → UNKNOWN (right NOT in predicate set)
  "That is right."            → UNKNOWN (right NOT in predicate set)
  "That is true."             → UNKNOWN (true NOT in predicate set)
  "That works."               → UNKNOWN
  "That is interesting."      → UNKNOWN
  "That is falsifiable."      → UNKNOWN (``false`` + ``i`` is word→word
                                         so ``\b`` after ``false`` fails)
  "That was wrongly accused." → UNKNOWN (same logic for ``wrong``+``ly``)

Tests extended:
  * ``test_correction_canonical_forms_still_route`` — 8 new parametrize
    cases for the fully-spoken copula forms
  * ``test_correction_does_not_eat_no_prefixed_words`` — 9 new
    parametrize cases for the affirmative ``That is/was ...`` shape
    AND the boundary-trap cases ``falsifiable`` / ``wrongly accused``

Verified:
  pytest tests/test_intent_subject_extraction.py         33/33 pass
  full intent + register-diagnostic + proposition graph  77/77 pass
  core test --suite smoke                                67/67 pass
  core test --suite runtime                              19/19 pass
2026-05-21 08:36:33 -07:00
Shay
0dd30b86a7 fix(intent): anchor CORRECTION trigger with word boundaries
While investigating the adjacent RECALL classifier gap, a much
wider intent-classification bug surfaced: every prompt beginning
with a word that *starts with* the letters of any CORRECTION
trigger silently routed to CORRECTION with a mangled subject.

Concrete examples seen during diagnosis:

  "Now remember light."        → CORRECTION  subject="w remember light"
  "Nothing matters."           → CORRECTION  subject="thing matters"
  "Notice the truth."          → CORRECTION  subject="tice the truth"
  "Note that recall fires."    → CORRECTION  subject="te that recall fires"
  "Nominate a candidate."      → CORRECTION  subject="minate a candidate"
  "Norma is here."             → CORRECTION  subject="rma is here"
  "Notwithstanding ..."        → CORRECTION  subject="twithstanding ..."

Root cause: ``generate/intent.py`` ``_RULES`` line ~213 used the
pattern

    (?:no|that'?s\s+(?:not|wrong)|incorrect|actually|correction)

The alternation has ``no``, ``incorrect``, ``actually``, ``correction``
as bare substrings — no word boundary on either side.  Combined with
``re.match``'s start-of-string anchor, *any* prompt beginning with
``No``-, ``Incorrect``-, ``Actually``-, or ``Correction``-prefixed
text matched as CORRECTION; the regex's match span was then sliced
off the prompt to produce a subject like ``"w remember light"``
(from ``"Now remember light."``).

The same hazard threatens:

  * ``no``         → eats ``Now`` / ``Notice`` / ``Note`` / ``Nothing`` /
                     ``Nominate`` / ``Norma`` / ``Notwithstanding`` / ...
  * ``incorrect``  → would eat ``incorrectly``
  * ``actually``   → would eat ``actualization``
  * ``correction`` → would eat ``corrections``

Fix: add ``\b`` anchors on both sides of the alternation.

    \b(?:no|that'?s\s+(?:not|wrong)|incorrect|actually|correction)\b

``\b`` is zero-width, so ``re.match``'s start-of-string anchor still
holds; the left ``\b`` is a no-op at position 0.  The right ``\b``
forces the matched token to end on a word boundary — i.e., the next
character must be non-word (whitespace, punctuation, EOL) — so
``\bno\b`` matches ``"No."`` / ``"No way"`` / ``"No, ..."`` but NOT
``"Now"`` / ``"Nothing"`` / etc.

Verified 11/11 previously-misfiring prompts now correctly classify
as UNKNOWN, and 8/8 legitimate CORRECTION pragmas
(``"No."`` / ``"No way."`` / ``"Incorrect."`` / ``"Actually, ..."`` /
``"Correction: ..."`` / ``"That's wrong."`` / ``"No, that's wrong."`` /
``"no, knowledge is wrong."``) still route correctly.

Tests extended with two new parametrized blocks in
``tests/test_intent_subject_extraction.py``:

  * ``test_correction_canonical_forms_still_route`` — 8 cases pinning
    the legitimate CORRECTION patterns
  * ``test_correction_does_not_eat_no_prefixed_words`` — 10 cases
    pinning the boundary fix against regression

Verified:
  pytest tests/test_intent_subject_extraction.py        25/25 pass
  pytest tests/test_intent_proposition_graph.py        + others       60/60 pass
  core test --suite smoke                                            67/67 pass
  core test --suite runtime                                          19/19 pass

Out of scope: ``"That is not right."`` (a real CORRECTION pragma the
regex never caught because ``that'?s\s+`` requires literal ``s`` after
``that``; the colloquial ``that is`` form was always UNKNOWN). Separate
gap, unchanged here.
2026-05-21 08:29:16 -07:00
Shay
7ef4ef4546 fix(intent): widen RECALL trigger to accept `recall alongside remember`
The articulation breadth benchmark surfaced a RECALL intent gap:

  Before (bench output):
    RECALL    UNKNOWN    pack    Pack-resident tokens — pack-grounded
                                 (en_core_cognition_v1): recall ...

The probe prompt ``"Recall truth."`` classified as UNKNOWN and fell
through to the ADR-0086 pack-resident-token surface — a graceful
degradation, not a hard failure, but a real classifier gap.

Root cause: ``generate/intent.py`` ``_RULES`` line 213 only matched
the imperative ``remember``:

    (re.compile(r"remember\s+", re.IGNORECASE), IntentTag.RECALL)

The verb ``recall`` — every bit as natural an imperative — was
missing from the trigger pattern.  ``"Remember truth."`` correctly
routed to RECALL; ``"Recall truth."`` did not.

Fix: widen the alternation to ``(?:remember|recall)\s+``.  One-word
change; ``re.match`` anchoring at the start of the prompt means the
fix only catches the canonical imperative form, leaving downstream
contexts untouched:

  * ``Does memory require recall?``      → VERIFICATION (unchanged;
    earlier rule on the aux-verb pattern fires first)
  * ``What is recall?``                  → DEFINITION   (unchanged;
    ``what\s+is\s+`` fires first)
  * ``Why does recall exist?``           → CAUSE        (unchanged;
    ``why\s+`` fires first)
  * ``I recall.``                        → UNKNOWN      (unchanged;
    no trailing word after ``recall``, ``\s+`` doesn't match)
  * ``Please recall the truth.``         → UNKNOWN      (unchanged
    — symmetric with ``Please remember the truth.`` since rules use
    ``pattern.match`` not ``pattern.search``)

After (bench output):
    RECALL    RECALL    pack    Truth is what is true. pack-grounded
                                (en_core_cognition_v1).

The articulation bench probe now routes correctly and produces a
pack-grounded definition surface — the canonical RECALL output on
a pack-resident lemma.

Tests extended: ``tests/test_intent_subject_extraction.py::
test_recall_strips_articles`` is parametrized with four new
``Recall ...`` cases parallel to the existing ``Remember ...``
cases.  A regression that re-narrows the trigger pattern fails the
gate immediately.

Verified:
  * pytest tests/test_intent_subject_extraction.py            7/7 pass
  * pytest tests/test_register_firing_diagnostic.py           3/3 pass
  * core test --suite smoke                                  67/67 pass
  * core test --suite runtime                                19/19 pass
  * core bench --suite articulation  → RECALL ✓ pack-grounded
2026-05-21 08:26:08 -07:00
Shay
f6f8ee603f
feat(evals): per-intent register-firing diagnostic + CI gate + tests (#103)
Replaces the per-pack-aggregate diagnostic landed at 58ac780 with a
per-intent matrix decomposition authored by Codex on a parallel
worktree. Codex's design directly answers the original motivating
question — "which packs' marker pools don't fire on which intent
shapes" — that the aggregate version flattened.

What Codex's version adds over the prior aggregate version:

  * **Per (pack × intent × prompt) matrix** — cells decompose by
    IntentTag. The C_stance / DEFINITION collapse pattern surfaced
    in the widened tour is now directly visible as
    matrix[register]["DEFINITION"][*].opening_fired == False.

  * **Replayed-variant verification** — every cell records
    decorate_surface()'s opening/closing AND asserts the resulting
    variant_id matches the runtime's emitted register_variant_id
    byte-for-byte. Catches future drift between the replayed
    selection and live selection in a single field
    (variant_id_matches_runtime / all_replayed_variants_match_runtime).

  * **Representative-prompt classification gate** — the companion
    test confirms every prompt in REPRESENTATIVE_PROMPTS actually
    classifies to its declared IntentTag. If intent classification
    drifts, the corpus is invalidated immediately rather than
    silently producing meaningless diagnostic output.

  * **--fail-on-gap CI mode** — exits 1 when any non-empty marker
    bucket never fires across its representative-prompt slice.
    Convertible into a CI gate once the deliberate-silent vs
    accidental-silent distinction is curated.

  * **--register / --intent filters** + **--output PATH** — operator
    ergonomics for targeted debugging and report archival.

  * **3 pytest cases** — corpus integrity, subset-report shape,
    full main()/--output round-trip.

Path: Codex authored at scripts/diagnose_register_firing.py.
Relocated to evals/register_diagnostics/run_firing_diagnostic.py to
match the convention used by evals/register_tour/, anchor_lens_tour/,
orthogonality_tour/, learning_loop/ — measurement artifacts live
under evals/, not scripts/. Test import path adjusted accordingly.

The sys.path bootstrap _REPO_ROOT computation was updated from
.parent.parent to .parents[2] to account for the new path depth.

Verified:
  PYTHONPATH=. pytest tests/test_register_firing_diagnostic.py -v
    → 3 passed in 5.39s
  PYTHONPATH=. python -m evals.register_diagnostics.run_firing_diagnostic \
      --register convivial_v1 --intent DEFINITION --intent CAUSE
    → emits per-cell matrix with variant_id_matches_runtime=True
  PYTHONPATH=. python -m evals.register_diagnostics.run_firing_diagnostic \
      --register expert_v1 --intent DEFINITION --fail-on-gap
    → exit 0 (expert_v1's empty buckets have non_empty_size=0, so
      not a contract gap — that's correct: gap = non-empty bucket
      whose entries never fire)

Co-authored-by: Codex <noreply@openai.com>
2026-05-21 07:05:23 -07:00
Shay
483c66dc5f test(register): widen invariant matrix to all 100 ratified packs
PR #102 ratified 93 drafted register packs, bringing the catalog to
100 fully-sealed packs on disk. This widens
tests/test_cognition_eval_register_matrix.py::_RATIFIED_REGISTERS
from 7 to 100 so every projection-invariant assertion (trace_hash,
intent_correct, terms_captured, surface_contains_pass,
versor_closure, versor_condition, canonical surface, and aggregate
metrics) now runs against every ratified pack.

Verification on PR #102 head: 801 cells passed in 316.76s
  = 100 registers × 8 projections + 1 meta-test
  = full ADR-0072 invariant proven across the entire register axis
    on all 45 cognition cases.

The meta-test test_register_matrix_covers_every_ratified_pack
remains the structural co-evolution guard: any future register pack
ratification must widen both REGISTER_IDS in
scripts/ratify_register_packs.py AND _RATIFIED_REGISTERS here in
the same change, or CI fails fast.
2026-05-21 06:38:22 -07:00
Shay
cad8b39928
feat(packs/register): 93-pack catalog rollout — drafted → ratified (#102)
* feat(packs/register): materialise A_depth drafted registers

Lands 3 drafted depth registers, dominated by disclosure-domain count and structural compression/expansion knobs; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path. Also aligns the smoke contract assertion with the current pack-grounded unknown evidence split.

* feat(packs/register): materialise B_tone drafted registers

Lands 15 drafted tone registers, dominated by bounded affective opening and closing marker palettes; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise C_stance drafted registers

Lands 11 drafted stance registers, dominated by epistemic posture markers plus light deterministic depth clauses; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise D_posture drafted registers

Lands 10 drafted posture registers, dominated by role-shaped marker families for peer, mentor, scholar, practitioner, and related voices; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise E_domain drafted registers

Lands 11 drafted domain registers, dominated by academic, executive, technical, legal, scientific, and philosophical marker families with bounded known-key knobs; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise F_cultural drafted registers

Lands 12 drafted cultural registers, dominated by plainspoken, diplomatic, classic, contemporary, and lyrical marker palettes; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise G_affective drafted registers

Lands 10 drafted affective registers, dominated by cheerful, somber, grave, wry, gentle, and earnest marker families; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise H_functional drafted registers

Lands 10 drafted functional registers, dominated by documentary, instructional, persuasive, clarifying, comparing, and exemplifying marker families; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise I_composite drafted registers

Lands 11 drafted composite registers, dominated by combined knob and marker families for tutorial, interview, briefing, lecture, memo, story, elegy, epigram, and manifesto voices; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.
2026-05-21 06:37:38 -07:00
Shay
66db063f0b test(register): full 7-pack invariant matrix on cognition lane
Adds tests/test_cognition_eval_register_matrix.py — strict superset
of tests/test_register_invariant_grounding.py (which covered only 4
of the 7 ratified register packs).

Parametrizes over all seven ratified register packs
({default_neutral, terse, precise, convivial, pedagogical, formal,
socratic}_v1) and asserts byte-identity against the unregistered
baseline for every per-case projection the cognition eval reports:

  * trace_hash               (ADR-0072 truth-path-isolation)
  * intent_correct           (intent runs upstream of realizer)
  * terms_captured           (scored off canonical surface)
  * surface_contains_pass    (scored off canonical surface)
  * versor_closure           (truth-path field invariant)
  * versor_condition         (exact float, stronger than closure)
  * surface                  (CognitiveTurnResult.surface is the
                              pre-decoration canonical the trace
                              hash consumes; substantive transforms
                              live on turn_log[-1].surface)

Aggregate metrics on EvalReport are pinned identically: total,
intent_correct, terms_captured, terms_expected, surface_grounded,
versor_closures.

Meta-test test_register_matrix_covers_every_ratified_pack enforces
that _RATIFIED_REGISTERS in this file stays in lockstep with
scripts/ratify_register_packs.py::REGISTER_IDS — so the 93 drafted
register packs in packs/register/_catalog.json cannot ratify into
CI without each one passing the full invariant matrix.

Run: 57 cells (8 projections x 7 registers + 1 meta), 27.7s
sequential across 45 cognition cases per register.

Pre-existing smoke failure (test_chat_response_surface_uses_
articulation_plan in tests/test_runtime_config.py) is the ADR-0086
expected-string test on main; unrelated to this change.
2026-05-21 06:24:36 -07:00
Shay
79f1678923 feat: ADR-0086 + ADR-0087 + 100-register catalog — cognition lane closure
Three load-bearing pieces:

1. ADR-0086 — UNKNOWN-intent pack-resident token surface
   New deterministic composer `pack_grounded_unknown_surface` in
   chat/pack_grounding.py.  When intent classification returns UNKNOWN
   but the prompt contains pack-resident lemmas (via cross-pack
   resolver), surface those lemmas with their semantic_domains
   instead of falling to the bare _UNKNOWN_DOMAIN_SURFACE.  Wired
   into chat/runtime.py::_maybe_pack_grounded_surface as the
   last typed-intent branch before the OOV fallback.  Null-lift
   invariant pinned: fully-OOV prompts still emit the universal
   disclosure byte-identically.  Closes four cognition-eval term
   misses: unknown_logos_019 (public), unknown_evidence_042 (dev),
   unknown_spirit_041 + unknown_word_018 (holdout).  Side effect:
   evals/results/phase2_pack_measurements.json refusal_rate drops
   from 0.25 → 0.125 across all three identity packs (no longer
   refusing on these prompts).

2. ADR-0087 — PROCEDURE selector + trailing-clause subject echo
   Two coupled changes in chat/pack_grounding.py:
   (a) Numeric-determiner downrank in _extract_procedure_topic_lemma:
       tokens whose primary semantic_domain starts with
       "quantitative.numeric." are demoted; non-numeric resident
       candidates always win.  So "compare two terms" anchors on
       `compare` not `two`.
   (b) Trailing clause echoes the full normalized subject_text
       rather than just the selected lemma, so OOV head nouns like
       "terms" reach the surface even when only the procedure verb
       is pack-resident.  Closes procedure_compare_011.

3. 100-register catalog
   New packs/register/_catalog.json — canonical machine-readable
   spec for all 100 registers (7 currently-ratified + 93 drafted)
   organized into 9 voice groups (depth/tone/stance/posture/domain/
   cultural/affective/functional/composite).  Each entry is a
   complete production input — realizer_overrides, marker palettes
   (openings/transitions/closings), depth_preference, description,
   author_notes.  All realizer_overrides use only legal keys per
   scripts/ratify_register_packs.py::_KNOWN_OVERRIDE_KEYS.
   Companion packs/register/CATALOG.md documents the production
   loop: materialize → widen REGISTER_IDS → ratify → smoke.

Cognition-eval lifts (all three splits):
  public:  term_capture 91.7% → 100.0%  (+8.3pp)
  holdout: term_capture 83.3% → 100.0%  (+16.7pp)
  dev:     term_capture 78.6% → 100.0%  (+21.4pp)
  surface_groundedness: 100% preserved on all splits
  intent_accuracy / versor_closure: 100% preserved on all splits

Tests:
  tests/test_pack_grounded_unknown.py     — 14 tests (composer
    direct + runtime engagement + null-lift invariant)
  tests/test_adr_0087_procedure_selector.py — 12 tests (selector
    numeric downrank + trailing-clause echo + regression guard)
  Existing test suites unaffected — cognition lane 120 passed / 1
  skipped both before and after.  Full lane net −3 failures vs
  pristine main (39 → 36 — none introduced).
2026-05-21 00:08:12 -07:00
Shay
583aae42ef
feat(packs): ADR-0085 content style pass v2 — 3sg + plural agreement (+ closure infra) (#100)
Applies the ADR-0085 v2 brief's 16 fluency rows (Pattern A 3sg agreement on
relative-clause verbs + Pattern B plural after quantifier) plus 7 additional
"what a person {VERB}" rows surfaced in live chat probe (`Knowledge is what
a person know` → `knows`, similar for `memory`/`question`/`word`/`answer`/
`response`/`express`). 23 gloss edits total across 5 packs.

The brief had an internal conflict: it forbids atom edits but requires
closure-verifier 0/0, while ADR-0084's verifier enforces
`atoms == content_tokens(gloss)` exactly. Resolved by:

  1. Extending `scripts/verify_definitional_closure.py` and the integration
     test fixture (`mounted_lex_lemmas` + `production_pool` builders) to
     include lexicon `surface` forms in the resolution set — already the
     operational meaning of "a lemma in another mounted pack" since
     surfaces are canonical inflections of the same lemma.
  2. Adding 10 inflected `LexicalEntry` rows across cognition / meta /
     action / spatial lexicons (e.g. `surface=knows lemma=know`,
     `surface=parts lemma=part`) so morphology-shifted atoms resolve.

Live surface verification (sample 6 prompts):

  before                                          after
  "what a person know from truth and evidence" -> "...knows from..."
  "what a person recall"                       -> "...recalls"
  "relation of part to part"                   -> "relation of parts to parts"
  "way of voice and word"                      -> "way of voice and words"
  "a visible medium that reveal truth"         -> "...reveals truth"
  "what a cause make"                          -> "what a cause makes"

Verification (all gates from brief Phase 4):
  - closure verifier: 0 unresolved / 0 mismatches on all ADR-0084 packs
    (remaining domain-pack red is PR #97 follow-up — addressed by PR #99)
  - ADR-0084 integration test: 30/30
  - cognition eval: byte-identical to baseline
  - packs lane: 6/6
  - smoke lane: 67/67

Files touched: 5 gloss files (cognition / causation / meta / attitude /
spatial), 4 lexicon files (cognition / meta / action / spatial), 5 manifest
checksum refreshes (+ action), 1 verifier code change, 1 integration test
fixture extension, 1 deterministic-pack-entry-id test bump (085→091).
2026-05-20 23:12:28 -07:00
Shay
3d922a1532
Add chain-first capability ledger and domain seeds (#97) 2026-05-20 21:33:24 -07:00