Commit graph

143 commits

Author SHA1 Message Date
Shay
23c126ebe0 feat(ADR-0131.G): GSM8K coverage probe — honest baseline + capability-first iteration discipline
ADR-0131 deferred GSM8K because it rewards paraphrase flexibility,
which is the deterministic engine's structural weakness. This ADR
re-engages it on architecture-aligned terms: as a *coverage probe*
of the bounded grammar + binding graph, not a promotion gate.

The framing pinned by this ADR:

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

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

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

Baseline pinned by this commit:

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

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

What's in this PR:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Headline (after scoring fix):

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

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

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

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

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

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

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

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

22/22 frontier-methodology tests still pass.

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

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

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

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

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

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

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

Changes in this commit:

  - frontier_runner.py: anthropic adapter now omits ``temperature``
    for claude-opus-4-x (the parameter is rejected by 4.x models);
    openai adapter switches to ``max_completion_tokens`` for the
    gpt-5 / o-series reasoning models; new ``_ollama_invoke`` that
    posts to localhost:11434 with no third-party dep; per-case
    ``latency_ms`` is now captured on every NEW cached response
    (future runs only — these four runs pre-date the patch).
  - comparison.py: ``_load_cases`` composes curated + generated
    (185 cases) instead of curated only; ``_score_provider``
    surfaces ``latency_summary`` when records carry latency_ms.
  - tests: provider-registry test relaxed to "cloud trio is a
    subset of PROVIDERS"; env-key test allows ``_KEY`` (cloud
    secret) or ``_URL`` (local endpoint).
2026-05-23 12:14:06 -07:00
Shay
22deaf02df
feat(ADR-0131.2.B): B2 teaching-corpus enrichment — load-bearing gate (#177) 2026-05-23 11:29:48 -07:00
Shay
eb5fb33252
feat(ADR-0131.3): bounded-grammar word-problem benchmark — lane PASSED 50/50 (#180) 2026-05-23 11:27:04 -07:00
Shay
169cec710e
feat(ADR-0131.1.B): harden symbolic equivalence lane with generated corpus + exact algebra (#169)
* feat(evals): add deterministic symbolic equivalence generated corpus

* feat(evals): add symbolic equivalence replay helpers

* feat(evals): load generated symbolic equivalence corpus

* feat(evals): emit symbolic equivalence replay manifest

* feat(symbolic): support multivariable integer polynomials

* feat(symbolic): support exact rational polynomial coefficients

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

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

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

Reconcile:

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

Lane invariants reaffirmed by the new tests: wrong==0, refusal-first for
truly out-of-scope inputs (symbolic denominator, transcendental, malformed,
negative exponent), determinism via byte-equal report.
2026-05-23 10:47:57 -07:00
Shay
ed759d1b43
feat(ADR-0131.2): teaching-corpus math eval — lane PASSED 30/30 (#172) 2026-05-23 10:44:25 -07:00
Shay
ca3b6011d4
feat(ADR-0131.1.S): sealed holdout for symbolic equivalence v1 (#173) 2026-05-23 10:44:23 -07:00
Shay
a76834cd3f
feat(ADR-0131.1): symbolic equivalence benchmark v1 + lane PASSED (#167)
ADR-0131 Benchmark 1 substrate — the primary discriminator for the
mathematics_logic expert promotion under the architecture-aligned
benchmark composite proposed in ADR-0131.

WHAT LANDED:

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

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

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

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

EMPIRICAL RESULT (the lane PASSED):

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

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

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

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

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

  These are independent of ADR-0131.1; no shared files, no
  cross-PR coordination required beyond final composite gate.
2026-05-23 09:58:26 -07:00
Shay
c13d7e14c4 feat(ADR-0127/0128 integration): pack-aware parser + Path-B trigger evidence
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).
2026-05-23 07:41:50 -07:00
Shay
fde62d713d docs(ADR-0127): units pack + units-aware parser + conversion graph (proposed)
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.
2026-05-23 06:42:39 -07:00
Shay
feeb64818c feat(ADR-0126 P3+P4): graph assembly + decision rule + runner wiring
P3 — generate/math_candidate_graph.py:
  Branch enumeration over per-sentence candidate choices (Cartesian
  product, cap=64). Per-sentence ambiguity tiebreaker via most-grounded-
  slots-wins (transfer beats subtract when 'to Tom' grounds). Decision
  rule: 0 admissible -> refuse; 1 -> emit; >=2 same answer -> emit;
  >=2 different answers -> refuse (preserves wrong==0 on genuine
  ambiguity). End-to-end parse_and_solve(text) -> CandidateGraphResult.

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

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

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

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

Regression: 714/714 existing math + runner tests still green.
ADR-0126 total: 74/74 tests green across P1+P2+P3+P4.
2026-05-23 06:36:13 -07:00
Shay
9d19b8176f feat(gsm8k): ADR-0126 P6 — train-sample runner + exit-criterion gate
Wraps existing math pipeline (parser -> solver -> verifier) against
PR #159's 50-case train sample. Emits deterministic report.json with
per-case verdicts. CLI exit code reflects exit criterion
(correct >= 10 AND wrong == 0).

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

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

Depends on PR #159 (gemini/adr-0126-train-sample). Rebase onto main
after #159 lands.
2026-05-23 06:33:06 -07:00
Shay
ad48ae8777 feat(gsm8k): ADR-0126 P5 — 50-case unsealed train-split sample
Deterministic SHA-256 salt-bound selection from GSM8K train split.
Provides inner-loop gradient signal for ADR-0126 candidate-graph
parser exit criterion (correct >= 10/50, wrong == 0). Unsealed by
design — train split, NOT test/holdout.
2026-05-23 06:10:41 -07:00
Shay
38872f825a feat: ADR-0119.7 — seal GSM8K test as gsm8k_math holdout (Phase 5 substrate complete)
The 1,319 GSM8K test cases are now sealed at
evals/gsm8k_math/holdouts/v1/cases.jsonl.age, age-encrypted to the
ADR-0119.1 recipient. Plaintext never touched disk in the working
tree; only ciphertext is committed.

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

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

What landed

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

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

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

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

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

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

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

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

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

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

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

ADR-0114a obligation status

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:37:54 -07:00
Shay
a65040cb73 merge origin/main and resolve conflicts 2026-05-22 17:37:13 -07:00
Shay
51d3a73589 feat: ADR-0119.6 — depth-curve measurement harness (ADR-0114a Obligation #6) 2026-05-22 17:33:58 -07:00
Shay
c21068ed3e feat: ADR-0119.4 — frontier-baseline comparison (ADR-0114a Obligation #7) 2026-05-22 17:33:28 -07:00
Shay
1a37f97c4f feat: ADR-0119.2 — author 200 grade-school math problems for the GSM8K eval lane (dev + public) 2026-05-22 17:28:00 -07:00
Shay
f9dd650df0 Merge remote-tracking branch 'origin/main' into feat/adr-0119.1-sealed-holdout-fabrication-control
# Conflicts:
#	docs/decisions/README.md
2026-05-22 17:24:32 -07:00
Shay
32c0a90ad9 feat: ADR-0119.1 — seal fabrication_control holdout with age encryption (Obligation #1) 2026-05-22 17:22:46 -07:00
Shay
c1d726179a feat: add ADR-0125 perturbation suite 2026-05-22 17:12:33 -07:00
Shay
9d2a5f22e3 feat: ADR-0118a OOD surface generator 2026-05-22 16:49:40 -07:00
Shay
a0e9833851 feat: ADR-0122 systems_software audit-passed deferred (lane-shape mismatch) 2026-05-22 16:31:59 -07:00
Shay
dc0988416e feat: ADR-0115 Phase 1.2 — rewrite gpd-021 to drop metaphor / mixed units (parser hits 50/50) 2026-05-22 16:11:22 -07:00
Shay
61d3cf8095 feat: ADR-0115 Phase 1.2 — 45 additional dev-set cases (gpd-006 .. gpd-050) 2026-05-22 15:56:46 -07:00
Shay
57b257ca1d feat: ADR-0115 Phase 1.1 — math problem graph schema + 5 seed cases
First Phase of ADR-0114's expert-capability roadmap. Decomposed into four
sub-phases so each lands as its own auditable step:

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

What landed

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

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

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

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

Why this is sticky

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

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

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

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

Signed claim digest: a104cad136f3219df05dc7ce6a78437c02f7b5827cd3cdce568db3acda6a43ed

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

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

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

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

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

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

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

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

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

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

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

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

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

This unblocks the second of ADR-0107's two named blockers. ADR-0110
(math expert-demo re-attempt) now becomes feasible once the math
domain's three lanes have signed-and-digested evidence.
2026-05-22 12:33:56 -07:00
Shay
257fd4503d
feat(evals): ADR-0105 — sealed holdout encryption via age (#108)
* feat(evals): add pyrage dependency

* feat(evals): add sealed holdout path resolution

* feat(evals): implement sealed holdout decryption

* feat(evals): add sealed holdout CLI

* test(evals): add sealed holdout encryption tests

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

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

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

* chore: restore project description

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

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

* test(teaching): cover curriculum proposal construction

* test(evals): add curriculum loop closure contract

* test(evals): add curriculum loop closure runner

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

* ci(lanes): pin curriculum loop closure lane

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

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

* docs(teaching): mark curriculum source activation

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

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

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

* feat(packs): attach fluency lanes to he_core_cognition_v1

* feat(packs): attach fluency lanes to he_logos_micro_v1

* feat(packs): attach fluency lanes to grc_logos_cognition_v1

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

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

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

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

* docs(evals): note Hebrew holdout attachment

* docs(evals): note Koine Greek holdout attachment

* docs: add ADR 0103 placeholder

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

* docs: index ADR-0103 and refresh frontier
2026-05-22 09:43:46 -07:00
Shay
a8c12670ec fix(capability): correct discourse_planner flag catalog + commit-independent public_demo pin
Two pre-existing latent issues fixed:

1. discourse_planner flag catalog drift (test_flag_report failure)

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

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

2. public_demo lane SHA shifted every commit

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- smoke 67/67, cognition eval byte-identical 100/100/100/100, all
  five lanes byte-identical (reviewer_registry 681a2aab..,
  miner_loop_closure 9f071733.., domain_contract_validation f9c06cde..,
  fabrication_control summary 01e1b6b7.., demo_composition 27d83824..)
2026-05-21 19:02:29 -07:00
Shay
d7713b07b1 feat(evals): implement ADR-0096 — Fabrication-Control Eval Lane
First negative-control measure. Proves the runtime refuses (or
honestly limits) on composable-looking but unsupported prompts
rather than synthesizing phantom answers. Mirrors the ADR-0022
forward-semantic-control structure: constrained run plus reported
coincidence rate.

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

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

- new teaching/from_miner.py: from_finding() / from_findings() turn
  ContemplationFinding records (kind=PACK_MUTATION_CANDIDATE) into
  PackMutationProposal candidates with source.kind="miner",
  source.source_id=<miner_id>, status=SPECULATIVE
- proposal_id = SHA-256(canonical(miner_id, finding, revision))[:16]
  — same inputs → byte-identical proposal_id; different miner_id or
  revision → different id
- identity-pack defense AT CONSTRUCTION: reuses teaching.review.
  _is_identity_override() against finding.subject AND
  finding.proposed_action; miner-sourced identity-override attempts
  never reach the proposal log
- pluggable ReplayEquivalenceChecker Protocol with ReplayEquivalenceResult;
  NoOpReplayChecker default explicitly notes "deferred to production
  checker"; production checker integration is downstream of this ADR
- from_findings() batch path collects identity-override and
  replay-equivalence rejections in a typed rejection log rather than
  raising, so a mixed batch can proceed with audit evidence
- serialize_proposal_emitted_event() emits ADR-0040-compliant redacted
  telemetry shape: type, proposal_id, source.serialize(),
  epistemic_status only (no raw subject/correction_text)
- 22 unit tests covering positive construction, identity defense in
  subject+proposed_action, malformed input, determinism (same inputs,
  different revision, different miner_id, batch stream), replay
  pre-gate (single + batch), telemetry redaction, and the structural
  grep gate enforcing miner_proposal_single_review_path (only
  teaching/review.py and teaching/store.py may promote to COHERENT)
- new evals/miner_loop_closure/ lane: 6 case classes (positive_basic,
  identity_override_subject, identity_override_action,
  replay_equivalence_failed, wrong_finding_kind, determinism) passing
  6/6 with byte-identical SHA-256 across runs
- smoke 67/67, teaching 17/17, cognition 120/121 (1 pre-existing skip);
  cognition eval byte-identical 100/100/100/100
2026-05-21 18:18:51 -07:00
Shay
afdd2ee413 feat(capability): implement ADR-0092 — Reviewer Registry v1
Closes the load-bearing gap blocking every reasoning-capable claim
under ADR-0091: docs/reviewers.yaml was previously `reviewers: []` and
unparsed. Now schema-validated at v1, with a bootstrap shay-j entry
self-sealed via provenance.

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

The pre-existing test_flag_report_tracks_default_off_flags failure is
unrelated (discourse_planner flag default) and not introduced here.
2026-05-21 18:01:24 -07:00
Shay
cc3beede53 evals/industry_demos: add run_all.py suite runner
ADR-0046 — Industry Demo Suite runner.

Adds `evals/industry_demos/run_all.py`, the single-entry-point script
that executes all three falsifiable demos in sequence, collects their
structured JSON evidence, and exits 0 iff every demo passes.

Design choices:
- Runs each demo in an isolated try/except so a crash in demo_01 does
  not suppress evidence from demo_02 and demo_03 (fail-open evidence
  collection, fail-closed exit code).
- Prints a human-readable banner + structured JSON evidence per demo.
- Prints a final machine-readable JSON summary `{"all_passed": bool,
  "results": [...]}` on stdout for CI consumption.
- Exits 0 when all_passed, 1 otherwise.
- Zero new dependencies: only stdlib + the same imports each individual
  demo already uses.

Also updates `evals/industry_demos/__init__.py` to document the new
runner in the module docstring.

Verification path:
  python -m evals.industry_demos.run_all
  echo $?   # 0 on full pass
2026-05-21 08:23:29 -07:00
Shay
f6f8ee603f
feat(evals): per-intent register-firing diagnostic + CI gate + tests (#103)
Replaces the per-pack-aggregate diagnostic landed at 58ac780 with a
per-intent matrix decomposition authored by Codex on a parallel
worktree. Codex's design directly answers the original motivating
question — "which packs' marker pools don't fire on which intent
shapes" — that the aggregate version flattened.

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Codex <noreply@openai.com>
2026-05-21 07:05:23 -07:00
Shay
58ac7805bd feat(evals): register firing diagnostic — opening/closing fire rates
Adds evals/register_diagnostics/run_firing_diagnostic.py. For every
ratified register pack, runs every cognition case and reports
whether the opening and closing markers actually fired (non-empty
selection from the bucket).

Why this exists.  The 100-pack widened tour revealed that some packs
collapse to baseline on certain prompts — their non-empty marker
entries simply don't get selected by the SHA-256 seed for that
particular (seed_text, register_id, turn_idx) combination. Without
a diagnostic, collapses are only visible by eyeballing surfaces.

The diagnostic surfaces three pack categories:
  * silent           : neither marker ever fires (empty buckets) —
                       legitimate for terse_v1, succinct_v1, the
                       A_depth knob-only registers; suspicious
                       elsewhere
  * sometimes_firing : 0 < observed_rate < 1 — '' is in the bucket
                       so the register "feels lighter"; quiet turns
                       mixed in (e.g. socratic_v1, convivial_v1)
  * always_firing    : opening_observed_rate == 1 — no '' in bucket;
                       most expressive (no current packs hit this on
                       both buckets)

For each (pack, cognition lane) cell it reports bucket_rate (the
structural ceiling, fraction of non-empty entries in bucket) and
observed_rate (fraction of cases where the marker actually fires).

Findings on the current 100-pack catalog:
  * 92 packs: sometimes_firing — most pack designs working as
    intended; observed_rate tracks bucket_rate within statistical
    noise of the 45-case sample
  * 8 packs:  silent
      - 7 by design (default_neutral/terse/precise/formal/succinct/
        expansive/exhaustive — A_depth + the seven-ratified neutral
        anchors)
      - 1 flagged for review: expert_v1 (D_posture); only D_posture
        pack without populated marker buckets — may have been an
        authoring miss given peer/mentor/student/scholar/practitioner/
        novice/narrator/journalist/elder are all populated
  * 2 packs:  closings 0% (assertive_v1, blunt_v1) — side effect of
    removing the bare '.' closing in the previous commit, leaving
    only [""] in the closing bucket. A future content pass may want
    to add ' — period.'-style separator-prefixed entries to round
    out the register without re-introducing the punctuation bug.
  * 1 pack:   openings 0% (epigram_v1) — by design? epigrams are
    short and pointed; closings still fire 42.2%

Usage:
  PYTHONPATH=. .venv/bin/python -m evals.register_diagnostics.run_firing_diagnostic
  PYTHONPATH=. .venv/bin/python -m evals.register_diagnostics.run_firing_diagnostic --json > firing.json

Operator-only utility; mirrors the eval-artifact convention used by
evals/register_tour/run_tour.py and evals/anchor_lens_tour/run_tour.py.
2026-05-21 06:58:05 -07:00