Vault, language-packs, and runtime-packs audits complete. Findings:
- Ratifies INFERRED (14th epistemic state): derived from DECODED
primitives by ratified deterministic rule; composite never curated.
Grounded by language_packs composition rules (per/square/cubic unit
synthesis). Sits between DECODED and UNVERIFIED-POSSIBLE in the
epistemic progression.
- Ratifies normative clearance axis as orthogonal companion to the
epistemic axis. Safety/ethics verdicts are not epistemic states;
they answer a different question (normative compliance vs. truth-
value). Four clearance states: CLEARED, VIOLATED, UNASSESSABLE,
SUPPRESSED. Every proposition in ChatResponse/TurnEvent carries
both an epistemic_state and a normative_clearance tag.
- Closes open question 5 (identity/safety/ethics interaction):
identity grounds the epistemic axis; safety/ethics live on the
normative axis; they coexist without collapsing.
- Updates RecognitionOutcome shape with both axes.
- Marks all four subsystem audits complete in Framing 1 block;
documents vault implementation debt (_status_admits conflation)
and deferred candidate (COMPOSED_RECOGNITION).
- Records four Phase 2 implementation bugs in summary:
evidence.py empty-pairs silent-FALSIFIED, runner DECODED-
UNARTICULATED misclassification, domain_contract present=False
inconsistency, _status_admits FALSIFIED/SPECULATIVE conflation.
Taxonomy is now stable. Phase 2 (bug fixes) and Phase 3 (first-class
state tagging in ChatResponse/TurnEvent) are the remaining work.
Math-subsystem audit (40 decision points, 9 files) confirmed the 9-state
starter taxonomy was too coarse. Ratifies four new states surfaced as
EPISTEMIC_STATE_NEEDED gaps:
- EVIDENCED-INCOMPLETE: lift completed for a sub-span; proposition
structurally partial but not contradicted (grounded by orphan-rate
refusal in math parser)
- DECODED-UNARTICULATED: trace is replay-equal; surface realization
path broke — exposes a runner misclassification where RealizerError
on a verified trace is currently outcome="wrong"
- SCOPE_BOUNDARY: engine recognized the proposition type; refuses
because the type is outside the current capability envelope, not
because the lift failed
- COMPUTATIONALLY_BOUNDED: search budget exhausted before answers were
enumerated; distinct from AMBIGUOUS (no answers compared) and
UNDETERMINED (text likely has structure)
Also: updates primitives mapping table, marks Framing 1 audit complete
for math subsystem, closes the "too coarse" risk item (9 → 13 states),
and records the runner misclassification as a load-bearing correctness
issue for the next runner revision.
* feat(ADR-0141): multiply as CGA dilator versor (positive non-zero)
Adds `multiply(scale)` to `generate/math_versor_arithmetic.py` as the
standard CGA dilator for multiplicative scaling along e1, restricted to
`scale > 0`. All ten ADR-0141 assertion families pass.
Preliminary measurement confirmed:
N = n_o ∧ n_inf: component -1 at index 15 (blade (3,4) = e4∧e5)
N² = +1.0 (pure scalar) → closed-form D_s = cosh(α/2) + sinh(α/2)·N
n_o · n_inf = -1; n_o² = n_inf² = 0
Because N² = +1, the cosh/sinh expansion is exact in float64 and
D_s · ~D_s = cosh² − sinh² = 1 holds to machine epsilon.
The sandwich D_s·X·~D_s produces a null point with n_inf normalization
1/s. `decode_quantity` is updated to divide by that factor, recovering
value · s. For translator outputs (normalization = 1) the result is
identical to the previous direct e1 read; all 152 prior add/subtract
tests pass unchanged.
`embed_quantity` is updated to embed directly in float64, eliminating
float32 quantization error for values like 0.01 (float32(0.01) ≠ 0.01);
all prior test-case values were exactly representable in float32.
* docs(ADR-0141): add decision document for multiply-as-dilator spike
The ADR doc was drafted in a separate branch and not present when the
implementation worktree was created from origin/main. Adding it now so
the decision record lands on main with the implementation it specifies.
Content unchanged from the draft — same spec the implementation already
satisfies (10 assertion families, fixed test cases, falsification
discipline, deferred scope for negative / zero / divide / Rate).
No code or test changes in this commit.
Extends generate/math_versor_arithmetic.py with one new function:
def subtract(addend: float) -> np.ndarray:
return translator(-float(addend))
Single-line delegate to translator(); no new algebra.
Adds tests/test_arithmetic_subtract_and_group.py covering all nine
ADR-0140 acceptance families:
Families 1-6 (ADR-0139 families applied to subtract):
1. Embedding well-formedness — null cone preserved for subtract cases
2. Translator-of-negative well-formedness — versor_condition < 1e-6
3. Closure — sandwich result stays on null cone
4. Arithmetic correctness — decoded value == a − b within 1e-9
5. Replay determinism — byte-identical across runs
6. Composability — subtract(c) ∘ subtract(b) decodes to a − b − c
New group-property families (structural verification of ADR-0139 claim):
7. Inverse composition — T_{-b} * T_b = identity (max residual: 0.000e+00)
8. Round-trip closure — versor_apply(T_{-b}, versor_apply(T_b, X)) → (a, u)
9a. Sum composition — T_a * T_b = T_{a+b} (max residual: 0.000e+00)
9b. Commutativity — T_a * T_b byte-equals T_b * T_a (all 10 cases)
All 96 tests pass. Group residuals are exactly 0.0 in float64.
The additive subgroup of Cl(4,1) translators along e1 is abelian and
closed; ADR-0139's algebraic claim holds at the group level.
First step of the Engine A lift program (CLAUDE.md commits the project to a
single deterministic cognitive engine; Engine B / math pipeline was always
intentional scaffolding per math_solver.py:24). Proves the load-bearing
unknown: one arithmetic operation can be represented as a closed versor at
the required tolerance, with no new normalization and no weakened invariant.
Scope (frozen by ADR-0139):
- One operation: add
- Single-axis embedding: quantities on e1 axis
- No graph wiring, no pipeline integration, no GSM8K case routed
- Unit carried as caller metadata
Construction:
- embed_quantity(v, u) = embed_point([v, 0, 0]) (existing CGA primitive)
- translator(b) = 1 - 0.5 * (b*e1 * n_inf) (textbook CGA translator)
- decode_quantity(F, u) = (F[1], u) (e1 coordinate)
Measured values (all 11 fixed cases + composability):
a b vcond(T) |<R,R>| decode_err
0.0 0.0 0.000e+00 0.000e+00 0.000e+00
0.0 1.0 0.000e+00 0.000e+00 0.000e+00
1.0 0.0 0.000e+00 0.000e+00 0.000e+00
3.0 4.0 0.000e+00 0.000e+00 0.000e+00
7.0 -3.0 0.000e+00 0.000e+00 0.000e+00
0.25 0.75 0.000e+00 0.000e+00 0.000e+00
1.5 2.5 0.000e+00 0.000e+00 0.000e+00
-5.0 5.0 0.000e+00 0.000e+00 0.000e+00
-2.0 -3.0 0.000e+00 0.000e+00 0.000e+00
100.0 1.0 0.000e+00 0.000e+00 0.000e+00
1.0 100.0 0.000e+00 0.000e+00 0.000e+00
compose (2, 3, 5) → 10: |<R2,R2>| = 0.000e+00, decode_err = 0.000e+00
Every residual is exactly 0.0 in float64. The construction is algebraically
closed: T_t * reverse(T_t) = 1 - 0.25*B^2 where B = t*n_inf, and B^2 = 0
because (e14)^2 + (e15)^2 = -1 + 1 and cross-terms cancel. No machine-epsilon
drift accumulates because the relevant cancellation happens at the algebraic
level before float arithmetic.
ADR-0139 acceptance items 1-6 (one parametrized test family each):
1. Embedding well-formedness — test_family1_embedding_is_null (11 cases)
2. Translator well-formedness — test_family2_translator_unit_versor (11 cases)
3. Closure — test_family3_sandwich_preserves_null (11 cases)
4. Arithmetic correctness — test_family4_decode_matches_sum (11 cases)
5. Replay determinism — test_family5_replay_byte_identical (11 cases)
6. Composability — test_family6_two_translators_compose (1 case)
Total: 56 tests, all passing.
Lift program decision: proceeds. Follow-on ADRs (subtract, multiply, Rate,
compare, MathProblemGraph → PropositionGraph, pipeline integration, first
GSM8K case end-to-end through Engine A) are now justified by a concrete
algebraic foundation rather than design speculation.
Out of scope per ADR-0139:
- No modifications to algebra/, core/cognition/, chat/, math_solver.py,
math_verifier.py, math_realizer.py, math_candidate_parser.py
- No GSM8K runner changes
- No pack changes
- Engine B continues serving GSM8K unchanged; the 3/50 admission set is
preserved
CLI lanes intentionally not run — main has known test-rot orthogonal to
this PR. The 56 new tests are self-contained and the diff touches only
three new files.
A small typed linguistic layer (FractionOperand, ComparativeOperand,
QuantityReference, ReferenceTarget) replacing the closed-and-deferred
ADR-0137 with a shape-driven design. Justified by the rescan-v3 ledger
showing 7 cases under fraction_operand + compound_comparative that
share a common deep structure (value relative to literal or
prior-sentence anchor) with multiple surface shapes.
Key commitments:
- Three typed phrase structures handle the closed surface set
(frac/percent of X, N times as much/greater than X, frac more/less
than X), where X is either a sentence-internal literal or a
QuantityReference to a prior grounded quantity.
- Binding pass is minimal: sentence-order lookup of QuantityReference
against prior grounded candidates with unique-match gating. No
generic DeferredCandidate apparatus.
- Pinned English convention: "N times greater" = N+1 multiplier
(documented + tested). Other reading requires explicit revisit.
- Supersedes ADR-0137 in scope; the deferral note's reopen criterion
is met by this lighter, more specific design.
- S.1/S.2 short-circuits stay as joins of grounded candidates;
this ADR does not subsume them.
Forcing case for cross-sentence binding: gsm8k-0029 ("three times
greater than the cost of the mouse" + "the mouse cost $16"). Clean,
unambiguous, two-sentence binding.
Self-correction caught before push: gsm8k-0010 was initially framed
as the forcing case, but its expected dataset answer requires a
non-standard English parse of "1/4 more than X" (the natural reading
gives 17; the dataset says 9, requiring X/4 + 7). Per non-negotiable
#2 the binding pass MUST refuse on this; 0010 is reclassified from
"expected admission" to "negative probe." 0029 takes the forcing-case
slot.
Honest expected admission delta: +1 to +2 (0005, 0029 high
confidence; 0041, 0043 partial credit only).
In scope: in the same PR family, fix the issue gsm8k-0029 surfaces by
implementing the binding pass. Out of scope: inverse arithmetic
(0004), multi-term aggregate references (0036), pronoun anchoring,
self-referential cycles.
S.4 extends initial-state parsing with two closed subject-slot widenings:
- Indefinite-article: `A <noun> has N <unit>` (gsm8k-0046 sentence 1)
- Prepositional-prefix existential: `In a <place>, there are N <unit>...`
(gsm8k-0038 sentence 1)
Design choice: sibling regexes (_INITIAL_HAS_INDEF_RE,
_INITIAL_THERE_ARE_PREFIX_RE) rather than widening the global _ENTITY
pattern — preserves existing behavior across all other initial-state
extractors (cascade-safety).
Per the S.x corridor discipline: no new short-circuit; new candidates
flow through extract_initial_candidates and the existing graph machinery.
No solver/graph/verifier changes.
Honest delta:
- Direct admissions: 0 (admission set unchanged at {0014, 0018, 0042})
- Barrier shifts: +2 (gsm8k-0038: novel_initial_form → compound_comparative;
gsm8k-0046: novel_initial_form → fraction_operand)
- wrong == 0 on every lane
Bundled with this PR for ledger currency:
1. tests/test_rescan_v3_invariants.py refactored to read frozen on-disk
v3 artifacts only (no more re-running build_rescan against live
parser). The previous design tied a historical snapshot to live code
and broke the moment any new phase landed.
2. rescan_v4.py + refusal_rescan_v4.json + refusal_taxonomy_v4.json +
tests/test_rescan_v4_invariants.py — the current live snapshot.
Shifts: exactly 2 (0038, 0046). Same pattern as v3.
Sonnet wrote: S.4 parser/axis-lane/tests/ADR.
Opus wrote: rescan_v4.py + v3 test refactor + bundling.
Files:
- generate/math_candidate_parser.py (+142 lines)
- evals/math_capability_axes/S4_novel_initial_form/v1/ (20-case lane)
- tests/test_adr_0136_S4_novel_initial_form.py (40 tests)
- docs/decisions/ADR-0136.S.4-novel-initial-form.md
- evals/gsm8k_math/train_sample/v1/{rescan_v4.py, *_v4.json}
- tests/test_rescan_v4_invariants.py (8 tests)
- tests/test_rescan_v3_invariants.py (refactored to artifact-only)
Re-runs parse_and_solve on the 50-case GSM8K train sample on current
main (post-S.3) and compares to v2. Result: admitted=3/50 (unchanged),
wrong=0, exactly 1 barrier shifted v2→v3.
Shift: gsm8k-0010 (compound_statement → fraction_operand). S.3's
_INIT_MUTATION_RE resolves "Yun had 20 paperclips initially, but then
lost 12" to InitialPossession(Yun, 8, paperclips). First refusal moved
to sentence 2: "Marion has 1/4 more than what Yun currently has, plus
7" — needs fraction-operand + coreference-quantity + comparative-additive
arithmetic.
Top blockers (v3):
compound_statement 5 (was 6)
novel_initial_form 5 (unchanged)
fraction_operand 4 (was 3 — gsm8k-0010 moved here)
novel_initial_verb 4 (unchanged)
Artifacts:
- evals/gsm8k_math/train_sample/v1/rescan_v3.py
- evals/gsm8k_math/train_sample/v1/refusal_rescan_v3.json
- evals/gsm8k_math/train_sample/v1/refusal_taxonomy_v3.json
- docs/decisions/ADR-0136.S3-post-rescan.md
- tests/test_rescan_v3_invariants.py (7 tests; determinism + admission
set unchanged + exactly-one-shift + 0010-specific shift assertions)
PR #204 (ADR-0137 retrospective binding, rescoped to subsumption-only)
was drafted then closed without merging. The deferral reason is
structural: the DeferredCandidate/BindingProof apparatus is the right
shape for true retrospective binding (first-pass candidate has open
slots awaiting later evidence), but re-examination showed neither S.1
nor S.2 short-circuit fits that definition — both join two fully
grounded candidates. With no kind that has open slots in the v2
ledger, the machinery would degenerate to a join everywhere, which is
speculative infrastructure ahead of a forcing function (CLAUDE.md
warns against this pattern).
Update appended to the post-rescan notes so the deferral lives in the
repo, not just in a closed PR thread. Includes a reopen criterion:
ADR-0137 (or successor) may return when a case appears whose first-pass
candidate has genuinely open slots only later-sentence evidence can
close. Until then the unlock vehicle is S.x.
The S.1/S.2 short-circuits remain as tactical bridges; the canonical
runner staleness is better fixed in the runner than in a binding
layer.
Measurement-only branch. Re-runs parse_and_solve on all 50 GSM8K train-sample
cases against the current parser (post-S.1/S.2) and produces a barrier-shift
ledger comparing v1 taxonomy to current behavior.
Results: admitted=3/50 (0014, 0018, 0042), wrong=0, barrier_shifted=27/50.
Context-filler dominance collapsed from 23→3 cases; compound_statement (6)
and novel_initial_form (5) are now the largest buckets.
Subsumption directive pinned: ADR-0137 SHALL re-derive all short-circuit
admissions as (DeferredCandidate, evidence, BindingProof) triples.
All five G.x capability axes shipped (G.1–G.5); admission remained 0/50
throughout — expected, because the 50 sample cases fail at the statement
layer (rate verbs, implicit entities, multi-step ops), orthogonal to the
question-layer and initial-state-verb work the G.x axes targeted.
Decisions:
- Probe retired as a per-iteration gate (signal-to-noise ≈ 0 at 0/50)
- `admitted_wrong == 0` invariant preserved as a standing architectural
invariant, CI-pinned by existing test + per-axis regression guards
- Re-activation criterion named: statement-layer expansion that
plausibly admits ≥1 new case (Δadmission_rate ≥ 0.02)
- Probe runner + report stay in-repo for future statement-layer push
Docs-only. No test, parser, solver, or eval changes.
Rebases onto current main (dec98ea, post-G.1/G.3.1/G.4/promotion).
Parser:
- Extend _COMPARE_MULT_ANCHOR_RE anchor alternation to include 'quarter'
and 'third'; add optional 'a\s+' article prefix so "a quarter as many"
and "a third as many" parse. Both anchors are in COMPARE_MULTIPLICATIVE_ANCHORS
and the round-trip factor-divisor table ("quarter":4, "third":3), so
round-trip checks pass. quarter→0.25 (exact), third→1/3 (float).
- Add _ANCHOR_TO_FACTOR entries for quarter and third.
Gate regex (test_adr_0131_G2_comparatives.py):
- Widen _COMPARATIVE_STATEMENT_PATTERNS multiplicative pattern from
'\d+\s+times' to '\w+\s+times' to match word-number forms ("four times")
that would be missed by the digit-only pattern if a future GSM8K case
contains one in a still-refused statement.
Cases (31 total, was 24):
- G2-mul-frac-005/006: two 'quarter' cases (fraction direction now has
half×4 + quarter×2 + third×1 = 7 cases, was 4 all-half).
- G2-mul-frac-007: 'third' case.
- G2-refuse-006: hyphenated 'one-third' pins the closed-anchor boundary.
- G2-refuse-007: 'double as many' pins the deferred grammar shape.
Tests (25, was 21):
- Add quarter and third parametric entries to test_multiplicative_direction_admits.
- Add one-third and double-as-many refusal params to test_refusal_cases.
- Add quarter/third to test_direction_literals_closed_set.
- Update test_runner_per_category_minima comment to reflect new counts.
ADR: document quarter/third admission, updated case table, deferred list.
report.json: refreshed to 31 cases, wrong==0 preserved.
Bundles the three pieces needed to consummate the promotion after
the reviewer signature lands:
1. Wire the expert tier in the capability ledger
2. Path-stability fix (digest filesystem-independence)
3. Reviewer-registry allow-list extension (regression fix for #194)
Result: mathematics_logic is now the first expert-tier domain in
the capability ledger.
$ ledger_report() -> mathematics_logic row:
status: "expert"
predicates: { seeded, grounded, reasoning_capable,
audit_passed, expert: True }
expert_reason: "ADR-0120-math composer admitted"
1. Ledger wiring (core/capability/reporting.py):
- _EXPERT_DOMAIN_STATUSES extends to 6 tiers with "expert"
after "audit-passed" (strict super-tier).
- New _EXPERT_COMPOSERS dict — per-domain registry of composer
module names. Currently only mathematics_logic ->
core.capability.expert_promotion_math.
- New `expert` predicate computation gated on audit_passed;
calls registered composer's evaluate_math_expert_promotion()
and reads promote_admitted as the verdict. Fail-closed on
exception or missing composer.
- status = "expert" when predicate True.
- predicates dict gains "expert" key; row gains expert_reason.
2. Path-stability fix (composite_math_gate.py + expert_promotion_math.py):
- New _rel(path) helpers return repo-root-relative POSIX
strings instead of str(absolute_path).
- claim_digest now commits to relative paths, so operator A
on ~/work/core and operator B on /srv/checkouts/core compute
the SAME digest for identical evidence.
- Without this fix no signature would ever match across
filesystems — a real bug that would have blocked every
signing attempt.
3. Allow-list regression fix (core/capability/reviewers.py):
- ALLOWED_TOP_LEVEL_KEYS extended with "math_expert_claims".
- PR #194 added the section to docs/reviewers.yaml but didn't
extend the allow-list, silently breaking the audit_passed
predicate for ALL 3 prior domains (loader rejected the file).
This PR's test_allowed_top_level_keys_includes_math_expert_claims
regression-pins the fix.
Reviewer signature (operator-only action by shay-j) carried in
docs/reviewers.yaml:
math_expert_claims:
- domain_id: mathematics_logic
signed_by: shay-j
claim_digest: "94149794e8c19896851e062cf1f921cfa9ba04770b674bc3b4c33023f7c7331b"
The auto-mode safeguard correctly blocked the agent from self-
signing during PR construction; the signature was performed by the
reviewer directly and brought into this PR. Future signatures stay
human-only.
Tests: 12/12 new ledger-flip tests + 174/174 across full obligation
auditor / composer / composite-gate / expert-demo / reviewer-registry
regression. Updated #194's awaiting-state snapshot to reflect the new
promote_admitted=True state on main.
GSM8K (honest disclosure, not gating): still 0/50 admission, wrong=0,
safety_rail_intact=True, substrate=candidate_graph. Probe lift is
future work (bounded pronoun coref is the highest-leverage item —
~28% of refusals route through it). The promotion does not depend
on GSM8K per ADR-0131.
Final wire-up after all 10 ADR-0114a obligations + ADR-0131.4
composite gate landed. Composes:
- all 10 obligation verdicts (5 from new auditor modules,
5 from inline checks over existing infrastructure)
- ADR-0131.4 composite math gate verdict
- ADR-0092 reviewer-signed claim entry from docs/reviewers.yaml
into a single deterministic promotion verdict + canonical
signed/unsigned ``expert_claims_math_v1_signed.json`` artifact.
Empirical verdict on current main (first evaluation):
all_obligations_passed: True
composite_gate_passed: True
technical_pass: True
claim_digest: d164866975341d9b82503caf50c0404ee140eab21fd60f589536c6daf6e1d706
reviewer_signature_present: False
promote_admitted: False
refusal_reason: awaiting reviewer signature
Every technical gate passes. The PR ships in the architecturally-
correct "awaiting reviewer signature" state — the reviewer's
signature is the separate, auditable operator action that
consummates the promotion.
Operator workflow (post-merge):
1. Run `core capability math-expert-promote`, confirm verdict,
capture claim_digest.
2. Add entry to docs/reviewers.yaml under math_expert_claims:
- domain_id: mathematics_logic
signed_by: shay-j
claim_digest: "d164866975341d9b82503caf50c0404ee140eab21fd60f589536c6daf6e1d706"
3. Re-run — promote_admitted flips to True.
4. Separate ledger-flip PR (out of scope here) consumes the
signed artifact and writes the capability ledger.
Safety property: if the evidence bundle changes after signing
(B-lane re-run, pack edit, obligation report shift), the digest
changes and the existing signature stops matching. The verdict
reports the mismatch explicitly and the operator must re-inspect
and re-sign — a ledger flip can't survive a silent evidence change.
New files:
- core/capability/expert_promotion_math.py — the composer
- tests/test_adr_0120_math_expert_promotion.py — 18 tests
- docs/decisions/ADR-0120-math-expert-promotion-wireup.md — ADR
Modified:
- core/cli.py — new `core capability math-expert-promote` cmd
- docs/reviewers.yaml — added math_expert_claims: [] section
with documentation comment
Tests: 18/18 covering each inline obligation evaluator
(#1/#3/#4/#7/#9 pass + failure modes), composer integration
against current main, reviewer-signature path (matching → admitted;
mismatched → refused with explicit diagnostic), digest
reproducibility, artifact byte-equality. All pass in 0.49s.
Trust boundary: read-only access to 4 B-lane reports +
GSM8K probe + 5 obligation auditor reports (transitively) +
frontier dir + docs/reviewers.yaml; single deterministic write
to the artifact path; no dynamic imports, no shell, no network.
This is the last PR before the first mathematics_logic -> expert
ledger flip attempt. The actual flip is reserved for a separate
small PR that consumes the signed artifact.
35-case OOD set (ood-001..ood-035): surface-varied siblings of B3's 35
solved_correct public cases. Entity-name pool: Maya/Liam/Noah/Diana/Felix/
Priya/Omar/Rosa/Jun/Kai. Unit-noun pool: oranges/marbles/pencils/books/
stamps/coins/balls (all parser-allowed count nouns). Every case in-grammar
per ADR-0131.3 and parseable without error.
Auditor (core/capability/ood_ratio.py): reads B3 public report.json + OOD
report.json, computes ood_ratio = ood_accuracy / public_accuracy, enforces
two independent gates — ratio ≥ 0.95 and wrong == 0.
CLI: core capability ood-ratio (exit 0 iff both gates pass).
Measured: public 50/50=1.000, OOD 35/35=1.000, ratio=1.000. Obligation #10
and B3 public lane unchanged.
Implements the external auditor for ADR-0114a Obligation #6:
"depth_curve.py produces a per-bucket curve;
accuracy(N) >= accuracy(depth_1) * (1 - eps)^(N - 1) for eps = 0.05."
Mirrors PR #189's auditor pattern (re-runs lane via the candidate-
graph pipeline, aggregates over committed cases, emits deterministic
report). Uses len(trace.steps) as the authoritative depth — the
engine's actually-executed reasoning, not the case's declared depth.
New module core/capability/depth_curve.py:
- Bucket schema mirrors ADR-0119.6: depth_1, depth_2-3,
depth_4-5, depth_6-8. Depth > 8 raises rather than silently
extending. Depth == 0 (initial-only problems) skipped — nothing
to decay.
- representative_depth = min(bucket) — most permissive bound
convention; tightening requires an ADR amendment.
- epsilon = 0.05 pinned per ADR-0120 §Threshold rationale.
- Two-axis verdict: obligation_6_mechanism_wired (always true if
auditor ran), obligation_6_assertion_holds (every populated
bucket satisfies the decay bound), coverage_sufficient (>=2
buckets populated AND >=3 cases each — required for the
assertion to be statistically meaningful).
CLI: core capability depth-curve (added to core/cli.py).
Writes evals/obligation_6_depth_curve/<lane_id>.json.
Empirical verdict on current main:
lane: B3_bounded_grammar
cases_total: 50
cases_solved: 22
mechanism_wired: True
assertion_holds: True
coverage_sufficient: False
populated: [depth_1 (21/21=1.0000), depth_2-3 (1/1=1.0000)]
Both populated buckets satisfy the decay bound. Coverage gap is
honestly named in the refusal_reason: depth_2-3 has only 1 case,
depth_4-5 and depth_6-8 have none. This is B3-owner work (case
authoring under the existing grammar contract), not auditor work;
reserved as a B3 v1.1 follow-up PR.
Honest scope-limit: B3 only. B1 (algebra, no trace) and B2 (chain
validation, not problem-solving) need different metrics — separate
sub-ADRs.
Trust boundary: read-only access to B3 cases + transitive pack
reads via the pipeline; single deterministic write to artifact path.
Tests: 24/24 covering bucket schema closure (depth 1..8 + raise on
9+), decay bound math (epsilon pinned, formula correct, depth_1 has
no bound), coverage-sufficient policy (thresholds pinned), lane
evaluation (passes on real B3 + refuses on missing cases),
coverage-sufficient distinction (B3 today vs synthetic 5+5 fixture
showing both pass), determinism (report identical + artifact
byte-equal).
External auditor for ADR-0114a Obligation #8:
"adversarial/score.py reports wrong == 0 across all families;
>= 30 cases x >= 8 families."
Verdict on current main:
cases_total: 36
families_total: 9
cases_refused: 28
cases_solved: 8
cases_wrong: 0 <-- the gate
obligation_8_passed: True
New module core/capability/adversarial.py mirrors PR #189/#190/#191
auditor pattern. Pure function over the committed cases set; broad
exception capture (correctly classified as refused — engine
couldn't process the input) makes the auditor robust to upstream
typed-refusal gaps.
New dataset evals/obligation_8_adversarial/v1/cases.jsonl — 36
cases x 9 families, closed taxonomy:
- paraphrase (verb outside initial-anchor whitelist)
- unrecognized_unit (not in en_units_v1)
- conditional (if/would/suppose)
- pronoun_coref (cross-sentence he/she/they)
- hedged_quantity (about/almost/approximately)
- ordinal_confusion (the 5th/third in cardinal position)
- implicit_subject (no named entity)
- self_reference (actor as comparison ref or transfer target)
- distractor_noise (adjectival/temporal/irrelevant siblings)
CLI: core capability adversarial. Writes
evals/obligation_8_adversarial/<lane_id>.json. Exit 0 iff
obligation passes.
Honest disclosure — 8 of 36 cases solved rather than refused;
none produced wrong answers. Two parser-layer gaps surfaced:
Gap A (pronoun_coref, 4/4 solved): unbound sibling sentences
silently drop; engine returns last-asserted state. Faithful but
semantically poor. Reserved follow-up: tighten admissibility so
unbound sentences refuse the whole case.
Gap B (unrecognized_unit, 4/4 solved): _canonicalize_unit
falls back to '+s' plural rule when pack doesn't recognize
the unit. Reserved follow-up: opt-in strict mode behind a flag
(some B3 units aren't in en_units_v1 either; strict mode
requires parallel pack extension).
Bug caught: adv-self-reference-003 ("Sam gives 3 apples to
Sam.") raises uncaught MathGraphError from
Operation.__post_init__. Auditor catches it as
refused-via-exception; ~3-line follow-up in
_build_op_candidate fixes the parser side.
Trust boundary: read-only access to cases + transitive pack reads;
single deterministic write to artifact path.
Tests: 11/11 in tests/test_adr_0114a_8_adversarial.py covering
threshold pinning (>= 30 cases / >= 8 families), closed taxonomy
(every documented family has cases; no unknown families),
obligation-passes snapshot, per-family wrong=0 invariant, failure
modes (missing file, below-threshold count), determinism (report
identical + artifact byte-equal).
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).
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.
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.
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.
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.
Zero behavior delta on the main baseline (both substrates produce
0/50 admission today) — but every subsequent ADR-0131.G.<n> iteration
now produces attributable admission deltas on the probe, instead of
silently extending a parser layer the probe wasn't measuring.
Background: ADR-0131.G's probe consulted run_lane → _score_one →
parse_problem (legacy first-match-wins parser, pre-ADR-0126). Every
G.<n> iteration extends the candidate-graph parser via
_score_one_candidate_graph → parse_and_solve. The mismatch was
discovered during G.3 development and explicitly reserved as this
follow-up.
Changes:
- run_coverage_probe.py: switch import to _score_one_candidate_graph;
new private _score_lane aggregator mirrors run_lane's output shape
via per-case scoring; report root adds "substrate": "candidate_graph"
for audit trail.
- train_sample_coverage_report.json: regenerated. All metrics
byte-identical to prior baseline (0/50 admission, wrong=0).
refused_reasons_top text differs (candidate_graph: prefix instead
of parser:) — expected and part of the substrate audit-trail shift.
Discipline: separate small PR per ADR-0131.G's "expansion that only
moves admission must be a standalone PR" principle. Substrate swap
attributable; future G.<n> deltas attributable.
Evidence:
- python3 -m evals.gsm8k_math.train_sample.v1.run_coverage_probe
→ admission 0/50, wrong=0, safety_rail_intact=True, exit 0
- pytest tests/test_adr_0131_G_gsm8k_coverage_probe.py
→ 8/8 pass in 0.18s (no test edits needed; tests pin invariants
not numbers)
- No changes to runner.py, no changes to any G.<n> work in flight.
Effect on in-flight iterations: each G.<n> PR (G.1 Gemini / G.2 #182 /
G.3 #183 / G.4 Opus#2) rebases after this lands and refreshes its
committed train_sample_coverage_report.json with the new substrate's
numbers. Rebase is mechanical.
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).
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.
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.
* 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).
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.
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.
Docs-only coordination artifact for the ADR-0131 post-GSM8K pivot.
Adds an implementation plan that defines the composite proof corridor,
claim boundaries, phase sequencing, and non-negotiable acceptance gates.
Docs-only PR. Follow-up to ADR-0127-0128-RESULTS' Path-B trigger.
Replaces ADR-0120's GSM8K-coverage requirement for the
mathematics_logic expert promotion with a composite gate over
three architecture-aligned benchmarks:
Benchmark 1: Symbolic equivalence (~500 algebra cases, public
+ sealed split). Primary discriminator — CGA exact-recall.
Pass: correct_rate >= 0.95, wrong == 0.
Benchmark 2: CORE-native teaching-corpus eval. Internal
consistency lane sourced from ratified packs (en_arithmetic_v1
+ en_units_v1 + en_numerics_v1). Grammar matches by
construction (no paraphrase-variance gap).
Pass: correct_rate >= 0.95, trace_hash byte-equality, wrong == 0.
Benchmark 3: Bounded-grammar word-problem set (~150 cases,
pre-filtered to ADR-0126/0127/0128 parser scope). Demonstrates
end-to-end pipeline determinism on the in-scope distribution.
Includes adversarial out-of-grammar probes.
Pass: correct_rate >= 0.95, wrong == 0.
Three benchmarks (not one) prevent overfitting per ADR-0114a
and force the math expert to demonstrate three distinct
architectural properties (algebraic correctness, internal
consistency, end-to-end pipeline determinism).
GSM8K stays as a stress-test lane the math expert RUNS but is
NOT gated on. Refusal counts reported in expert-claims artifact
as honest disclosure.
Other 12 ADR-0120 expert-promotion checks UNCHANGED.
Status: Proposed. The contract revision (removing GSM8K from
the gate) is a follow-up implementation PR after sub-phases
0131.1-0131.6 land the new benchmark substrate.
Integrates en_units_v1 (#164) + en_numerics_v1 (#163) into the
ADR-0126 candidate-graph parser. Loader merge (re-exports from
numerics_loader.py give single import path), pack-aware unit
canonicalization (handles irregular plurals like feet/children
via lookup_unit), indefinite-quantifier refusal (ADR-0128.4 —
'some'/'many' emit no candidates, preserving wrong==0), and
widened initial-possession shapes:
- <Entity> has N <unit> [of <substance>] (ADR-0127 substance qualifier)
- There are N <unit> [in <place>] (implicit-subject shape)
Plus: pack-backed cardinal grounding in math_roundtrip._value_grounds
(widens word-number coverage from hard-coded 0-12 to full numerics
pack cardinal table + compound rule). Op-pattern trailing prep
alternation gains of/for/with for substance qualifiers.
REGRESSION: 1050/1050 tests green across math + ADR-0126 + ADR-0127
ratification + ADR-0128 ratification + runner.
EMPIRICAL RESULT (the Path-B trigger ADR-0126/0127/0128 named):
correct = 0/50 wrong = 0/50 refused = 50/50
on evals/gsm8k_math/train_sample/v1/cases.jsonl
Per ADR-0127's exit criterion (correct >= 10/50, wrong == 0):
**MISSED** — the full deterministic design (candidate-graph
topology + units pack + numerics pack + pack-aware parser) does
not move the GSM8K-math lane. This is the real Path-B trigger.
WHAT WORKS (synthetic verification, 6/6 cases solve end-to-end):
- 'Jan has 5 apples. Jan buys 3 apples. ...' -> 8
- 'Sam has 10 feet of rope. Sam uses 3 feet of rope. ...' -> 7
- 'There are 5 kids in camp. ...' -> 5
- 'Sam has 10 children. Sam loses 2 children. ...' -> 8
- (money + time-dimension variants pass)
WHY GSM8K STAYS AT ZERO: real GSM8K problems carry compound
linguistic structure (pronouns across statements, possessives,
subordinate clauses, multi-word entities, multi-step inference)
that no amount of pack vocabulary addresses. Per-sentence parse
rate improved measurably on simple shapes; joint problem-level
pass rate stayed at zero because every real problem contains at
least one sentence the parser still cannot handle.
Full results + Path-B recommendation in
docs/decisions/ADR-0127-0128-RESULTS.md. The substrate
(architecture + packs) stays load-bearing in main; the math
expert promotion path retargets to a benchmark where exact
recall and determinism are the discriminators (proposed
ADR-0131).
Operator-supplied review of 'Beyond Traditional Pedagogy' triggered
a literature confirmation pass and a structural cross-walk against
CORE's teaching loop. Three artifacts:
1. ADR-0129 (DEFERRED) — Spaced reviewed-correction replay.
Maps onto retrieval-with-spacing literature (most robust finding
in cognitive psychology). Deterministic re-run of past
corrections at fixed cadence to verify they still produce
intended outcomes; failures emit operator-visible events
(no auto-correction). Deferred pending GSM8K-math Path-A/B
resolution + observed incident triggering un-deferral criteria.
2. ADR-0130 (DEFERRED) — Pre-articulation calibration logging.
Maps onto metacognitive prediction-outcome literature. Logs
CORE's pre-correction prediction; emits gap event on
correction acceptance. Provides empirical signal for 'is CORE
actually getting better' across pack-version cohorts. Deferred
pending same conditions as ADR-0129; the two compose if
un-deferred.
3. SESSION-2026-05-23 session note. Documents the review process:
literature confirmation pass (productive failure overstated,
retrieval transfer weaker than claimed, embodied cognition
replication crisis), missed frameworks (worked-example effect,
expertise reversal, CLT, deliberate practice, Bloom's 2-sigma),
structural cross-walk to CORE architecture (12 mappings), and
the rationale for ADRs 0129 + 0130 over alternative ports
(productive failure rejected as inverse of wrong==0; pre-testing
same; embodied learning N/A).
No code changes. Docs-only PR; lands independently of in-flight
ADR-0126 / 0127 / 0128 substrate chain.
Diagnostic from ADR-0126's first train-sample run (0/0/50): every
refusal happens at the first statement of each problem, and every
refused first statement fails on the unit-of-measurement construction,
not on the operation grammar. Adding more verb regexes is the per-axis
treadmill that produced 4 zero-lift ADRs. Units form a finite, externally
well-defined ontology (NIST SI tables, currency, English container nouns)
that is semantic substrate the candidate-graph parser was designed to
consume.
Scope:
- en_units_v1 pack: dimensions, units (<=60), containers, rate connectors
- conversions.jsonl: directed weighted graph of within-dimension unit pairs
- 3 new initial-possession shapes + rate-declaration extractor in the
candidate parser
- Round-trip filter gains optional pack-typed-unit check
- Solver gains dimensional canonicalization helper (shortest path through
conversion graph); fired edges join SolutionTrace.steps for replay
- Pack ratification invariants: round-trip identity, per-dimension
connectivity, path consistency, canonical unit per dimension
Wire the same train-sample exit criterion as ADR-0126 (correct >=10/50,
wrong==0). If passed -> sealed holdout. If still missed -> Path B
trigger is REAL (full deterministic design with units substrate failed),
demote GSM8K, re-target math expert promotion.
Also commits the empirical evidence: train_sample/v1/runner.py swapped
_score_one -> _score_one_candidate_graph; report.json baseline 0/0/50
confirming the candidate-graph topology refuses cleanly without units
substrate.
Architectural pivot from per-axis grammar expansion (ADR-0122/0123/0123a/
0123b, all 0/1319 sealed lift) to candidate-graph topology with round-trip
admissibility filter. Converts compound-gap failure from multiplicative
p^k to parallel 1-(1-p)^k.
Adds new invariant: round-trip admissibility (op slots must reconstruct
to byte-equal source sentence under whitespace/case normalization).
Preserves wrong==0, trace_hash byte-equality, pack-binding, replay
determinism, no stochastic sampling.
Exit criterion: 50-case GSM8K train-split sample (unsealed) must show
correct >=10/50 with wrong==0 before any sealed-holdout run. If miss,
escalate to Path B (benchmark re-selection).
Supersedes ADR-0123b (never opened as PR).
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.
Documents the Phase 5 GSM8K-math substrate completion across 7 narrative docs.
All 8 sub-phases of ADR-0119 (5.1 through 5.8) have landed on main; ADR-0114a's
10 anti-overfitting proof obligations are all discharged for the gsm8k_math lane.
Key facts surfaced in each doc:
- CORE-original public split: 150/150 correct, 0 wrong, 0 refused
- Real GSM8K test (sealed holdout): 0 correct, 0 wrong, 1319 refused
- Adversarial suite: 38 cases x 12 families, 0 wrong
- Depth curve: flat at 1.0 across depths 1-8 on public split
- Frontier baselines: Claude 3.5 Sonnet 96.4%, GPT-4 92.0%, Gemini 1.5 Pro 90.8%
- New lane shape gsm8k_capability_shape in LANE_SHAPE_REGISTRY
- New operational pack en_arithmetic_v1 (5 lemmas)
- ADR-0120 (first expert promotion contract) is the next gate
Docs updated: docs/PROGRESS.md, docs/capability_roadmap.md, docs/runtime_contracts.md,
docs/Whitepaper.md (§XIII), docs/Yellowpaper.md (gsm8k_capability_shape formal spec),
README.md, docs/decisions/README.md (current frontier).
No code changes. No new ADRs.
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>
Defines the `expert` ledger tier (sixth status above `audit-passed`)
and the composition gate that governs every future promotion.
The gate composes ALL TEN ADR-0114a obligations plus three contract-
level checks:
- audit_passed predicate must hold
- correct_rate >= 0.60 on public AND sealed holdout splits
- signed expert_claims entry whose digest reproduces byte-equal
Numeric thresholds (load-bearing choices documented with rationale):
- correct_rate floor: 0.60 ("advanced" — above weak open-source LLMs,
forces real architecture work, raisable later)
- depth-curve flatness ε: 0.05 (accuracy(N) ≥ accuracy(d1) · 0.95^(N-1))
- drift tolerance from promotion: ±0.02
Documents the post-ADR-0120 sequence in Open Candidate Directions:
Phase 1: ADR-0121 first worked math promotion (likely deferral)
Phase 2: parser-expansion arc to lift sealed-GSM8K correct_rate
Phase 3: math expert promotion succeeds
Phase 4: second domain = symbolic_logic (60-70% of math substrate cost;
ProofWriter / PrOntoQA benchmark; same machinery class)
Phase 5: third domain = high-stakes refusal-centric (medical or legal;
wedge-sharpener; needs two prior successes first)
Phase 7: open candidate — multi-reviewer threshold signing for expert
No code lands with this ADR. Implementation ships under ADR-0120a
to keep the contract change reviewable independently.
The first promotion attempt (ADR-0121) will likely defer on the
correct_rate gate — current measurement is 0/1319 on real GSM8K.
That deferral IS the contract working as designed; same load-
bearing pattern ADR-0107 → ADR-0110 demonstrated for audit-passed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Umbrella ADR for Phase 5 of ADR-0114. Decomposes the eval-lane work
into eight sub-phases (5.1..5.8) that ship under their own ADRs.
Sub-phase scope:
5.1 — sealed-holdout encryption (one lane, fabrication_control)
5.2 — CORE-original GSM8K-style corpus (dev + public, 200 cases)
5.3 — lane runner (parser → solver → verifier → realizer)
5.4 — frontier-baseline comparison (Obligation #7)
5.5 — adversarial generation; misparse rate zero (Obligation #8)
5.6 — depth-curve measurement harness (Obligation #6, measurement)
5.7 — sealed GSM8K test as the holdout (Obligation #1, lane-side)
5.8 — overall lane gate; new gsm8k_capability_shape
Roadmap only — no code lands with this ADR. Each sub-phase ships
independently and discharges a specific ADR-0114a obligation.
Critical invariant: dev + public splits are CORE-original; the real
GSM8K test set enters only via the encrypted holdout under 5.7.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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>
Documentation-only amendment to ADR-0114. Locks in the 10-point
falsifiable proof framework that any future `expert` ledger-tier
promotion (ADR-0120+) MUST require.
The obligations:
1. Sealed-holdout discipline
2. OOD surface variation ≥ 0.95 of public
3. Every correct answer ships with replay-equal trace
4. Refusal is first-class; misparse rate zero; zero `wrong` answers
5. Reasoning-isolation perturbation suite (invariance + predictable change)
6. Compositional-depth curve flat within documented ε
7. Frontier-baseline comparison on identical items, published
8. Adversarial generation; misparse rate zero
9. Determinism across release boundaries
10. Operation provenance via the pack (not hardcoded strings)
Each obligation is load-bearing and falsifiable: a domain that
cannot satisfy any one stays at `audit-passed`. ADR-0114a binds
ADR-0116..ADR-0120 to carry the obligations into implementation;
ADR-0120 finally invokes all ten as hard gates.
The audit-passed tier (ADR-0106/0109/0113) is unaffected. The two
tiers measure orthogonal properties: audit-passed verifies CORE
claim-shape compliance (transformer-unreachable invariants); expert
verifies capability with anti-overfitting proof.
No code change. Pure forward contract for the next phase of work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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>
ADR-0111 landed via PR #123 (merge commit 4b5e6b7). The structural docs
(README, ADR index, reviewers.yaml, CLAIMS.md) were updated in that PR.
The narrative docs that reference the worked-promotion arc by ordinal
("first promotion / one demonstrated domain") needed a follow-up sweep:
- docs/PROGRESS.md — ADR-0100 footnote, ADR-0111 row, ledger table,
Open-within-Phase-6 changed from "second" to "third" promotion
- docs/runtime_contracts.md — narrative line on contract demonstration
- docs/Whitepaper.md §XIII — chain range, demonstration paragraph,
"two domains demonstrated" line, full-chain pointer
- docs/capability_roadmap.md — Phase 6 worked-promotion narrative step 5
and exit-criteria checklist (5th item now ☑, 6th open as "third")
docs/Yellowpaper.md is unchanged: its expert-demo section is the formal
contract spec, domain-agnostic. docs/eval_methodology.md unchanged for
the same reason. docs/sessions/SESSION-2026-05-22-contract-layer-arc.md
is left as the historical snapshot of the original session.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Closes the documentation audit. All four lower-priority surfaces from
the earlier scan now reflect the post-ADR-0110 reality.
New:
- docs/sessions/SESSION-2026-05-22-contract-layer-arc.md: full
narrative of the ADR-0103 -> ADR-0110 arc, including the
refused/amended/succeeded narrative, infrastructure bridges, and
ledger state at session close. Pairs with the 2026-05-21
articulation-arc session log.
Extended (additive, no rewrites):
- docs/runtime_contracts.md: new 'Expert-Demo Promotion Contract
(ADR-0106 + ADR-0109)' section. Surfaces ledger_report() shape,
reviewer-yaml schema, lane-shape registry, replay invariant,
fail-closed registry behavior, and trust boundary.
- docs/eval_methodology.md: new 'Lane-shape registry (ADR-0109)'
section. Documents what to do when adding a new eval lane:
pick a shape or amend ADR-0109. Notes the holdout-runner gating
(cases_plaintext.jsonl vs sealed). Header bumped to 2026-05-22.
- docs/Whitepaper.md: new section XIII 'Evidence-Governed Domain
Layer' between XII (Forward Semantic Control) and the
Extensions/closing. Narrates the contract chain at the
philosophical / external-reader level. The original §XIII
becomes §XIV.
- docs/Yellowpaper.md: new section XII 'Ratification Contract
(ADR-0091 + ADR-0106 + ADR-0109)'. Formal specification of the
nine predicates, the promotion predicate (with explicit logical
form), the digest function, and the lane-shape registry table.
The original §XII becomes §XIII.
No code, no tests, no contract changes.
PROGRESS.md was last updated mid-Phase-5 (~ADR-0021). The pack-layer
chain (ADR-0027..0045), forward-graph+surface composer chain
(ADR-0046..0089), and evidence-governed domain layer (ADR-0091..0110)
were all absent.
capability_roadmap.md was last updated 2026-05-17 and predated the
expert-demo promotion contract entirely.
Added to both:
- Phase 6 — Evidence-Governed Domain Layer section covering ADR-0091
contract chain through ADR-0110 expert-demo arc, including the
refused-amended-succeeded narrative and the current ledger state
(math at expert-demo, four others at reasoning-capable).
- Backfill subsections for ADR-0027..0045 (pack-layer chain) and
ADR-0046..0089 (forward-graph + surface-composer + register +
anchor-lens + contemplation-loop work) marked retrospective.
capability_roadmap.md also gains a '2026-05-22' status update at the
top alongside the existing 2026-05-17 entry. The full Phase 0-5
exit-criteria rewrite remains queued separately.
No code, no tests, no claims changes.
- README.md: fix broken evals/CLAIMS.md link to root CLAIMS.md;
add 'Evidence-Governed Domain Layer' section pointing to the
ADR-0091/0096/0106/0109 contract chain and current ledger state
(math at expert-demo, four other domains at reasoning-capable).
- docs/decisions/README.md: extend the 'fully accepted' slate from
0091..0105 to 0091..0110 with ADR-0106 through 0110 entries; extend
the 'Evidence-governed domain chain' chain-notes section to reflect
the expert-demo arc (refused-amended-succeeded narrative).
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.
Amends ADR-0106 \xc2\xa71.2 to dispatch threshold rules by lane shape rather
than imposing cognition-pack-shape metrics uniformly. ADR-0107
surfaced that every non-cognition lane was failing the gate by
absence-of-key, not by substance.
Status: Proposed. Ships five shapes covering every lane currently
attached to a ratified pack: cognition_shape, accuracy_shape,
inference_shape, refusal_shape, symbolic_logic_shape. Four invariants
pinned. Unknown lanes fail closed; new shapes require ADR amendment.
\xc2\xa71.1 (reasoning-capable prereq), \xc2\xa71.3 (signature scoping), \xc2\xa71.4
(domain-aware), \xc2\xa71.5 (replay byte-equality) all preserved. ADR-0106
status remains Accepted.
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.
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.
- ADR-0108 Status: Proposed -> Accepted
- README index row updated to Accepted
- 'Current frontier' rewritten with the ranked Proposed-ADR list mandated
by ADR-0108 \xc2\xa7Decision; removes the now-false 'No ADR currently sits in
a "Proposed but unimplemented" state' sentence
- Open candidate directions (no-ADR-yet) section retained for the
multi-reviewer governance frontier item from ADR-0105
Makes the post-ADR-0105 sequencing of ADR-0080 / 0084 / 0087 / 0106
explicit, durable, and revisable. Status: Proposed. No content of the
four sequenced ADRs is modified — sequencing is meta, not content.
Defines a domain-aware, reviewer-signed expert_demo promotion gate to
replace the current cognition-lane-only predicate in
core/capability/reporting.py:418. Status: Proposed. This ADR does not
promote any domain — it defines the contract that a follow-up ADR (likely
mathematics_logic as ADR-0107) will consume as the first worked
promotion.
Sibling reconciliation PR to #104. The four ADRs explicitly called out as
the 'current implementation frontier' in PR #104 are already implemented
to the same evidence bar as the eight ADRs that PR accepted:
- ADR-0094: teaching/source.py + proposal schema widening + migration
script; tests/test_proposal_source.py green
- ADR-0095: teaching/from_miner.py + miner_loop_closure lane;
SHA-pinned in scripts/verify_lane_shas.py; tests/test_miner_proposals.py
green
- ADR-0098: core/demos/contract.py + adapter surface + demo_composition
lane; SHA-pinned; tests/test_demo_composition.py green
- ADR-0099: core/demos/showcase.py + public_demo lane;
SHA-pinned; tests/test_public_showcase.py green
Three of four lanes are SHA-pinned in CI (a stricter bar than several
already-accepted ADRs). Local pytest run: 85/85 passed across the four
tests/test_*.py files in 17s.
Also refreshes docs/decisions/README.md:
- flips the four table rows to Accepted (2026-05-22)
- rewrites the 'Current frontier' section now that no ADR-0091..0102
entry is unimplemented
- enumerates candidate next directions (curriculum proposals,
language-specific holdout splits, expert-demo ratification)
Docs-only change; no runtime code touched.
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.
Eight load-bearing ADRs closing the loop from contemplation Phase 5 through
a public showcase demo. Each one is small and evidence-bearing; together
they sequence the next arc without duplicating existing substrate.
- 0092 Reviewer Registry v1 — populates docs/reviewers.yaml schema;
unblocks all reasoning-capable claims under ADR-0091.
- 0093 Domain Pack Contract v1 Implementation — wires ADR-0091's five
follow-up items (parser, dry-run validator, chain registry, eval lane
refs, reviewer resolution) so manifest fields actually gate status.
- 0094 Proposal Source Provenance — sealed ProposalSource type widening
proposal schemas ahead of 0095.
- 0095 Miner-Sourced Teaching Proposals — closes the contemplation
loop: articulation_quality / contradiction_detection / frontier_compare
miners emit PackMutationProposal candidates routed through the single
reviewed teaching path; identity-pack defense at construction, not
review; replay-equivalence pre-gate.
- 0096 Fabrication-Control Eval Lane — first negative-control measure;
three case classes (phantom endpoint, cross-pack non-bridge, sibling
collapse) with frozen thresholds (fabrication_rate ≤ 0.01).
- 0097 Mathematics-Logic Reasoning-Capable Ratification — first
domain claim under ADR-0091; chain corpus + eval lanes already
exist, this is the formal contract ratification.
- 0098 Demo Composition Contract — DemoCommand protocol so demos can
be composed without reimplementation; deterministic JSON, no global
state mutation, declared output paths only.
- 0099 Public Showcase Demo — composes four scenes (determinism /
honest unknown / reviewed learning / multi-hop+trace) under 30s;
pure composition enforced by grep gate; JSON byte-equality CI-pinned.
Landing order: 0092 → 0094 → 0095 → 0093 → 0096 → 0097 → 0098 → 0099.
Deliberately not included: curriculum compiler, formation course
runner, calculator operators, response-mode taxonomy expansion,
learning-scale 10k harness. Each is deferred with a documented reason.
Phase 5 landed in commit 327047c (articulation-quality miner +
runtime sink wiring + full end-to-end loop tests). Extending the
session note in-place per its own append-only convention so the
single document covers the complete arc Phases 1–5.
Sections updated
----------------
* §0 executive summary
- commit count: 9 → 11
- phases shipped: 4 → 5 (new Phase 5 row in the deliverables
table)
- observation surfaces table grows two rows for
``attach_articulation_sink`` and
``mine_articulation_observations``
- test artifacts: 6 → 8 files, 64 → 82 cases
* §3.5 NEW — full architectural walkthrough of Phase 5
- Why the loop closes (links the user's
"memory confidence scoring" intuition to ADR-0080's
doctrine-aligned realisation: reviewable evidence, not
autonomous mutation)
- File-by-file delta (chat/articulation_telemetry.py + miner
+ runtime wiring)
- Three v1 mining rules (recurring_predicate_monotony /
recurring_planner_gap / low_average_predicate_diversity)
- Loop diagram showing live + offline halves
- Recorded demo output from the commit message
- Doctrine pin table mapping each constraint to its test
* §4 pipeline diagram extended to show Phase 5 sink + offline
miner branches
* §5 verification table gains four new rows for Phase 5 claims
(full-loop emission, byte-equal finding IDs across two e2e
runs, JSONL round-trip identity, opt-in gating)
* §5.6 suite totals updated:
- Contemplation subsuite: 35/35 → 53/53 (Phase 3+4+5)
- New row for Phase 5 articulation-quality e2e (7/7)
* §6 case study — added the "After Phase 5" trace and the
closing one-line story across all five phases for one prompt
("What is truth, and why does it matter?")
* §7 architecture surfaces table grows a row for
chat/articulation_telemetry.py and adds the miner to the
contemplation subsystem row
* §9 inverted from "what would close the loop" (future work) to
"SHIPPED — here's what it now unlocks":
- production sink + retention policy
- additional aggregation rules
- CLI hook
- review-loop wiring back into PackMutationProposal
* §10 reference index grows new lines for
chat/articulation_telemetry.py,
core/contemplation/miners/articulation_quality.py,
tests/test_articulation_quality_miner.py, and
tests/test_articulation_quality_e2e.py
* Footer updated: notes the arc is complete; future arcs should
start a new session-notes file and cross-link rather than
rewriting this one.
The session-notes file is now 1100+ lines — the complete frozen
reference for the articulation arc that took CORE from one-sentence
pack-grounded surfaces to a full live-reasoning + offline-mining
+ reviewable-proposals feedback loop, all doctrine-aligned, all in
one session.
Thorough why/when/where/how reference for the four-phase articulation
arc shipped this session plus the pre-arc classifier/Rust cleanup
that made it possible. Designed as the load-bearing entry point for
future contributors, case studies, capability audits, and
architectural reviews.
Sections
--------
0. Executive summary — what was achieved (9 commits, 64 new tests,
user-visible before/after, doctrine evidence)
1. Why this work happened (visible gap, doctrine constraint, what
was already wired vs. missing)
2. Pre-arc cleanup — RECALL trigger / CORRECTION x2 / Rust FFI
3. Phase 1 — discourse planner default ON + fast-path
Phase 2 — reflective rendering (subject pronominalization)
Phase 3 — live plan contemplation pre-flight
Phase 4 — per-plan articulation telemetry metrics
4. The pipeline today (diagram + before/after table)
5. Verification — every claim and the test that holds it.
Five sub-tables: doctrine claims, quality claims, back-compat /
null-lift claims, ADR-0072 structural invariants,
`core bench --suite all` performance, suite-level totals.
6. Case study — the compound prompt as a story across all four
phases
7. Architecture surfaces touched (which file got what change in
which phase)
8. What was deliberately NOT built (and why — connective rotation,
generalised pronoun selection, plan revision, Phase 2.5,
Rust algorithmic optimisation)
9. Phase 5 — what would close the user-intuited "live reasoning
→ memory confidence" loop, doctrine-aligned
10. Reference index — modules, flags, tests, ADR cross-refs
Per the user's request: captures the why/when/where/how thoroughly
so this work is recoverable for future reference, case studies, and
building on top. Append-only convention: future sessions extending
this arc should add new sections below rather than rewriting — the
history is itself evidence of the doctrine working in practice.
Brief 1 scopes the PR #97 follow-up: flip `definitional_layer` to false on
the three new domain seed packs (en_mathematics_logic_v1 / en_physics_v1 /
en_systems_software_v1) so the ADR-0084 closure verifier stops flagging
them as malformed. They're governed by ADR-0091 `domain_contract_version`,
which is a different layer; the integration test's allowlist already
excludes them, so the flag-flip aligns the script with the test's
clearly-intended scope.
Brief 2 scopes ADR-0073 L1.1 content phase II: extend the grc/he anchor-lens
substrate with 8 cross-language lemma pairs (νοῦς ↔ בינה / διάνοια /
καρδία ↔ לב / ψυχή ↔ נפש / ἔλεος / εἰρήνη / δικαιοσύνη / ἅγιος ↔ קדוש)
authored against the L1.1 file shapes, alignment edge weights, and
en-collapse annotation discipline. Anchor-lens-tour seam claims and
cognition-eval byte-identity under unanchored default remain the gates.
Both briefs are bounded to content authoring; neither touches code.
Closes audit Findings 6 (within-turn recall not batched) and 7
(probe-ingest / commit-ingest dual field) as a single PR — the two
are architecturally entangled and resolve together.
Pre-fix flow in ``ChatRuntime.chat()``:
1. ``probe_ingest(filtered)`` → ``probe_state.F``
2. Gate check on ``probe_state.F``
3. If gate fires: ``commit_ingest`` + stub response
4. Otherwise: ``commit_ingest`` + drive bias → ``field_state.F``
5. Walk runs on ``field_state.F``
The gate observes one manifold position; the walk navigates a
slightly different one (drive bias applied between them). Honest
refusal decisions and walk outputs are made on different fields —
the audit's named coherence gap.
This PR ships a flag-gated unified-ingest path following the
codebase's standard substantive-change pattern (ADR-0046 /
ADR-0062 / ADR-0085 / ADR-0088 / ADR-0089):
``RuntimeConfig.unified_ingest: bool = False`` (default).
When ``True``:
1. ``commit_ingest(filtered)`` runs first.
2. Drive bias applied immediately.
3. Gate observes ``committed.F``.
4. If gate fires: stub response (turn has already committed —
intentional semantic change documented in ADR-0090).
5. Otherwise: walk runs on the same ``committed.F`` the gate
decided against — no second ``commit_ingest`` call.
6. ``probe_ingest`` is not called on this path.
When ``False`` (default): historical behavior is preserved
bit-for-bit; ``probe_ingest`` still runs first.
ADR-0090 documents:
* Phase 1 (this PR): unified-ingest substrate.
* Phase 2 (separate PR, after Phase 1 validates): batched recall
— pass the gate's ``direct_hits`` into ``generate()`` as a
``prebuilt_first_recall`` so the walk's first step does not
re-call ``vault.recall()`` on the same field. Single recall
call eliminated per turn.
* Out of scope: ``recall_batch`` for per-step walk recalls
(each step's query depends on the previous step's field
state; not batchable without changing walk geometry).
Validation:
* 5 new tests in ``tests/test_unified_ingest_null_lift.py``:
- flag defaults to ``False`` on ``DEFAULT_CONFIG``
- flag-off surface + trace_hash + vault_hits byte-identical
- flag-on does not call ``probe_ingest`` (verified via spy)
- flag-on produces well-formed surface + trace_hash
- flag-off still calls ``probe_ingest`` (historical guard)
* ``core eval cognition`` byte-identical across all three splits:
public 100/100/91.7/100, dev 100/100/78.6/100, holdout
100/100/83.3/100.
* ``core test --suite cognition`` 120/0/1, ``smoke`` 67/0,
``runtime`` 19/0.
Comb-pass status after this PR:
* Item 4 (graph topo) ✓ #92
* Item 5 (realizer node_map) ✓ #91
* Item 6 (batch recall) ✓ ADR-0090 substrate (this PR); Phase 2
optimization is queued
* Item 7 (probe/commit dual ingest) ✓ ADR-0090 (this PR)
* Item 8 (dead defensiveness sweep) ✓ #91
* Item 9 (local imports) ✓ #91
* Item 11 (dead ``_fold_compose_into_surface``) ✓ #91
* Item 13 (``_serialize_*`` fold) ✓ #91
* Item 15 (GenerationResult tuple/list) ⊘ false positive
* Item 16 (subject normalization consistency) ✓ #93
* Item 17 (redundant ``^`` anchors) ✓ #94
* Tier 5 minor (``_BE_FORMS`` hoist, walrus, reverse-iter) ✓ #94
Comprehensive survey of every pack in the tree across five layers
(primitives → language → teaching → policy → selection axes → style)
with per-pack stats, cross-pack lemma overlap, teaching-chain graph
topology, and a ranked top-leverage-gaps list.
Key findings:
* **Layer 4 (selection axes) is the densest by far** — 24 packs
(17 anchor lenses + 7 registers). Greek and Hebrew lens
families are at parity (8 each).
* **Layer 2 (teaching corpora) is the load-bearing thin layer** —
only 41 reviewed chains across 18 subjects, with just 2 intent
shapes covered (CAUSE / VERIFICATION) and 7 connectives total.
No COMPARISON / PROCEDURE / CORRECTION chains exist; those
intents route through pack composers only.
* **Layer 1 EN is solid** — 354 lemmas across 12 mounted packs
with 93% gloss coverage; 24 ungloss'd lemmas remain.
* **Cross-language is structurally present but content-light** —
grc / he micro-packs ship and mount; the bigger
``*_cognition_v1`` siblings ratify but are not in the default
mount; no glosses exist on any non-EN pack.
* **Three drafted ethics packs are unratified** (legal /
research / engineering) — the highest-leverage low-effort gap.
* **Rhetorical-style axis ships substrate only** — one
null-lift pack; ADR-0087 consumer phase pending.
Top-leverage gaps ordered by ratio of unblocked value to effort
are enumerated in §7 of the doc itself. No code lands here.
Two ADRs that unblock the remaining items from the 2026-05-20 audit
that could not ship as direct PRs.
ADR-0088 — Realizer-Grounded Authority (Finding 2 retry)
========================================================
The first-response audit remedy (wire ``ground_graph`` between
``runtime.chat`` and ``realize_semantic``) was empirically attempted
on ``fix/ground-graph-wiring`` and reverted: the grounded realizer's
template output (e.g. ``"Light is a visible medium that reveal
truth"``) is grammatically and stylistically weaker than the runtime
path's ADR-0085-polished pack-grounded surface, so the realizer wins
the surface resolver (PR #76) and the user-visible surface regresses
on 23 byte-identical tests + ``register_invariant_grounding``.
ADR-0088 reframes Finding 2 as a two-phase rollout:
* Phase A (no behavior change) — realizer fluency parity.
Templates consult the same gloss source ADR-0085 wired into the
CAUSE composer, emit 3sg verb agreement, and carry the same
pack-provenance tag the runtime path emits. Byte-identical
today because the realizer is still gated by
``_is_useful_surface``.
* Phase B (substantive) — ground the graph and let the realizer
compete. Surfaces change exactly once, with a per-case
re-baseline justified by a "fluency ≥ pre-fix runtime surface"
invariant.
The audit's final-draft remedy (hot-path short-circuit only) is
explicitly rejected — pure perf cleanup, no metric lift since
``core eval cognition`` is already at 100% groundedness.
ADR-0089 — Compound-Intent Pipeline Dispatch (Finding 4)
========================================================
``classify_compound_intent`` is implemented but never reaches
``CognitiveTurnPipeline.run()``. Compound inputs like *"What is X
and how does it relate to Y?"* silently drop the second clause.
Naive multi-node dispatch breaks every downstream stage:
PropositionGraph (one root), plan_articulation (single-root),
realize_semantic (one target), surface resolver (one surface per
turn), compute_trace_hash (one intent_tag + articulation_surface),
teaching loop (one correction-source proposal), register / anchor-
lens telemetry (one variant per turn).
ADR-0089 proposes a three-phase rollout:
* Phase C1 (no behavior change) — call ``classify_compound_intent``
in step 1b, record dropped clauses on
``CognitiveTurnResult.dropped_compound_clauses`` for
observability while routing the dominant clause through the
existing single-intent path.
* Phase C2 (opt-in substantive) — flag-gated multi-node graph
dispatch with new ``CompoundEdge`` / ``ConjunctionRelation``,
widened ``compute_trace_hash`` carrying a
``compound_clauses_hash``, and a ``multi_clause_surface`` field
on the resolver. Flag-off preserves byte-identity.
* Phase C3 — telemetry alignment + demo + docs.
Each phase is independently shippable and preserves the existing
null-lift / byte-identity invariants register and anchor-lens
established (ADR-0072, ADR-0073d) as the project's pattern for
substantive runtime behavior changes.
Both ADRs are Proposed; ratification follows the existing pack /
ADR review process. No code lands in this commit.
Follow-up brief for the cheaper dev agent. Scoped tight: ~18 concrete
row-by-row edits across two patterns the v1 pass (PR #73) deferred or
missed.
Pattern A — 3sg present-tense agreement after relative pronouns
what/who/that/which followed by a bare-form verb where the implied
subject is singular. 10 candidates identified up front by repo scan:
- en_core_causation_v1/effect (with judgment note)
- en_core_cognition_v1/beginning, creation, definition, evidence,
light, reason, symbol
- en_core_meta_v1/example, mind
Brief explicitly clarifies that bare verbs after modals (`can`,
`may`, `should`) are CORRECT and must NOT be changed — flagging
the "who can know and do" case the v1 agent did right.
Pattern B — plural agreement after quantifier / preposition
between/among/of/two/three/many + count-noun-in-singular. 5
confident edits + 3 borderline cases with judgment guidance:
- en_core_attitude_v1/broad
- en_core_cognition_v1/context, order, style
- en_core_spatial_v1/between
Borderline guidance distinguishes count vs mass nouns: `reason`
in "group of reason" is count (apply fix); `reason` in "because
of reason" is mass (leave alone).
Same hard rules as v1 brief:
- no code edits
- definitional_atoms / predicates_invited / pos / lemma /
definition_version must not change
- Greek/Hebrew packs and primitives pack untouched
- closure verifier must exit 0
- cognition eval must stay byte-identical
- draft PR, human review before merge
Estimated effort: ~18 one-character edits. Whole pass should take an
order of magnitude less time than v1 because the candidate rows are
enumerated in the brief itself rather than discovered via heuristic
scan.
Why a v2 brief rather than amending v1's PR:
Plural patterns were not on v1's explicit pattern list (the v1
brief only named verb agreement, missing articles, missing
infinitives, missing copulas). 3sg agreement was named but
required a different tool than the agent had at hand. Scoping
v2 to exactly the rows known to need fixing is cheaper than
re-running the v1 heuristic scan with an expanded ruleset.
Provenance tag for the v2 pass:
adr-0085-style-v2:reviewed:2026-05-22
Pre-work for a writing-curriculum extension to CORE. Two companion
documents, both Proposed status (no code shipped).
docs/decisions/ADR-0087-rhetorical-style-axis.md
Pins rhetorical style as a third selection axis — sibling to anchor
lens (ADR-0073), orthogonal to register (ADR-0070). Substantive
axis: trace_hash DISTINCT across styles (style changes which moves
the composer requires and which frames the realizer emits, which
changes the propositional plan, which changes the trace).
Four anti-patterns explicitly named and rejected:
- style as motor (re-couples realizer to geometry; same shape as
the ADR-0085 fusion-operator rejection)
- style as register variant (conflates substantive with stylistic)
- style as identity axis (bloats identity doctrine)
- style auto-detected from user input (operator-chosen only)
Pack shape mirrors packs/anchor_lens/. default_unstyled_v1 is the
null-lift pack identical to no-style behavior. Three CI invariants
proposed: rhetorical_style_null_lift, schema validation, three-axis
orthogonality.
Substrate-only ADR — no consumer code, no genre packs. Consumer
integration is a follow-up ADR (composer + realizer extensions
that read permitted_frames + required_moves_per_claim +
forbidden_moves).
docs/curriculum/writing-chain-harvester-spec.md
Layer 0 of the writing curriculum. A deterministic tool that
extracts candidate (subject, predicate, object) triples from
reviewed expert prose and surfaces them as proposals to the
existing teaching/review pipeline.
Five stages (segment → classify → extract → propose → audit) —
pure-Python rule-based, no LLM generation, no auto-acceptance.
Trust boundary: reviewer accept/reject via the existing
core teaching propose/review path. No bypass permitted.
The harvester is a proposal PRODUCER, not a proposal CONSUMER.
Plugs into the existing pipeline without inventing a new review
mechanism. Each proposal carries source_id + source_line + the
exact source_clause it came from for reviewer verification.
First-implementation acceptance criteria deliberately tight:
Stage 0+1 with dry-run only. Stages 2-5 are follow-up PRs.
Substrate-first sequencing pattern (ADR-0084 → 0085) reused
throughout. Both documents acknowledge open questions deferred to
implementation phase rather than pre-deciding.
Why now: a writing curriculum is being scoped. Without this ADR,
every downstream PR faces the same "should style be a motor?"
question and the temptation to reach for the geometry will recur
every time the realizer produces a stilted surface. Pinning the
axis up front prevents that recurrence.
Brief for a fluency pass on the 333 ratified gloss entries. Closes the
content-side counterpart of ADR-0085's surface lift:
Before "Light exists as visible medium that reveal truth."
After "Light exists as a visible medium that reveals truth."
Same gloss content, English-correct. Fixes 3sg agreement after
relative clauses, plural agreement after quantifiers, missing
articles before adjective-noun NOUN-frame glosses, and missing 'to'
in VERB-frame glosses.
Hard constraints encoded in the brief (matching the wrapper-prompt
pattern that worked for ADR-0084 content):
- code untouched: only language_packs/data/<pack>/glosses.jsonl edits
- definitional_atoms, predicates_invited, pos, lemma, definition_version
must NOT change
- closure verifier (scripts/verify_definitional_closure.py) must
still exit 0
- cognition eval must stay byte-identical to baseline
- Greek/Hebrew packs untouched (not in definitional layer per ADR-0084
scope limit)
- primitives pack untouched
- draft PR, human review before merge
Includes a Phase-1 inventory script the agent runs first to scope the
work (heuristic pattern matcher across the 13 opted-in packs),
worked-example fluency rules with before/after table, per-pack
checksum-refresh shell snippet, and Phase-4 verification commands.
Estimated effort: ~30-60 lines of JSONL edits. Same handoff format as
docs/handoff/ADR-0084-pack-content-brief.md.
The original "Why does light exist?" complaint that motivated ADR-0084
was specifically about CAUSE-intent surfaces. ADR-0084 (substrate) +
PR #65 (content) already moved DEFINITION/RECALL to gloss-grounded
surfaces ("Light is visible medium that reveal truth."). But CAUSE
still dispatched through the chain-walk path:
Before: light — teaching-grounded (cognition_chains_v1):
cognition.illumination; logos.core.
light reveals truth (cognition.truth).
No session evidence yet.
After: Light exists as visible medium that reveal truth.
pack-grounded (en_core_cognition_v1).
The chain-walk is structurally correct but the wrong SHAPE for a why-
question — it's a graph traversal, not an explanation. ADR-0085 fixes
the shape using the same gloss material that DEFINITION/RECALL already
consume, with no new content authoring.
Additive composer
chat/pack_grounding.py:gloss_aware_cause_surface()
- Resolves gloss via lexicon-residency-checked resolve_gloss().
- Frames POS-aware:
NOUN -> "{Lemma} exists as {gloss}."
VERB -> "To {lemma} is to {gloss}."
ADJ -> "To be {lemma} is to {gloss}."
* -> falls back to _frame_gloss (predicate-identity).
- Threads anchor lens via the existing helper (ADR-0073c parity).
- Returns None when no gloss exists — runtime falls through to the
existing chain-walk path. Additive: no CAUSE case loses its surface.
Runtime dispatch
chat/runtime.py — IntentTag.CAUSE tries gloss path FIRST under the
flag; falls through to teaching_grounded_surface* on None.
Unconditional fallback — never silent.
Opt-in flag
core/config.py — RuntimeConfig.gloss_aware_cause: bool = False
Default off preserves pre-ADR-0085 chain-walk surfaces byte-
identically (null-drop invariant, CI-pinned).
Prompt-diversity classifier update
evals/prompt_diversity/runner.py — _CAUSE_MARKERS widened with the
explanation-frame markers ("exists as", "is to", "to be", "is for",
"purpose of") plus bare-form predicates ("reveal" alongside
"reveals"). Neither composer path is penalised on shape_fit just on
inflection grounds.
v1/public lift (flag OFF vs ON, 26 cases)
intent_accuracy : 65.4% -> 65.4% ( — )
versor_closure_rate : 100.0% -> 100.0% ( — )
response_shape_fit : 57.7% -> 57.7% ( — , both frames recognized)
audit_in_surface_rate : 42.3% -> 42.3% ( — , envelope ADR's job)
gloss_quote_rate : 11.5% -> 23.1% (+11.5pp, structural lift)
Tests (15)
- 5 pure composer (NOUN/VERB frame, unknown/empty None, no chain-
walk artifacts in surface)
- 5 runtime dispatch (flag-off chain-walk, flag-on gloss, parametrized
across glossed subjects, VERIFICATION unchanged under flag, no-
gloss fallback engages)
- 5 cognition lane invariance (aggregate metrics byte-identical
under both flag states; surfaces deliberately shift on the 2 CAUSE
cases with glossed subjects — the structural-change-vs-metric-
invariance both-sides invariant)
Lanes
smoke 67/0, cognition 120/0/1 skipped, packs 6/0, teaching 17/0,
runtime 19/0. core eval cognition byte-identical 100/91.7/100/100
under both flag states.
Scope limits (per ADR §Scope limits)
- CAUSE only; VERIFICATION still chain-walks (different shape).
- English pilot only; Greek/Hebrew packs not opted into definitional
layer yet (ADR-0084 scope limit).
- Single-lemma subjects; compound/anaphoric fall through.
- Opt-in until cognition holdout confirms the lift transfers off-
fixture. Future PR flips default on.
Out of scope
- Surface-vs-envelope cleanup ("pack-grounded (...)" still leaks).
- Predicate licensing (ADR-0086).
- Content style pass (bare lemma forms in glosses — separate brief).
* docs(adr-0084): propose definitional layer + prompt-diversity suite
Three companion artifacts proposing the next substantive design step
after ADR-0083:
1. ADR-0084 (Proposed) — Definitional Layer for Lexicon Packs
Optional `definition` block on pack entries: gloss,
definitional_atoms, predicates_invited, definition_version,
provenance. Pack-level opt-in. Closure rule: every word in a
gloss must resolve to a same-pack lemma, another mounted pack's
lemma, or a primitive in a new `packs/primitives/` pack.
NO composer change in this ADR (sequenced for ADR-0085) —
ratify substrate before any consumer depends on it.
2. evals/prompt_diversity/ (Proposed) — companion eval lane
~50 cases across question-shape × sophistication × domain,
measuring three new metrics: response_shape_fit,
audit_in_surface_rate (quantifies the trust-boundary leak into
user surfaces), gloss_quote_rate (zero today; rises with future
gloss-aware composer). No v1 pass thresholds — the lane
establishes a baseline distribution so future work has
something to move. 26 seed cases authored covering all 21
categories.
3. docs/handoff/ADR-0084-pack-content-brief.md — paste-ready brief
for a cheaper/faster dev agent to produce the pack content in
parallel. Self-contained, 5 sequenced phases (primitives pack
→ extend 9 existing glosses → add to relations/anchors → write
closure verifier → run safety lanes), explicit don't-touch list
(no composer / runtime / algebra / Greek+Hebrew packs / schema
parser), no-LLM-glosses discipline, per-phase acceptance.
Discovery while drafting: 9 packs already carry glosses.jsonl
under language_packs/data/ with a flat schema (78 entries in
en_core_cognition_v1 alone). The brief reflects that — most
work is extending existing entries, not authoring from scratch.
Strategic context: ADR-0083 raised the *depth* ceiling on chain
composition; ADR-0084 raises the *fidelity* ceiling. The φ
separation probe (memory: phi-separation-falsified) established
that semantic capability lives in chain composition, not in φ
geometry, so deepening the composer's substrate is the natural
next step. ADR-0084 → 0085 (gloss-aware composer) → 0086
(predicate licensing at ratification) is the planned sequence.
* feat(adr-0084): substrate — schema parser, primitives loader, closure verifier
Substrate-only code-side for ADR-0084 (Definitional Layer for Lexicon Packs).
No composer touches the new fields yet; consumer integration is ADR-0085.
Schema (additive, default preserves byte-identity)
- LanguagePackManifest.definitional_layer: bool = False
- compiler loader propagates the flag from manifest.json
language_packs/definitions.py (new)
- GlossEntry dataclass: lemma, gloss, pos, definitional_atoms,
predicates_invited, definition_version, provenance_ids
- parse_gloss_entry(payload, *, strict) — strict mode enforces ADR-0084
§Schema validation row-by-row: required keys, typed lists, no
unknown keys, positive definition_version; lax mode preserves the
legacy two-field shape for back-compat
- load_pack_glosses(pack_id, *, strict) with cache + clear hook
- verify_definitional_closure(pack_id, *, mounted_pack_lemmas,
primitive_lemmas, strict) returning tuple[ClosureViolation, ...];
case-insensitive resolution; cycles permitted per ADR
packs/primitives/loader.py (new)
- Sister loader to packs/safety/ and packs/identity/
- PrimitivesPack frozen dataclass with .lemmas frozenset
- Gates: checksum match, kind=='primitives', definitional_layer:true,
never_auto_mutable:true, pack_id matches dir, primitive_count
cross-check, duplicate-lemma rejection, path-traversal rejection,
strict per-entry schema with allow-list
- DEFAULT_PRIMITIVES_PACK = 'en_semantic_primitives_v1'
tests/test_adr_0084_definitional_substrate.py
- 38 tests covering strict parser (each required key rejection, unknown
key rejection, empty predicates_invited allowed, empty
definitional_atoms rejected, invalid definition_version), lax
parser back-compat, load_pack_glosses (missing/strict raise/lax
skip/malformed JSON), closure verifier (same-pack/primitive/mounted/
unresolved/case-insensitive), primitives loader (every gate), and
a back-compat check that every shipped pack still ratifies with
definitional_layer=False
Lanes: smoke 67/0, cognition 120/0/1, teaching 17/0, runtime 19/0,
packs 6/0. Cognition eval byte-identical 100/91.7/100/100.
When the content PR lands (primitives.jsonl + extended glosses.jsonl
under ADR-0084-pack-content-brief.md), the gate catches any closure-rule
violation without further code change.
* feat(evals): prompt_diversity lane runner — measurement instrument for ADR-0084+
Implements the runner against the existing contract.md + 26-case v1
public split. Lane auto-discovered by evals.framework via the standard
contract + runner convention.
Runner (evals/prompt_diversity/runner.py)
- run_lane(cases, *, config, workers) -> LaneReport
- 5 metrics: intent_accuracy, versor_closure_rate (carried over from
cognition), plus the three new lane-specific metrics —
response_shape_fit, audit_in_surface_rate, gloss_quote_rate
- breakdown dict groups by (question_shape, sophistication, domain)
per contract §How to read the output
- mirrors evals.cognition.runner's parallel worker pattern
Per-shape classifier (deliberately substring/regex-simple at v1)
- predicate_identity, explanation, sequence, two_subject_contrast,
narrative, honest_disclosure
- Unknown shape => neutral pass (don't penalise new categories)
Audit-leak detector
- trust-boundary preamble markers (teaching-grounded (, pack-grounded
(, No session evidence yet.)
- dotted semantic-domain tag regex (cognition.illumination, etc.)
Gloss-quote detector
- resolves expected_terms via chat.pack_resolver.resolve_gloss
- 4-token contiguous-window match against surface (high-confidence
"gloss actually quoted", not "shared one common word")
Tests (tests/test_prompt_diversity_runner.py — 23)
- shape classifier parametrized over the six expected_shape values
- audit-leak detector parametrized over preamble + tag + clean cases
- end-to-end on v1 public:
* versor_closure_rate == 1.0 (only v1 pass threshold per contract)
* every metric in [0, 1]
* breakdown groups present with the four per-cell metrics
* diversity gate: >=5 question shapes, >=3 domains
(defends against future regressions that collapse the suite
back to a cognition-shaped fixture)
v1/public baseline (26 cases)
intent_accuracy : 65.4% (contract predicted 70-85%)
versor_closure_rate : 100.0% (only v1 pass threshold) PASS
response_shape_fit : 53.8% (contract predicted low)
audit_in_surface_rate: 42.3% (contract predicted ~100%)
gloss_quote_rate : 7.7% (contract predicted 0%)
Three baseline surprises worth noting in the report (NOT failures —
the v1 lane is explicitly there to establish the distribution):
- audit_in_surface_rate at 42% (not 100%) means the chain-walk leak
fires on ~11/26; the other 15 are honest-disclosure cases that
emit no audit envelope. Sharpens the future surface-vs-envelope
ADR's actual target: grounded surfaces specifically.
- response_shape_fit at 54% (not "low") — classifier likely has
false positives on the ", which " cause-marker. Worth tightening
once we have an ADR-0085 baseline to compare against.
- intent_accuracy at 65% (below predicted 70-85%) — classifier dips
harder on adversarial/cross-pack than expected. Real gap.
All five smoke/cognition/teaching/runtime/packs lanes still green;
core eval cognition byte-identical 100/91.7/100/100.
* feat(packs): ADR-0084 pack content (primitives + extend glosses + closure verifier) (#65)
* feat(packs): ADR-0084 pack content
* feat(packs): repair ADR-0084 definitional content
* test(adr-0084): adjust substrate manifest tests for post-#65 content reality
PR #65 flipped definitional_layer:true on 13 English packs (9 core +
4 relations + collapse-anchors). The substrate's previous test
test_existing_packs_unchanged asserted that en_core_cognition_v1 +
en_core_relations_v1 still had definitional_layer:False — which was
the right pre-content invariant but is wrong post-content.
Replace it with two complementary tests that hold against real content:
- test_non_opted_packs_default_false:
pins that packs that DIDN'T flip the flag (en_minimal_v1,
he_core_cognition_v1, grc_logos_cognition_v1) still surface
definitional_layer=False through the loader. Defends against
a future change accidentally flipping the flag on a non-opted
pack.
- test_opted_packs_carry_flag:
pins that packs that DID flip the flag (en_core_cognition_v1,
en_core_relations_v1) surface definitional_layer=True through
the loader. Proves the substrate's manifest-field propagation
works against real ratified content, not just fixture packs.
Net: +1 test, same intent (substrate ratifies the manifest field
correctly), now with real-content coverage on both sides of the gate.
All 62 ADR-0084 substrate + prompt-diversity tests pass.
Strict superset of ADR-0062's depth-1 composer. `max_depth` is the
number of follow-up hops appended beyond the initial chain:
max_depth=0 → byte-identical to single-chain surface
max_depth=1 → byte-identical to ADR-0062 composed
max_depth=2 → byte-identical to ADR-0062 when no second hop
survives, strict superset when one does
The composer surfaces content the realizer was silently dropping
from chains already ratified in `cognition_chains_v1`. Example
live lift on `"Why does light exist?"`:
composed: "light reveals truth, which grounds knowledge."
transitive(2): "...which grounds knowledge, which requires evidence."
Cycle-safe at every depth via a single visited-set; single-corpus
traversal in v1 (cross-corpus transitive deferred to a follow-up
ADR alongside ADR-0064's cross-pack model).
Both flags default False — every existing surface is preserved
byte-identically. When both `composed_surface` and
`transitive_surface` are True, transitive wins.
Implementation:
- `core/config.py`: `transitive_surface: bool = False`,
`transitive_max_depth: int = 2`.
- `chat/teaching_grounding.py`: `_resolve_followup` shared helper
refactored out of the depth-1 composer (no behavioural change),
plus new `teaching_grounded_surface_transitive(subject,
intent_tag, *, max_depth)`.
- `chat/runtime.py`: dispatch order — transitive > composed > single.
Verification:
- tests/test_transitive_surface.py: 16 new tests covering pure-fn
contract, visited-set cycle guard at every depth, runtime
integration, and the cognition-lane null-drop invariant at
`max_depth=2` (public + holdout splits).
- tests/test_composed_surface.py: 11/11 pass after the helper
refactor (ADR-0062 behaviour preserved).
- `core test --suite smoke`: 67 pass.
- `core test --suite cognition`: 120 pass, 1 skipped.
- `core test --suite teaching`: 17 pass.
- `core eval cognition`: 100 / 91.7 / 100 / 100 (byte-identical).
* chore(evals, cli): contract standardization + bench --json stdout cleanliness
End-of-session shippability pass. Three concrete fixes:
1. core/cli.py — bench --json no longer pollutes stdout
Several bench paths call scripts.run_pulse.run_pulse which prints
verbose [pulse] traces unconditionally to stdout, breaking jq /
programmatic consumers of --json output.
New _bench_stdout_guard() redirects stdout → stderr for the
duration of the bench run when --json is set. Operator still sees
the pulse trace (on stderr), but --json consumers get a clean JSON
document on stdout. Applied to all four bench paths: cost,
articulation, default suite, and --suite all.
Verified: core bench --suite determinism --json now produces
parseable JSON; human path still shows 1140 [pulse] lines.
2. evals/{frontier_compare,realizer_guard}/contract.md (new)
core/contemplation/contract.md (new)
Each new contract follows the established pattern (37 contracts
already exist under evals/<lane>/contract.md):
- What it measures
- Why it matters (structural win)
- How to run
- How to read the output
- Pass criteria table
- When it has failed and why
- Runner / module layout
Coverage:
- frontier_compare: both Lane A (CORE-only suites) and Lane B
(cross-provider prompt_battery) with explicit guardrails
against mixing — operator asks for the wrong lane combination,
runner exits 2 with helpful error.
- realizer_guard: C1/C2 articulation safety boundary — synthetic
illegal candidates rejected directly by check_surface AND
former-bug runtime prompts now produce legal articulations.
- contemplation (ADR-0080): not under evals/ since it's runtime
infrastructure that consumes eval reports — contract lives at
core/contemplation/contract.md. Documents the read-only +
SPECULATIVE-only + deterministic-replay invariants and the
shared DiscoveryCandidateSink plumbing convergence (ADR-0080).
3. evals/CLAIMS.md — Tier 2 rows added
- frontier_compare Lane A: determinism.primary_score, max_versor_condition
- frontier_compare Lane B: prompt_battery.primary_score (CORE adapter),
cross-provider artifact persistence
- realizer_guard: all_claims_supported
- contemplation: SPECULATIVE-only invariant, deterministic replay,
additive sink path, no pack mutation (all CI-pinned by tests)
Verification
------------
$ core test --suite smoke -q
67 passed in 27.22s (no regression)
$ uv run pytest -q tests/test_contemplation_loop.py \
tests/test_contemplation_pipeline_convergence.py \
tests/test_frontier_compare_cross_provider.py
27 passed in 4.87s
$ core bench --suite determinism --json 2>/dev/null | jq .results[0].passed
true (was: JSONDecodeError on prior [pulse] pollution)
* feat(evals/ui): report viewer renders Lane B cross-provider + pass-rate chart
Stop-hook caught that #62 only covered contracts — the 929-line
report_viewer.html was never audited against the new cross-provider
report shape from #61. Two real gaps:
1. Lane-aware observation drawer
The drawer hardcoded Lane A (CORE-native) fields: surface,
grounding_source, anchor_lens_mode_label, versor_condition.
Lane B (cross-provider) observations carry different fields:
provider, model, elapsed_ms, error_type, error_message.
Loading a cross-provider report rendered only the surface row
with empty `grounding` — the provider + model + timing data
was unreachable without expanding "Show raw JSON".
Fix: detect Lane B (presence of `obs.provider`) and render the
appropriate field set. Lane A still renders identically (now
also surfaces trace_hash + register_id when present, which were
silently buried in the raw JSON before).
2. Pass-rate chart per suite
The summary strip showed one aggregate Primary % across all
suites, with no way to see WHICH suite is dragging the score.
Multi-suite runs (e.g. --suite all) had to expand each panel
individually to find the failing one.
Fix: new .passrate-chart element below the summary strip,
one horizontal bar per suite showing passed/total. All-pass =
solid green, all-fail = solid red, partial = green/red split
at the pass fraction. CSS only — no new dependencies.
3. SUITE_PREAMBLES gains the prompt_battery entry so the sidebar
shows the "side-by-side surface evidence across providers"
description when loading a Lane B report.
Verified
--------
- Brace/paren/div balance unchanged (308/308 / 380/380 / 54/54)
- One <script> tag pair preserved
- Generated a real Lane B report via
`python -m evals.frontier_compare --provider core --suite prompt_battery`
for visual confirmation
Out of scope (noted for future PR)
----------------------------------
Sampled 3 `core demo` targets:
- register-tour: clean schema (all_claims_supported, claims, grid)
- audit-tour: both scene_1_* keys AND an empty scenes:[] array — inconsistent
- anti-regression: no all_claims_supported key, uses all_gates_held instead
Demo schema standardization deserves its own PR — operator tooling
would benefit from a uniform top-level success field across demos.
* docs(evals) + chore(demos): systematic audit + uniform success field
Stop-hook caught two real gaps after the contract+UI PR:
- demos had divergent success-field names (all_gates_held vs
learning_loop_closed vs claim_supported vs nested claims_supported)
- no systematic look at the 48 eval directories had been done
Both addressed concretely; remaining work captured in audit doc
rather than vaguely deferred.
1. Demo schema standardization — uniform all_claims_supported field
----------------------------------------------------------------------
All 9 ``core demo`` targets now emit a top-level
``all_claims_supported: bool`` field. Existing per-demo fields
(``all_gates_held``, ``learning_loop_closed``, ``claim_supported``,
nested ``claims_supported``) are preserved for backwards compat —
the new field is an alias derived from the demo's existing success
signal, not a replacement.
Operator tooling and the CI gate can now target
``all_claims_supported`` without knowing each demo's idiomatic
field name.
Files touched:
- evals/anti_regression/run_demo.py — adds AND of all_gates_held +
active_corpus_byte_identical
- evals/learning_loop/run_demo.py — adds AND of learning_loop_closed +
active_corpus_byte_identical
- scripts/publish_pack_measurements.py — adds AND of the three
entries in the nested claims_supported dict
- evals/long_context_cost/comparison_runner.py — adds alias for
claim_supported (singular)
The 5 demos already using ``all_claims_supported`` (audit-tour,
register-tour, anchor-lens-tour, orthogonality-tour, articulation)
are unchanged.
Verified across all 9 demos:
audit-tour : True
register-tour : True
anchor-lens-tour : True
orthogonality-tour : True
pack-measurements : True ← new alias
anti-regression : True ← new alias
learning-loop : True ← new alias
articulation : True
long-context-comparison : True ← new alias
2. docs/EVAL_AUDIT_2026-05-20.md — systematic 48-lane audit
------------------------------------------------------------
Replaces the "future PR" deferral with a concrete document.
Contains:
- Method (what was inspected for each lane).
- Summary (40/48 have contract.md; 18/48 have saved results;
empty results/ ≠ broken — most lanes regenerate on demand).
- Cross-provider relevance triage:
* 9 lanes are cross-provider-relevant and could benefit
from the prompt_battery-style adapter pattern (cognition,
english_fluency_ood, hebrew_fluency, koine_greek_fluency,
grammatical_coverage, inference_closure, multi_step_reasoning,
discourse_paragraph, foundational_*_ood, etc.).
* 29 lanes are CORE-only by design (versor closure, anchor
lens, identity divergence, provenance, etc.) — wiring
providers would be category-erroneous.
- Demo schema standardization status (this PR closes that).
- UI/UX coverage matrix.
- 5 concrete follow-up items, each focused enough for a single
PR, none requiring architectural change.
Regenerated reports
-------------------
evals/long_context_cost/results/comparison_v1.json and
evals/results/phase2_pack_measurements.json now contain the new
all_claims_supported field (auto-regenerated when validating the
schema change).
evals/frontier_compare/results/sample_core_promptbattery.json
added as a reference Lane B report so the new viewer always has
something to load on first open.
Resolves a same-day numbering collision: the prior session produced
ADR-0080 + ADR-0081 (geometric stress field, falsified) in
docs/decisions/ while the frontier-provider-adapters work was
authored as ADR-0081 in a newly-created docs/adr/ directory,
unaware of the concurrent track.
This commit takes the minimum-blast-radius fix:
- docs/adr/ADR-0081-...md → docs/adr/ADR-0082-...md
- Update title header to ADR-0082, add "Renumbered from" breadcrumb
- Update the two source-file docstrings that cite the ADR number
(providers.py, model_registry.py)
The "two ADR directories" question (docs/adr/ vs docs/decisions/)
is NOT resolved here — docs/adr/ now has exactly one entry, while
docs/decisions/ is the canonical location per CLAUDE.md. A future
PR should either consolidate or document the split; this commit
just unblocks the immediate naming collision.
Out of scope:
- Consolidating directories
- Renumbering anything in docs/decisions/
- Re-numbering on the dev's local main (already pulled into this branch)
Wires observational telemetry on the composer-vs-graph atom-set
relationship. Phase 1 is strictly observational: no enforcement,
no surface mutation, no grounding-source change, no trace-hash impact.
New telemetry fields on TurnEvent + ChatResponse:
composer_graph_atom_status ∈ {equivalent, divergent,
graph_unconstrained,
composer_no_atoms,
not_applicable, ""}
composer_atom_set_hash SHA-256 over sorted unique atoms
graph_atom_set_hash SHA-256 over sorted unique atoms
composer_graph_atom_overlap_count int
Composer atoms come from existing pack candidate metadata
(pack_semantic_domains channel through _maybe_pack_grounded_surface).
Graph atoms come from build_graph_from_input + resolve_lemma on
node.subject/predicate/obj — no prose parsing. When a grounded
composer path lacks explicit atom provenance, status is
'composer_no_atoms'.
New pure helper:
chat/atom_equivalence.py — normalize_atoms, hash_atoms,
atoms_for_graph_nodes, compare_atom_sets
Tests (tests/test_composer_graph_atom_equivalence.py):
- Pack DEFINITION path produces observable equivalence
- Divergent atom sets produce distinct hashes
- Register invariance: atom hashes + status identical across
{neutral, terse, convivial}; trace_hash also constant (R5 axis)
- Anchor lens engaged case still ASCII-only on surface
- No prose-parsing helper symbols introduced in runtime.py
(extract_candidate_surface_lemmas, surface_lemma,
parse_surface_atoms) — enforces Phase 1 boundary
Performance note: build_graph_from_input now runs on every warm
English turn (previously only when forward_graph_constraint=True).
Phase 1 accepts this cost to make the telemetry universally
available; Phase 2+ can introduce a feature flag if needed.
Validation:
- Cognition eval byte-identical: 100/100/91.7/100
- Full lane: 2864 passed, 3 skipped, 0 failed (+5 over baseline)
- Targeted lane: 72 passed in tests/test_{graph_constraint,
pack_grounding,register_tour_demo,anchor_lens_tour_demo,
orthogonality_tour_demo,realizer_guard_holdout,
composer_graph_atom_equivalence}.py
R5 (ADR-0072) shipped the register *machinery*; ADR-0074's orthogonality
tour proved the axis was decoratively orthogonal to anchor-lens but
inspection of the cognition-eval surfaces revealed two structural gaps:
* On pack-grounded DEFINITION/RECALL/COMPARISON composers, the only
realizer override any register consumed was `disclosure_domain_count`
— which only fires on the no-gloss disclosure path. Under terse_v1,
every gloss-DEFINITION cell was byte-identical to default_neutral_v1.
* The register-tour's `surfaces_vary_at_least_once` gate could be
satisfied by convivial's decorative wrapper alone, masking that
regression in CI.
R6 closes both:
Layering separation (the load-bearing fix):
* New TurnEvent/ChatResponse field `register_canonical_surface` carries
the composer output BEFORE any register transformation. The pipeline
hashes this field for `trace_hash`, preserving R5's invariant that
per-prompt trace_hash is CONSTANT across registers even while
substantive transforms produce visibly different surfaces.
Substantive transforms (`chat/register_substantive.py`):
* terse_v1 gains 3 bool knobs: `drop_provenance_tag`, `compress_gloss`,
`drop_articles` — all pure regex transforms on the canonical surface.
* convivial_v1 gains `append_semantic_domain_clause` — appends a single
bounded "Related: <atom>." clause using the lemma's pack atoms.
* default_neutral_v1 leaves overrides empty; substantive transform is
byte-identical no-op (preserves `byte_identity_null_lift`).
* C1 (ADR-0075) safety preserved: drop_articles refuses to drop
articles following `not` (avoids R3 violations); no knob combination
trips R2/R3.
Strengthened tour gate (`evals/register_tour/run_tour.py`):
* Replaces `surfaces_vary_at_least_once` with two falsifiable claims:
- `terse_substantively_differs_from_neutral_on_pack_grounded_definition`
- `convivial_substantively_differs_from_neutral_on_pack_grounded_definition`
Both restrict to DEFINITION+pack-grounded cells and require
difference beyond whitespace/punctuation.
* New claim `register_canonical_surfaces_identical` directly proves
the layering separation.
* Preserves R5's `all_grounding_sources_identical` +
`all_trace_hashes_identical`.
Pack ratification:
* Loader widened to accept `bool` for closed-set R6 keys
(drop_provenance_tag / compress_gloss / drop_articles /
append_semantic_domain_clause).
* `_KNOWN_OVERRIDE_KEYS` ratify gate extended with same.
* terse_v1 + convivial_v1 reratified with new knobs; companion
mastery reports re-sealed. default_neutral_v1 unchanged.
Invariants pinned:
* `invariant_register_canonical_surface_constant_across_registers` (new)
* `invariant_terse_substantively_distinct_from_neutral` (new)
* `invariant_convivial_substantively_distinct_from_neutral` (new)
* `invariant_realizer_no_illegal_articulation` (C1, preserved)
* `invariant_realizer_guard_byte_identity_on_currently_passing_cases`
(C1, preserved)
Verification:
* `core eval cognition`: 100.0% / 91.7% / 100.0% / 100.0% — byte-
identical under default_neutral_v1.
* `core demo register-tour`: all 5 claims green, exit 0.
* `core demo anchor-lens-tour`: green (no anchor-lens code touched).
* `core demo orthogonality-tour`: green (5/5 claims).
* Full lane: 2858 passed, 1 pre-existing failure
(test_all_preamble_explains_combined_run, carried forward
unchanged from main). 56 new R6 tests across three files.
C1 coherence floor: a deterministic verifier that runs on every
candidate surface produced by the truth path, before assignment to
ChatResponse.surface. Rejects illegal articulations and routes them
to a bounded disclosure string — admission control with a
deterministic fallback, not normalization.
Active rules (R1 deferred during ratification — see ADR):
R2_aux_neg_requires_verb — "<aux> not <wrong-POS>" rejected
R3_be_neg_requires_predicate — "<be> not <verb>" rejected
Fail-open on unknown POS, fail-closed on explicit wrong POS.
Cognition eval byte-identical (100/91.7/100/100).
Original bug class — "Light reveals truth, right?" → "Right does not
thought." — now routes to "I do not have a reviewed articulation for
that yet." with grounding_source=none, walk_surface preserving the
rejected candidate, and telemetry carrying R2_aux_neg_requires_verb.
Files:
generate/realizer_guard.py NEW — pure verifier
chat/runtime.py hook on stub + main paths
chat/telemetry.py serialize guard fields
core/physics/identity.py TurnEvent +2 fields
evals/realizer_guard/run_holdout.py NEW — 6-prompt cluster
tests/test_realizer_guard_*.py NEW — 46 tests (unit/seam/holdout)
docs/decisions/ADR-0075-*.md NEW — ratified
Invariants pinned:
invariant_realizer_no_illegal_articulation
invariant_realizer_guard_byte_identity_on_currently_passing_cases
Lanes (excluding 1 pre-existing TestDemoPreambles failure unrelated
to C1, already present at 4426f38):
smoke 67/67 cognition 120/120(+1s) teaching 17/17
packs 6/6 runtime 19/19 algebra 132/132 full 2792/2793
A single demo that walks the full 3 × 3 × 2 matrix (register × lens
× prompts, 18 cells) and pins five claims simultaneously, packaging
both single-axis invariants into one composition gate.
The single-axis tours assert opposite invariants:
register-tour : per (lens, prompt), trace_hash CONSTANT across
registers (R5 / ADR-0072).
anchor-lens-tour : per (register, prompt), engaged lens diverges
in trace_hash from the unanchored baseline
(L1.4 / ADR-0073d).
Orthogonality-tour packages both claims simultaneously across the
full matrix, plus three surface-level claims that pin the markers
operators actually see.
Composed claims (all five must hold)
A) inner_register_invariant_within_lens
For each (lens, prompt) cell, the three register runs share an
identical trace_hash. (R5 register-tour, applied 6 times:
3 lenses × 2 prompts.)
B) outer_lens_distinctness_within_register
For each (register, prompt) cell where any non-unanchored lens
engages, that engaged lens's trace_hash differs from the
unanchored baseline at the same (register, prompt).
(L1.4 anchor-lens-tour, applied 6 times: 3 registers × 2 prompts.)
C) surface_carries_register_marker_under_convivial
Every convivial cell with a non-empty surface has a non-empty
register_variant_id.
D) surface_carries_lens_annotation_when_engaged
Every engaged cell carries [lens(<id>):<mode>] in surface AND
a non-empty anchor_lens_mode_label.
E) no_substrate_glyph_leak_across_grid
No cell's surface contains Greek/Hebrew/Syriac/Arabic glyphs.
(ADR-0073c gate re-asserted across the full matrix.)
CLI wiring
core demo orthogonality-tour human-readable grid + claims
core demo orthogonality-tour --json structured report
Exit code 0 iff all five claims hold.
Files
evals/orthogonality_tour/__init__.py NEW
evals/orthogonality_tour/run_tour.py NEW
core/cli.py EDIT
- cmd_demo handler wires orthogonality-tour
- demo choices + EPILOG examples updated
tests/test_orthogonality_tour_demo.py NEW (9 tests)
docs/decisions/ADR-0074-orthogonality-tour.md NEW
Sanity check baked into tests
test_engaged_cells_appear_for_both_non_trivial_lenses pins that
grc_logos_v1 engages on knowledge in all 3 registers (3 cells)
and he_logos_v1 engages on truth in all 3 registers (3 cells).
Prevents the lift claims being vacuously satisfied by a future
engagement regression.
Lane evidence
- 9 new orthogonality-tour tests pass.
- core demo register-tour → all_claims_supported: True
- core demo anchor-lens-tour → all_claims_supported: True
- core demo orthogonality-tour → all_claims_supported: True
- python -m core.cli eval cognition → byte-identical 100/100/91.7/100.
- Full lane: 2745 passed / 4 skipped / 1 pre-existing failure
(+9 over L1.4's 2736; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
No runtime / composer / loader / pack / schema changes. Pure demo
consumer of existing telemetry contracts.
L1.3 of the anchor-lens inside-out rollout — first substantive
surface lift on the substantive axis. Two ratified non-trivial
lenses engage on cognition-pack lemmas via the alignment graph,
appending [lens(<id>):<mode>] annotations to the existing
pack-grounded surface.
Two ratified lenses
grc_logos_v1 (Greek substrate)
primary_substrate : "grc"
semantic_domain_preferences: ["logos.episteme.systematic_knowledge"]
cognitive_mode_label : "systematic"
Engages on en "knowledge" via grc-core-cog-021 (ἐπιστήμη) →
en-core-cog-007 alignment edge.
he_logos_v1 (Hebrew substrate)
primary_substrate : "he"
semantic_domain_preferences: ["logos.aletheia.verity"]
cognitive_mode_label : "covenant-verity"
Engages on en "truth" via he-core-cog-002 (אמת) →
en-core-cog-002 alignment edge.
Both ratified under method anchor_lens_lifts_proposition.
Engagement rule (single)
1. Resolve en_lemma → entry_id (cognition pack).
2. For each substrate pack matching lens.primary_substrate, load
alignment.jsonl; find edges where target_id == entry_id.
3. For each such substrate lemma, if any atom in its
semantic_domains ∈ lens.semantic_domain_preferences → engage.
4. No match → None (no annotation; byte-identical surface).
The pivot is shared semantic_domain atoms surfaced via the
alignment graph — exactly the language-neutral commitment from
ADR-0073. Engagement never touches non-English surface text;
entry_ids and atom strings only.
Surface lift
no-lens : "Knowledge is X. pack-grounded (en_core_cognition_v1)."
lens-on : "Knowledge is X. pack-grounded (en_core_cognition_v1) [lens(grc_logos_v1):systematic]."
Annotation between existing provenance and trailing period.
Both metadata fields are ASCII-bounded ≤64 chars at the loader
level, so the annotation can never carry non-ASCII.
Scope deliberately narrow
L1.3 wiring restricted to pack_grounded_surface /
build_pack_surface_candidate (DEFINITION/RECALL only). Other
composers (COMPARISON / CORRECTION / PROCEDURE / NARRATIVE /
EXAMPLE / CAUSE / VERIFICATION) accept the anchor_lens kwarg via
forward-compat default UNANCHORED but do not yet consume it.
L1.3b or later broadens to those intent shapes.
Ratify gate widening
Non-null lenses must:
- have primary_substrate ∈ {grc, he, en}
- have a non-empty cognitive_mode_label
- every preferred atom must exist in at least one lemma of the
named substrate (trust boundary: operators cannot ship a lens
pointing at atoms not on disk).
Method: anchor_lens_lifts_proposition. Null lenses still ratify
under byte_identity_null_lift (L1.2 method).
Seam allow-list widening
Truth-path modules (cognition / trace / pipeline / intent /
propagation / vault / algebra) still refused. Composer-side
imports from chat/pack_grounding.py now permitted — the same way
ADR-0069's R2 widened the register seam.
New invariants pinned (3)
tests/test_anchor_lens_engagement_unit.py (14 tests) — resolver
returns mode label only on intended substrate × en lemma pair;
case-insensitive; engagement None under null lens; synthetic
lens with unmatched atom returns None; annotation is pure ASCII.
tests/test_anchor_lens_lifts_proposition.py (17 tests) — grc
engages on knowledge only, he engages on truth only,
cross-lens isolation, three-way distinctness, replay determinism
per (lens × prompt), register-tour seam holds within each lens
scope (orthogonality CI-pinned, parametrized over 4 lens
choices).
tests/test_anchor_lens_no_glyph_leak.py (5 tests) — hard
block-scoped gate: Greek (U+0370..03FF, U+1F00..1FFF), Hebrew
(U+0590..05FF), Syriac, Arabic. Stylistic punctuation
(em-dash etc.) explicitly allowed; em-dash predates L1.3 by a
wide margin and is not a substrate-leak risk. Tested per-lens
across every cognition case + direct lens-metadata ASCII check.
Lane evidence
74 anchor-lens tests pass (37 from L1.2 + 37 new).
python -m core.cli eval cognition → public 100/100/91.7/100
byte-identical (lens=None / default_unanchored_v1).
core demo register-tour --json → all_claims_supported: True
(R5 seam still holds; L1.3 doesn't perturb presentation axis).
Full lane: 2706 passed / 4 skipped / 1 pre-existing failure
(+37 over L1.2's 2669; the one failure remains
test_all_preamble_explains_combined_run, unrelated).
Files
packs/anchor_lens/grc_logos_v1.json NEW
packs/anchor_lens/grc_logos_v1.mastery_report.json NEW
packs/anchor_lens/he_logos_v1.json NEW
packs/anchor_lens/he_logos_v1.mastery_report.json NEW
scripts/ratify_anchor_lens_packs.py EDIT
LENS_IDS adds grc_logos_v1 / he_logos_v1; gate widened.
chat/pack_grounding.py EDIT
_resolve_anchor_lens_mode, _maybe_append_anchor_lens_annotation,
_substrate_lexicon_by_entry_id, _en_lemma_to_entry_id.
build_pack_surface_candidate + pack_grounded_surface gain
anchor_lens kwarg (default UNANCHORED).
chat/runtime.py EDIT
Thread self.anchor_lens into pack_grounded_surface() call.
tests/test_anchor_lens_pack_seam.py EDIT
Doc-comment updated for L1.3 allow-list.
tests/test_anchor_lens_* NEW (3 files)
docs/decisions/ADR-0073c-anchor-lens-composer-wiring.md NEW
Umbrella ADR-0073 ratified (Accepted); L1.1 content phase
(ADR-0073a) landed. Pure pack enrichment — no runtime code, no
composer change, no test of behaviour. Substrate prerequisite for
the L1.2–L1.4 phases.
Greek additions (grc_logos_cognition_v1, 20 → 29 entries)
Knowledge family (English collapses to `knowledge`):
- ἐπιστήμη logos.episteme.systematic_knowledge
- σύνεσις logos.synesis.insight
(γνῶσις at grc-core-cog-007 unchanged — treated as the
experiential variant by the L1.3 lens config)
Love family (English collapses to `love`):
- ἀγάπη logos.agape.covenant_love
- φιλία logos.philia.companion_love
- ἔρως logos.eros.passionate_love
- στοργή logos.storge.familial_love
Time family (English collapses to `time`):
- αἰών logos.aion.age_era
- χρόνος logos.chronos.clock_time
- καιρός logos.kairos.opportune_moment
Hebrew additions (he_core_cognition_v1, 20 → 23 entries)
- חסד logos.chesed.covenant_loyalty
- שלום logos.shalom.wholeness_peace
- צδק logos.tzedek.right_order
Alignment.jsonl on both cognition-tier packs (previously only the
micro packs carried alignment)
- grc_logos_cognition_v1/alignment.jsonl — 20 edges: three-way core
dyads (word / truth / light / life / beginning / wisdom),
knowledge-family → en collapse, ἀγάπη↔חסד covenant-love pairing
(weight 0.86, Septuagintal), `cross_lang.no_english_collapse`
annotations for love + time families pointing at
`en-collapse-<family>` sentinel ids (weight 0.0).
- he_core_cognition_v1/alignment.jsonl — 7 edges: core dyads to en,
חסד↔ἀγάπη covenant pairing, no-english-collapse annotations for
חסד / שלום / צδק.
Manifest checksums refreshed per CLAUDE.md doctrine
- grc_logos_cognition_v1: b45bcf581cee… → 0f9436675707…
- he_core_cognition_v1: dee1e8c6ad9a… → 22145d008185…
Design decisions
- Existing 20 + 20 lemma atoms untouched — downstream tests /
composers / teaching chains keep referencing the same atoms.
Only new lemmas carry the distinguishing atoms.
- `cross_lang.no_english_collapse` edges are metadata not data
(sentinel target ids, weight 0.0). Their purpose is letting the
alignment graph answer "does English split this family?" without
forcing an artificial English lemma.
- Every new entry carries `adr-0073a:hand_authored:2026-05-19` in
its `provenance_ids` so future audits can find the L1.1 cohort
deterministically.
Verification
- python -m language_packs verify grc_logos_cognition_v1 → OK
- python -m language_packs verify he_core_cognition_v1 → OK
- python -m language_packs compile <both> → 29 / 23
manifold points; spot-check confirms καιρός / צδק resolve.
- python -m core.cli eval cognition → public
100 / 100 / 91.7 / 100 byte-identical (new lemmas sit on disk but
no composer references them yet).
- python -m core.cli test --suite cognition → 120/1 pass
- python -m core.cli test --suite smoke → 67/0 pass
- python -m core.cli test --suite full → 2632 passed
/ 4 skipped / 1 pre-existing failure (test_all_preamble_explains_
combined_run rename drift, unrelated).
- core demo register-tour → exit 0
(R5 seam still holds; L1.1 doesn't touch register pathway).
What L1.1 deliberately does NOT do
- No AnchorLens class (that's L1.2 / ADR-0073b).
- No composer wiring (L1.3 / ADR-0073c).
- No --anchor-lens CLI flag or demo (L1.4 / ADR-0073d).
- No teaching corpus in non-English (post-L1).
Umbrella ADR for the substantive-variation axis that composes
orthogonally against register (ADR-0068..0072). Drafted only;
status Proposed. No code, no pack, no test landed.
Architecture summary
- Anchor lens is the substantive axis: register varies surface text
while keeping grounding_source / trace_hash byte-identical;
anchor lens deliberately moves both because the proposition
itself changes when the substrate changes.
- Pivot is shared `semantic_domains` atoms (already on disk across
grc / he / en cognition packs), not transliteration tables — the
seam stays language-neutral so future substrates compose without
touching anchor-lens code.
- English compound phrasing only at the surface ("knowing-as-
experience", "knowing-as-system"); Greek / Hebrew glyphs live in
audit / provenance fields only. L1.3 invariant
`anchor_lens_no_glyph_leak` is a hard gate.
Four-phase rollout (mirrors R1–R5 cadence)
L1.1 content phase — distinction-bearing lemma additions
(ἐπιστήμη / σύνεσις / ἀγάπη-φιλία-ἔρως-στοργή / αἰών-χρόνος-
καιρός; חסד / שלום / צδק) + alignment.jsonl on the cognition-
tier packs. No code. Prerequisite for every later phase.
L1.2 AnchorLens pack class + loader + `default_unanchored_v1`
sentinel. Null-lift CI invariant pinned.
L1.3 First non-trivial lenses (`grc_logos_v1`, `he_logos_v1`)
wired into chat/pack_grounding.py composers. Proposition-
lift invariant + glyph-leak gate pinned.
L1.4 Telemetry (TurnEvent + ChatResponse gain anchor_lens_id),
`core chat --anchor-lens` flag, `core demo anchor-lens-tour`
asserting trace_hashes_distinct_across_lenses (opposite of
register-tour's claim — both must hold).
Three honest gaps blocking L1.2+
- Distinction-bearing lemmas absent from cognition packs.
- No reviewed teaching corpus for non-English (cognition_chains,
relations_chains, cross_pack_chains all en-only).
- No realizer infrastructure for cross-lingual surface composition.
L1.1 (pure content) closes all three for the cognition tier.
Orthogonality claim — load-bearing
register-tour : per prompt, fix lens, vary register → trace_hash CONSTANT
anchor-lens-tour : per prompt, fix register, vary lens → trace_hash DISTINCT
Both must continue to hold; failure of either breaks the seam.