Commit graph

246 commits

Author SHA1 Message Date
Shay
32f7441d28
Merge pull request #581 from AssetOverflow/feat/phase2a-comprehension-reader
feat(comprehend): general comprehension reader + set_membership end-to-end (Phase 2a)
2026-06-05 16:48:31 -07:00
Shay
96b9b942e6 feat(comprehend): general comprehension reader + set_membership end-to-end (Phase 2a-r1/r2)
Disciplined Path β (field decode α was empirically falsified). Reads S-P-O
structure SYMBOLICALLY from the token sequence via domain-agnostic templates
keyed on FUNCTION WORDS + ORDER, mints content as MeaningGraph entities/relations,
parse-or-refuse (wrong=0 at the comprehension layer).

Templates (set_membership): 'X is a Y' -> member; 'all Xs are Ys' -> subset;
'is X a Y?' / 'are all Xs Ys?' -> queries; definite-NP ('the X is a Y');
conservative singularization incl. irregulars (people->person), refusing
unknown morphology rather than guessing.

End-to-end: prose -> comprehend -> project -> INDEPENDENT set_membership oracle
-> answer vs gold. Result on the real v1 lane: 8 correct / 0 wrong / 0 refused
(full coverage, wrong=0). Cross-content generality (animals/professions/geography)
asserted. 14 reader unit tests + 3 end-to-end tests, each bites under its
violation. Additive only; invariants + capability index green.
2026-06-05 16:32:34 -07:00
Shay
50ba1183b2
Merge pull request #580 from AssetOverflow/evals/capability-baseline-freeze
evals: freeze capability-index baseline + digest regression guard
2026-06-05 16:23:52 -07:00
Shay
2fc1d73a8f evals: freeze capability index baseline 2026-06-05 16:14:30 -07:00
Shay
96cb5b34bc evals: add staged independent gold lanes 2026-06-05 16:03:26 -07:00
Shay
514c6c52ca feat(evals): AGI-roadmap Phase 1 — cross-domain capability index (the MEASURE yardstick)
The instrument that gates every later "more capable" claim and makes "general,
not narrow" a number. evals/capability_index/ composes the self-loading
independent-gold reasoning lanes (deductive_logic, dimensional, relational_metric)
into one report with honest, un-gameable axes:

- accuracy (of committed answers; wrong stays 0 in assert mode),
- coverage (attempted-not-refused),
- coverage_geomean — the headline: geometric mean of per-domain coverage, which is
  0 if ANY domain has zero coverage, so a narrow per-domain win cannot move it; it
  rises only when breadth rises,
- capability_score = coverage_geomean × accuracy, HARD-GATED to 0 if any domain
  committed a wrong answer (assert-mode invariant),
- a deterministic digest (the replayable baseline the autonomous loop must climb).

Baseline (today): score 0.9196, accuracy 1.0, breadth 3, wrong_total 0 — high
because all three composed lanes are formal/structured; when comprehension-gated
NL domains join, the geomean will honestly drop to expose the breadth gap (the
instrument working). Adapters surface any lane that fails to run as not_covered —
no silent drop (proven: it caught a deductive-report shape mismatch mid-build).

Pure aggregation + the geomean anti-gaming property + the wrong=0 hard gate are
unit-tested; a real-composition integration test asserts wrong=0 + breadth=3.
10 tests + 52 architectural invariants pass. Additive (new evals/ package).
Part of docs/analysis/AGI-candidacy-autonomous-improvement-roadmap-2026-06-05.md (Phase 1).
2026-06-05 15:17:46 -07:00
Shay
f2dac1dc5c feat(identity): L11 identity continuity — same identity across reboot, not just same bytes
Builds the L11 lived-spine half on top of Shape B+ T-resume: prove the
continuous/resumed life is the SAME identity, with a content-derived, hash-chained
lineage and a falsifiable behavioral proof.

- core/engine_identity.py (L11-1): EngineIdentity = sha256 of the ratified
  PERSONALITY substrate (identity/safety/ethics/register/anchor-lens pack files)
  + code revision. Content-derived, NOT entropy — same substrate => same identity
  (cross-engine portable). The "who am I" hash; bumped by a ratified identity
  change, NOT by lived learning (that is experience, carried by Shape B+).
- engine_state + chat/runtime (L11-2): every checkpoint manifest stamps
  engine_identity + parent_engine_identity (git-like lineage). Stable substrate
  => identity == parent (one continuous life); a ratified change => the bump.
- chat/runtime + config (L11-3): on reboot, recompute identity and compare to the
  stamped one. Mismatch (substrate changed while down) surfaces a warning +
  identity_continuity_break flag; strict_identity_continuity (opt-in) refuses
  (IdentityContinuityError). Default warns — reboot is recovery, not control flow
  (ADR-0157); the operator must not be bricked by a benign ratified pack swap.
- tests (L11-4): the proof. Continuity is SUFFICIENT (byte-identical resume +
  no break under a fixed identity), identity is LOAD-BEARING (distinct packs =>
  distinct hashes), and the CONTRAPOSITIVE holds (resuming under a different
  identity raises the break). Same identity <=> continuous; different => break.

Test hygiene (required by L11's always-on identity stamping): conftest isolates
the default engine_state dir per test; the refusal-calibration cold-start probe
uses no_load_state=True. Both prevent cross-test identity-lineage pollution.

19 dedicated tests; curated smoke green (no spurious break warnings).
2026-06-05 13:52:57 -07:00
Shay
5ed9fbb8e7 feat(persistence): Shape B+ Phase D+E — opt-in lived-state persistence; reboot transparent
The load-bearing L10 milestone: with resume mode enabled, a reboot resumes the
SAME life. Wires SessionContext.snapshot/restore (Phases A-C) into the
engine-state checkpoint and flips the L10 spike's P2b oracle to transparent.

Persistence is OPT-IN (RuntimeConfig.persist_session_state, default False): it is
a deliberate always-on-runtime mode, and per-turn snapshotting has an O(turns)
cost, so demos / evals / one-shot runtimes do NOT pay for resume they don't use.
This keeps every existing ChatRuntime byte-for-byte unchanged (no perf tax, no
pinned-lane SHA drift, no test breakage); only the L10 continuity lane and the
production L10 process enable it.

Phase D (wiring):
- core/config.py: persist_session_state flag (default False).
- engine_state/__init__.py: bump _SCHEMA_VERSION 1->2; add save_session_state /
  load_session_state (atomic, ADR-0156). v1 checkpoints still load (1 <= 2) with
  no session_state -> fresh session.
- chat/runtime.py: when persist_session_state, checkpoint_engine_state saves the
  session snapshot BEFORE the manifest (manifest = the commit marker / WAL force
  boundary); _load_engine_state restores it into self._context.

Phase E (flip the oracle):
- evals/l10_continuity/runner.py: the continuity lane forces persist on (it IS
  the resume-mode lane).
- tests/test_l10_continuity.py: test_p2b_documents_current_resume_gap ->
  test_p2b_reboot_is_transparent (asserts post_reboot_transparent, divergence
  None). predicates.py / runner.py / contract.md: P2b is now the
  resume-as-same-life guard.
- tests/test_adr_0146_engine_state.py: manifest schema_version 1 -> 2.

Validation: full spike (P1 closure, P2a determinism, P2b NOW TRANSPARENT, P3
bounded, P4 crash-recovery determinism + commit point, P5b/P5c) +
reboot-restores-lived-state + v1 back-compat + ADR-0146, all green;
[run K -> reboot -> run M] byte-identical to [run K+M]. With persistence off
(default), the curated smoke + showcase budget + pinned lanes are unchanged.

Closes the A->E Shape B+ scope (docs/analysis/L10-shapeBplus-persistence-scope-2026-06-05.md).
2026-06-05 13:17:30 -07:00
Shay
23bc28caf9 feat(evals): L10 continuity spike — falsifiable long-horizon soak lane
Build evals/l10_continuity/, the empirical gate between the two L10 targets
(T-resume: provable same-life resume; T-experience: continuous experiencing
field-life). Drives the REAL turn loop (ChatRuntime + CognitiveTurnPipeline)
over a deterministic in-vocab corpus, with reboot and orphan-crash legs, and
evaluates falsifiable predicates over recorded evidence. Additive only; no
existing file touched; read-only over the runtime; no serving-path import.

Predicates (each with a *_holds real-soak test AND a *_bites mutation test, per
the CLAUDE.md schema-as-proof discipline):
- P1 closure: versor_condition < 1e-6 every turn (green guard).
- P2a determinism: two independent runtimes -> byte-identical trace_hash.
- P2b reboot transparency (the diagnostic): a reboot never alters pre-reboot
  turns (hard guard); post-reboot transparency is MEASURED and today FALSE --
  the mechanical proof that Shape B (ADR-0146) discards the lived field/vault,
  i.e. "many lives sharing a checkpoint". A pinned test flips if persistence is
  ever added, forcing a doc update so the gap can't close silently.
- P3 bounded resources: vault grows linear-bounded/monotonic (RSS recorded).
- P4 crash recovery: two recoveries from one checkpoint converge (determinism)
  + commit-point/ARIES force boundary (recovered turn_count == committed) +
  atomic-write survives mid-os.replace kill (ADR-0156).
- P5b anchor stability (T-experience crux): field anchors without COLLAPSE
  (dist_to_anchor not -> 0) or FREEZE (turn_movement not -> 0); the long-horizon
  test of the sanctioned _session_anchor_pull (alpha=0.05). Thresholds measured.
- P5c coherence: surfaces stay non-empty and not collapsed to one output, over
  more than one corpus cycle.
- P5a recall precision@k: recorded as not_covered (needs a held-out probe set).

report.py assembles the panel into a structured report with a hardware-stable
deterministic_digest (trace_hash sequence + verdicts; excludes RSS/wall-clock)
as the freeze handle. Run: python -m evals.l10_continuity [n_turns] [reboot_turn].

24 tests pass; adversarially reviewed across 4 lenses (bite-discipline,
invariant/trust-boundary, honesty/determinism, correctness) before landing.
2026-06-05 11:14:17 -07:00
Shay
51c6852b0c test(l10): add independent-gold adversarial logic fixtures 2026-06-05 09:08:23 -07:00
Shay
5f274b75b7 feat(deductive): binary relations + multi-variable grounding (finite propositional)
Extends evals/deductive_logic/grounding.py from unary predicates / single-var rules to
binary relations + multi-variable universal rules, still by FINITE PROPOSITIONAL
grounding into the regime the ROBDD engine + the independent truth-table oracle both
decide. wrong==0 stays structural. This is the real capability step a RuleTaker/
ProofWriter-style mirror needs (the unary fragment alone is trivial).

- atom_n lowers pred(a,b) -> pred__a__b; arity-1 is byte-identical to the old atom, so
  the live unary panel lowers unchanged (proven by an exact-string back-compat test).
- multi-variable universal rules ground over n^k assignments — transitivity now decides.
- range-restriction: a rule with a head variable unbound in the body refuses (unsafe_rule)
  — it grounds soundly but is outside the clean regime real benchmarks use.
- typed refusals: arity>=3/functions, explicit quantifiers, variable-free rules, bounds.

Honest ceilings (documented in docs/analysis/relational-grounding-extension-2026-06-04.md):
- THE binding constraint is the GOLD, not the grammar: the truth-table oracle is
  O(2^atoms), so grounding refuses above MAX_GROUND_ATOMS=20 => binary problems cap at
  ~4 entities/predicate. A real lift needs a 2nd genuinely-independent sub-enumeration
  oracle (not built).
- OPEN-WORLD only: RuleTaker/ProofWriter's main splits are closed-world + NAF; a future
  adapter MUST refuse CWA/NAF (mapping CWA "False"->"refuted" is a wrong=0 breach).
- arity <= 2, function-free.

Validated: held-out differential fuzz (400 random binary problems, oracle-golded) = 0
engine/oracle mismatches; unary back-compat byte-identical; INV-25b reproducibility green;
deductive lane wrong=0 16/16; smoke 87.
2026-06-04 20:17:33 -07:00
Shay
5c77c9eece feat(field-wedge): ablation verdict — field is decoration on additive (C3) (Phase W.2)
The falsifiable experiment's measurements #2 (ablation) and #3 (diversity). Builds the
competent, code-disjoint SYMBOLIC reader (the control arm AND the C3 capability path)
and the ablation instrument that runs both readers through the real
verify_tier2_agreement gate.

VERDICT: C3 — the field is decoration on this domain (a sanctioned, honest negative):
- field_wrong_commits = []  (wrong=0 holds; the per-step drift guard refuses bad ints)
- field_caught_symbolic_errors = []  (the field caught ZERO symbolic errors)
- per-class diversity = 0 everywhere (both readers agree and are both correct)
- the only admitted-set change is the field LOSING coverage at the precision ceiling.

Insight: on forward-substitutable relations, geometric translation IS arithmetic
addition, so there is no metric over-determination for the field to exploit — field and
symbol are common-mode (Knight-Leveson), not a genuine second derivation. This is the
deductive finding's twin: logic was combinatorial (field can't earn it), additive is
arithmetically trivial (field adds nothing). The field needs metric-nontrivial AND
arithmetically-hard structure to earn a reasoning role — dedicated research, not
near-term. Field-as-reasoner is NOT earned; no field vote enters any serving path; the
field stays a servant. Capability path = symbolic (C3), not shipped here.

- generate/relational_symbolic_reader.py: competent independent reader (pure int).
- evals/relational_metric/ablation.py: the reusable decoration instrument.
- docs/analysis/field-wedge-ablation-result-2026-06-04.md: the recorded verdict.

All prior artifacts STAY (field reader = real wrong=0 read demo + 3rd panel domain).
Green: full wedge suite 104; 53 architectural invariants.
2026-06-04 19:44:22 -07:00
Shay
145d797196 feat(field-wedge): geometric field reader — relational-metric lane wrong=0 (Phase W.1)
Measurement #1 of the field-reasoner falsifiable experiment: does the CL(4,1) field,
given an honest metric encoding, read forward-substitutable quantitative-relational
problems from TEXT with wrong==0? It does — 14/15 correct, 0 wrong, 1 refused
(precision ceiling), scored against an independent arithmetic oracle.

- generate/relational_field_reader.py: reads problem text into conformal points on
  the e1 number line; additive/part-whole relations are conformal TRANSLATOR versors
  (versor_apply(T_delta, embed[x]) == embed[x+delta], exact); the answer reads back
  by projective dehomogenization. Refusal-first: fences multiplicative/ratio (the
  sign/orientation-blind cases), the precision ceiling, non-forward-substitutable
  references, negatives. A per-step exactness self-check turns any f64 translator
  drift into a refusal (precision_drift) — it NEVER commits a wrong integer. Its
  parser is an independent reimplementation importing no generate.derivation/math_*.
- evals/relational_metric/: independent arithmetic oracle (computes gold from the
  STRUCTURE, shares no code with the reader), 15-case fixture, and a runner that
  enforces gold integrity + wrong==0.
- INV-25: relational_metric registered in INDEPENDENT_GOLD_LANES (oracle proven
  code-disjoint from the field reader and the algebra engine). The independently
  golded panel is now three domains: deductive, dimensional, relational-metric.

Green: smoke 87, 53 architectural invariants, 16 new tests; deductive + dimensional
lanes unperturbed (wrong=0).
2026-06-04 19:34:43 -07:00
Shay
4c6290f773
Merge pull request #557 from AssetOverflow/feat/dimensional-reasoning-domain
Dimensional-reasoning lane — 3rd diversity-panel domain
2026-06-04 16:48:55 -07:00
Shay
96c1d4bcee feat: dimensional-reasoning lane — 3rd diversity-panel domain
The first non-GSM8K consumer of the binding-graph interlingua's unit algebra as a
load-bearing reasoner: given two units and an operation, decide the result's
dimension. SUT = generate.binding_graph.units; gold = evals/dimensional/oracle.py,
a genuinely INDEPENDENT dimensional reasoner (own unit->base-exponent table, own
exponent arithmetic, own canonical-string renderer; shares no code with the SUT).

12 cases (area / speed / wage / mass-density / dimensionless / 2 refused) gated by
SUT == oracle == gold (wrong=0). Registered in INV-25's INDEPENDENT_GOLD_LANES,
proving the independent-gold discipline generalizes to a SECOND oracle.

This is the 3rd structurally-distinct golded domain (logic / grounding / dimensional)
— the anti-overfit >=2-domain panel is now real, and the interlingua is load-bearing
beyond GSM8K.
2026-06-04 16:38:56 -07:00
Shay
3e2a52870d feat: Phase 2 — finite-entity grounding compiler + Phase 1.5 finding
The first comprehension->structure compiler: evals/deductive_logic/grounding.py
lowers a typed finite-entity problem (finite entities + unary predicates +
single-variable universal rules) into the propositional regime the ADR-0206
entailment operator decides, refusal-first with a closed typed reason vocabulary
(unsafe_symbol / unknown_entity / unsupported_predicate_arity / unsupported_
quantifier / malformed_case / empty_case) and collision-safe atom slugging.

finite_entity/v1/cases.jsonl: 8 cases with INDEPENDENT (oracle-derived) gold
(4 entailed, 2 unknown, 1 refuted, 1 refused) — chained rules, conjunctive
bodies, negative heads, inconsistent premises. 20 tests gate engine==oracle==gold.

This is the second diversity-panel domain (distinct comprehension, same checkable
substrate) — the universal-structure thesis validated on a different problem shape,
with the anti-overfit >=2-domain discipline now live.

Phase 1.5 finding recorded: a clean geometric/algebraic propositional decoder
agrees 716/716 with the oracle but is O(2^n) (enumeration-class), so logic is the
wrong first domain for field-as-reasoner; the wedge redirects to quantitative-
relational structure where the field is the natural non-redundant decoder.
2026-06-04 16:32:03 -07:00
Shay
a447dce5d1 feat: ratify independent-gold invariant (INV-25) + SHA-pin deductive lane
INV-25 makes the GSM8K lesson structural: no capability claim is valid unless
its gold is computed by a procedure sharing no code with the system under test.
Three meaningfully-failing checks (proven able to fail): 25a oracle imports no
SUT module (AST); 25b every committed deductive gold is reproduced by the
independent oracle AND matched by the engine; 25c an unsound engine disagrees
on committed cases.

SHA-pin the deductive lane (deductive_logic_v1, dev+holdout+external 716/716,
wrong=0, refused=0) via a deterministic --report writer + run_as_module (the
lane dir's local generate.py shadows the package in script mode) + CLAIMS regen.

Fix drift the review surfaced: contract.md + pivot doc claimed an 8,000/7,340
fuzz and an 'external mirror' -> corrected to the real gated 3,000-case fuzz
(2,796 definite) and 'hand-authored, NOT a published-benchmark mirror'; pivot
doc GSM8K holdout 0->5 (all 5 are R1 reconstruction; composer scored 0).

Validated: smoke 78, deductive 23, proof 29, invariants 44, lane-sha+claims 9.
2026-06-04 15:56:28 -07:00
Shay
7155f5ab34 feat: add gsm8k r1 reconstruction 2026-06-04 13:25:11 -07:00
Shay
3389cc68c3 feat: add deductive proof evidence gates 2026-06-04 08:37:51 -07:00
Shay
48827f281a feat: sound+complete propositional entailment operator + deductive-logic lane
The first SIZEABLE, honestly-verified reasoning capability — built on CORE's
own terrain (exact, verifiable, deterministic), not GSM8K's stochastic terrain.

THE OPERATOR (generate/proof_chain/entail.py, ADR-0206):
- evaluate_entailment(premises, query) -> entailed | refuted | unknown | refused.
- The multi-hop inference operator evals/symbolic_logic/gaps.md said did not exist
  ("no operator that takes A->B, B->C and returns A->C") and ADR-0205 deferred.
- Built on the ADR-0201 ROBDD canonicalizer: premises |= Q iff (AND P) -> Q is a
  tautology. SOUND AND COMPLETE for propositional logic, not single-step.
- wrong=0 is structural: an exact tautology check refuses (LogicError) on
  malformed / out-of-decidable-regime (quantified/predicate) input, never guesses.

THE HONEST METRIC (evals/deductive_logic/):
- holdout v1 (500 cases): 500 correct / 0 WRONG, incl. 227 non-trivial deductions
  (117 entailed + 110 refuted). dev (200): 200/0.
- Gold from an INDEPENDENT truth-table oracle (oracle.py) sharing zero code with
  the engine. 8,000-case fuzz across two independent decision procedures:
  0 disagreements. This is the soundness evidence the GSM8K composer could never
  produce (it could not separate its 2 right from its 87 wrong answers).
- contract.md states the load-bearing honesty boundary: PROPOSITIONAL ONLY, and
  given-formulas (NL->logic grounding is a separate later layer, kept out of scope
  so we do not re-step on the GSM8K natural-language rake).

TESTS (17, all green): classic inference shapes (MP, multi-hop chain, modus
tollens, disjunctive/hypothetical syllogism, conjunctive rules, genuine unknown),
refusal boundary (inconsistent / quantified / predicate / malformed), and a
deterministic engine-vs-oracle fuzz cross-check.

Pure new module — does NOT touch serving. Smoke 73 passed; invariants 40 passed.
2026-06-04 07:47:01 -07:00
Shay
f301f3a928
Merge pull request #548 from AssetOverflow/feat/holdout-dev-set
feat(eval): held-out dev lane — honest iteration metric (real GSM8K capability = 0/500)
2026-06-04 07:14:58 -07:00
Shay
f56a0cfdba
Merge pull request #546 from AssetOverflow/docs/reconcile-current-state-2026-06-03
docs: reconcile current-state claims after GSM8K + sensorium progress
2026-06-04 07:14:39 -07:00
Shay
d81084ffe3 feat(eval): held-out dev lane — the honest iteration metric (real capability = 0)
The 2026-06-04 sealed-breach post-mortem proved the 50-case train_sample has ZERO
predictive validity (its 4 "correct" are overfit; they hid a 5-wrong sealed breach).
This adds the instrument we never had: 500 real GSM8K cases CORE was NOT built on —
the train split minus the 50 train_sample, deterministic sha256(question) sort.

Same scorer as train_sample + the sealed lane, so the three are directly comparable:
  train_sample(50): 4/0/46   holdout_dev(500): 0/0/500   sealed test(1319): 0/0/1319

Real GSM8K capability is 0%. The 4 train "correct" generalize to NOT ONE of 500
held-out cases. wrong=0 holds (refuses, never confabulates).

- evals/gsm8k_math/holdout_dev/v1/: cases.jsonl (500), runner, report (0/0/500), README.
- tests/test_holdout_dev_lane.py: floor (wrong==0, forever) + baseline snapshot (0/500).

Discipline: iterate here (open, large enough to resist trivial overfit); the sealed
test stays the final arbiter. wrong=0 is the floor; correct rising is the goal;
"refuse everything" is the FAILING baseline to beat, not a pass. Non-serving (eval only).
2026-06-04 02:30:42 -07:00
Shay
763c46d2f4 fix(gsm8k): disable unsound serving bridges — restore sealed wrong=0 (0/5 -> 0/0)
The FIRST real sealed measurement (operator-decrypted 1,319 held-out GSM8K)
found `0 correct / 5 WRONG` — a wrong=0 breach hidden for weeks because the
working metric was the 50-case train sample the bridges were tuned to. Bisection
isolated it to the product_bridge serving promotion (ADR-0195).

- generate/math_candidate_graph.py: REMOVE both serving promotion bridges
  (product_bridge + goal_residual/ADR-0207 §5 step 2). Serving = main-graph-only.
  Restores sealed 0/0/1319 (verified by bisect: disabling product_bridge -> 0 wrong).
  Production modules remain in generate/derivation/; only serving promotion is
  unwired, until a gate is proven wrong=0 on the SEALED set (never the train sample).
- Honest numbers everywhere: train_sample 7/43/0 -> 4/46/0 (the bridges' "correct"
  was train-overfit). report.json + coverage probe regenerated. 7 ADR test lanes
  de-pinned from the inflated count. corpus: cv-0005 (R4) reverts to refuse; cv-0020
  (a "baseline control" that solved ONLY via product_bridge) reclassified.
- docs/claims_ledger.md: dated wrong=0-breach-and-remediation note + the rule:
  the train_sample number had ZERO predictive validity for the exam; never the score.
- docs/analysis/gsm8k-lift-program-strategy: the program to actually move the 1,319.

NOTE the exit gate stays `correct>=10 AND wrong==0` — refusing-everything is an
explicit FAIL, not a wrong=0 pass; serving still commits (main graph). Verified:
broad regression 848 passed, smoke 73 passed.
2026-06-04 01:55:05 -07:00
Shay
7dc9dbaa6a docs: reconcile train sample README metric 2026-06-03 22:42:35 -07:00
Shay
ad9cf57069 feat(r4): flip cv-0005 to serving — train_sample 6/44/0 -> 7/43/0 (ADR-0207 §5 step 2)
Wires the R4 goal-residual production to serving via
resolve_promotable_goal_residual (math_candidate_graph.py, mirroring
product_bridge). cv-0005 / train_sample 0037 now solves on serving as
goal - Σprogress = 10 - 3 - 4 = 3. First Phase-5b composition lift on serving.

wrong=0 preserved on every runnable surface:
- train_sample 6/44/0 -> 7/43/0 (0037 added; 6 prior correct intact; wrong=0).
- Fires on 2/455 visible GSM8K cases, both correct, ZERO wrong.
- Gain-goal divergence firewall proves it reads the GOAL, not a possession.
- smoke 73, math+invariants 53, derivation/pool/practice 341, corpus, all green.

Lockstep updates (the ratified metric move, 6/44/0 -> 7/43/0):
- report.json; 7 ADR test lanes that pinned 6/44/0; corpus cv-0005 baseline
  fields + snapshot (4/18 -> 5/17) + contract; plan-doc cv-0018 control fix.

⚠ SEALED MEASUREMENT REQUIRED — NOT DONE. The sealed 1,319 (encrypted, not
CI-reproducible) is the real bar (ADR-0207 §6) and was NOT re-measured. The
operator/CI must decrypt+run it and confirm sealed wrong==0; if wrong>0, revert
the resolve_promotable_goal_residual block (isolated). See
docs/handoff/sealed-measurement-obligation-2026-06-04.md.
2026-06-03 22:20:12 -07:00
Shay
9cabeeb40d Add sensorium eval and governance runway 2026-06-03 20:53:05 -07:00
Shay
d9fc7f9e56
Merge pull request #540 from AssetOverflow/codex/vision-eval-environment-sensorimotor
[codex] Add vision evidence and sensorimotor contracts
2026-06-03 20:41:10 -07:00
Shay
2d2b096784 Add vision evidence and sensorimotor contracts 2026-06-03 20:27:46 -07:00
Shay
94bf1be1bc docs: ratify ADR-0207 — GSM8K comprehension/composition substrate
Consolidating ratification of the GSM8K design of record. Ratify the built
comprehension/derivation substrate, freeze the serving regex recognizer/
injector path to lexemes + refusal-only, pin Phase 5b execution to
WIRING -> COMPOSITION -> LEXICON.

- ADR-0207: new consolidating decision (Accepted, ratified 2026-06-03).
  Supersedes ADR-0163 §Phase B-E + ADR-0136 regex sentence-template
  prescriptions. Freeze + wrong=0 gates (22-case corpus + sealed 1,319).
- ADR-0164/0165/0174/0178/0179: -> Accepted (ratified by ADR-0207,
  2026-06-03). 0164 keeps its implementation clause (Phase 1+2 shipped;
  remainder per §5) so Accepted != fully built.
- composition_validation/v1: 20 -> 22 cases (2nd R4/R5 positives,
  dataset-sourced golds), +contract invariants 6-7, +dataset-gold test.
  Baseline 4/18/0; 47 passed.
- docs/analysis: extraction-richness audit (read-only) reconciling
  ADR-0179 to the tree (EX-1/2/4/5/6 landed; EX-3 deferred).

Non-serving (evals/docs/tests only). train_sample 6/44/0 unchanged;
no-ref <N> times hazard stays refused. GB3b/0136 untouched.
2026-06-03 19:42:47 -07:00
Shay
a8027ca34d test(gsm8k): add composition validation corpus 2026-06-03 16:30:29 -07:00
Shay
0d05d3a1b3 fix(refusal-taxonomy): parse new refusal-reason format; reconcile to reader gains
build_refusal_taxonomy_cases._STATEMENT_RE only matched the old 'no admissible
candidate for ...' shape, so post-#359 'recognizer matched but produced no
injection ... (category=X)' refusals were silently dropped (44 refusals -> 12
extracted). Extend it to both shapes (same gap fixed in rescan_v4 before that
layer was retired).

The lane mirrored 50 cases from the all-refused era; the reader now admits 6,
so it covers the 44 refused. Regenerated the cases fixture + committed
report.json and updated the count pins (50 -> 44).

Removed the perverse categorized_rate >= 0.95 floor: the exact histogram is
already pinned by test_committed_report_matches_categorizer, and the rate
drifts DOWN as the reader graduates categorized refusals — it fought reader
progress. Replaced with a sanity floor.

adr_0126: the unparseable 'contemplates' input still refuses (wrong=0); only
the reason wording changed (#359). Accept either non-admission phrasing.
2026-06-03 01:49:41 -07:00
Shay
78001d6f78 chore: deprecate orphaned rescan_v4 archaeology layer
The rescan_v2/v3/v4 chain has zero runtime consumers (nothing in core/,
generate/, teaching/, chat/ imports it). v2/v3 are frozen disk-snapshot
tests; v4 alone re-ran the LIVE reader and asserted a barrier-shift count +
admission set, so it broke on every legitimate reader improvement (it was 8
of the red tests). Its only real invariant (wrong=0) is guarded directly by
the serving runner and test_runtime_wrong_zero_preserved.

No ADR doc references the v4 layer (only S2/S3-post-rescan exist). Remove the
brittle live test plus the now-orphaned generator and its two artifacts.
Keep the frozen, tested v2/v3 layer as the historical record.

Net: -8 brittle tests, removes a maintenance-only archaeology surface rather
than greening it.
2026-06-03 01:38:49 -07:00
Shay
c655f2fcda fix(rescan-v4): recognize new refusal-reason format + record reader advances
build_rescan's _FIRST_REFUSAL_RE only matched 'no admissible candidate for
...' and required the quote at end-of-string. PR #359 added the
'recognizer matched but produced no injection ... (category=X)' shape, so
extraction returned None and manufactured spurious shifts. Extend the regex
to both shapes and tolerate the trailing (category=...).

With extraction fixed, the live reader legitimately diverges from the v3
baseline on 10 cases (was 2 at the S.4 cut): 5 more first-refusals shifted
one sentence deeper (0019/0023/0025/0027/0047 — overrides added) and 3
refusals became correct admissions (0003/0021/0024 — the +3 that moved
serving 3->6). wrong stays 0; no admission lost. Tests retargeted to the
live counts and artifacts regenerated.

Follow-up: the v4 tests re-run the live reader, so they will drift again on
the next advance; a frozen v5 snapshot (or artifact-derived expectations)
would decouple them.
2026-06-03 01:13:10 -07:00
Shay
b1f12e1dce chore(evals): refresh stale committed reports
G3_numerics report.json: refusal reason-string drift (PR #359 made
recognized-but-uninjectable refusals more specific); verdicts and wrong=0
identical. train_sample coverage probe: admitted_solved 4->6 reflecting
reader coverage gains earlier in the window; wrong=0, safety_rail_intact.
Regenerated via their runners.
2026-06-03 01:01:50 -07:00
Shay
2d18976fa4 docs(claims): ADR-0200 reconciliation — expert claim to audit-passed truth
Reconcile every artifact that asserted the (since auto-reverted) mathematics_logic
expert promotion to the live machine state. Determinism proven intact (Week-1a):
the digest divergence is genuine single-source evidence-drift (GSM8K coverage probe
3/47 -> 4/46 via #310/#488), not a non-determinism defect. ADR-0120's fail-closed
property fired as designed; CORE revoked its own expert claim.

History keeps receipts; current-state reconciles to truth:
- Regenerate expert_claims_math_v1_signed.json -> promote_admitted:false,
  reviewer_signature_matches:false, digest 02f6d3c8.
- reviewers.yaml math_expert_claims: quarantine note; entry kept (mismatch-refusal
  keeps firing); intentionally NOT re-signed.
- ADR-0120-math-expert-ledger-flip: dated valid-at/auto-reverted header note.
- README: "next gate" narrative -> built-attempted-reverted; refresh stale count.
- docs/decisions/README: revert note + ADR-0200 index row.
- 3 fail-closed tests (2 files): "is-expert" -> fail-closed-revert assertions.
  Were RED on main; now green (30 passed).

No eval gate, threshold, or safety boundary changed.
2026-06-02 10:06:16 -07:00
Shay
f8b6f91627
feat(learning-arena): ADR-0199 PR-2 — extract domain-agnostic run_practice (#516) 2026-05-31 21:07:23 -07:00
Shay
69b89df606
Merge pull request #495 from AssetOverflow/feat/adr-0175-propose-step
feat(adr-0175): wire the PROPOSE step — autonomous attempt-and-eliminate loop closes
2026-05-31 08:37:23 -07:00
Shay
9df1e6522b
feat(adr-0195): GSM8K product promotion bridge — serving 4/46/0 → 6/44/0, wrong=0 (#500)
Narrow product promotion boundary (`generate/derivation/product_bridge.py`)
wired into `generate/math_candidate_graph.py`: only complete pure-product
derivations with a product-target question and no known hazard surface lift
from the sealed pooled derivation reader into serving.

- Serving train_sample: 4/46/0 → 6/44/0, wrong=0; case 0050 still refused.
- Renumbered from the collided ADR-0194 (labeled-container, #499) to ADR-0195
  and rebased onto current main.

CI: smoke + verify-pinned-lane-SHAs green on the merge commit.
2026-05-30 17:33:56 -07:00
Shay
b82897a0dd feat(adr-0175): wire the PROPOSE step — autonomous loop closes (attempt->tether->ledger->propose)
The attempt/score/ledger half existed (run_practice -> ClassTally scored vs
gold); nothing consulted the gate to turn earned reliability into a ratifiable
proposal. Adds core/reliability_gate/propose.py (propose_from_ledger +
RatifiableProposal): for each class, license_for(PROPOSE) emits a proposal iff
its conservative Wilson floor (0 below N_MIN=10) clears theta=0.85. Refusals
never penalize; deterministic; PROPOSAL-ONLY (never a serving mutation).

propose_runner.py closes the loop end-to-end with an aggressive sealed scorer
(resolve_pooled): practice 95c/5w/50r -> ONE proposal (additive, reliability
0.8608>=0.85, 95/100); 5 wrongs tolerated but floor held; rest stayed sealed.
The gold-tethered autonomous contemplation: the engine earns the right to ASK,
not to SERVE. 11 failing-under-violation tests.
2026-05-30 13:50:24 -07:00
Shay
0770648257
feat(GSM8K): comprehension reading → first metric move 3/47/0 → 4/46/0 (#488)
* feat(adr-0189): comparative reading — anchor-verb widening + multi-word units

The candidate-graph comparative extractor (ADR-0131.G.2) read only has/have +
single-word units, so real-GSM8K comparatives ('Brooke does three times as many
jumping jacks as Sidney') didn't parse — a dark statement in 17 places blocking
15 of the 47 refused train_sample cases, despite the ADR-0123 solver already
supporting compare_additive/compare_multiplicative.

Widens the anchor-verb set (reusing legacy vetted lemmas + does/collected/
gained/studied…), EXCLUDING polarity-inverting verbs (lose/spend/give/sell/win)
to preserve wrong=0; admits 1-2 word units via the existing multi-word
_unit_grounds branch. Feeds the existing solver unchanged.

wrong=0 proven: G2_comparatives 29/29, G3 20/0, G4 32/32, train_sample 3/47/0
byte-identical; polarity-inverting verbs proven refused (failing-under-violation).
Chain composes correctly in isolation (146 -> 438). Flips 0 cases ALONE — every
comparative case needs a composing partner (aggregation / multi-word-noun
injection); this ships the component, not yet a flip.

- generate/math_candidate_parser.py: _comparison_anchor_verb widening + 1-2 word
  unit slots in the two multiplicative comparative regexes.
- tests/test_adr_0131_G2a_*: 5 tests incl. polarity-inversion wrong=0 guards.
- docs/decisions/ADR-0189: gap, change, wrong=0 evidence, honest scope.

* feat(adr-0189a): first metric move 3/47/0 -> 4/46/0 (case 0024, comprehension-composed)

Case 0024 now SOLVES (answer 438) by composing three general comprehension
capabilities feeding the unchanged ADR-0123 solver:
  1. day-of-week count enumeration: Sidney = 20+36+40+50 = 146
     (_day_enumeration_candidates; derived sum grounds via first count token,
      mirroring _embedded_quantifier; closed to the 7 day names)
  2. comparative reading (ADR-0189): Brooke = 3 x Sidney
  3. activity question 'How many <unit> did <Entity> <verb>?' (_Q_DID_RE)
Plus do/does/did added to the CandidateInitial anchor whitelist (production-
possession), admitted only via the closed day-enumeration shape.

wrong=0 PROVEN across every lane: all 8 capability axes wrong=0 (G2_comparatives
29/29, G3 20/0, G4 32/32, G5/S1/S3/S4 all pass), train_sample 4/46/0 wrong=0,
verify_lane_shas exit 0 (no pinned lane changed), generate_claims --check OK.
872 tests pass; new tests are failing-under-violation incl. wrong=0 guards
(non-day comma list not summed; polarity-inverting comparative verbs refused).

Re-baselined report.json + train_sample_coverage_report.json (latter also clears
pre-existing reason drift) + CLAIMS.md to the new 4/46/0 metric. Decode-not-guess:
0024 solved by READING its structure, not storing an answer. Remaining pre-existing
failures (G3 committed-report, telemetry) unrelated, fail on pristine main.

- generate/math_candidate_parser.py: day-enum extractor + _Q_DID_RE + does-anchor.
- tests/test_adr_0189a_day_enum_activity.py: 5 tests (incl. end-to-end 0024=438).
- docs/decisions/ADR-0189a + report.json/coverage/CLAIMS re-baseline.
2026-05-30 09:21:48 -07:00
Shay
0fbcce429b
feat(adr-0182): cross-composer disagreement pooling — distractor 0014 + disguised-polarity refuse (confuser wrong 5->2) (#476)
Implements ADR-0182's first win on top of EX-6 (#473). The distractor-quantity
confuser 0014 misfired (20x3x5=300) because a blunt product-of-all was the *unique*
self-verifying reading: the completeness clause forces the distractor into it, and
no rival reading existed to trigger the wrong=0 disagreement rule. No tight cue rule
separates it from the legitimate cross-unit products (`for` licenses both the 0014
distractor AND the correct train-0021 product) -- that is the deferred cue-precision
problem. So instead of a reactive patch, let the disagreement rule do the refusing.

Mechanism (sealed lane; chat/ does not import these -> serving 3/47/0 frozen):
- verify.classify_derivation: a derivation is `complete` (commit-eligible),
  `exempt` (verified but for an isolated-foreign unused quantity -> commit-
  INELIGIBLE), or None. Refactored self_verifies into _base_reasons +
  _unused_quantities so the two share logic and cannot drift (behavior identical;
  385 derivation tests + smoke 67 green).
- accumulate.accumulation_candidates: exposes the ungated readings, incl. a
  distractor-skip reading that drops an isolated-foreign quantity from a multi-
  quantity change clause (20+5=25). compose_accumulation is byte-identical
  (drop_isolated_foreign=False + the same gate).
- search.multiplicative_candidates / multistep.candidate_chains: ungated candidates.
- pool.resolve_pooled: pools every composer's readings; disagreement -> refuse; a
  single answer commits only if a `complete` candidate produced it (exempt-only ->
  refuse, so the commit-path completeness guarantee from ADR-0175 is untouched).
- confuser runner: _engine_answer now delegates to resolve_pooled (the prior
  first-composer-wins order could not notice it held two incompatible readings).

Result (the microscope):
- confuser wrong 5 -> 2. distractor 0014 refuses (product 300 vs additive 25);
  BONUS: both disguised-polarity cases (0001/0003) refuse -- the spurious
  "buys X for N coins" product disagrees with the accumulation reading. Remaining
  wrong: 0016 (distractor in the anchor clause -> needs anchor-skip, separate step)
  and 0020 (temporal-scope). pair-tells 4 -> 1.
- genuine positives still 7 solved, 0 wrong.
- train_sample 3/47/0 and practice 3/47/0 byte-identical (they call
  compose_accumulation/search directly -- unchanged -- not the pool).
- smoke 67, architectural invariants 40, lane-SHA freeze 8/8.

Tests:
- test_adr_0182_pool.py: classify (complete/exempt/None incl. narrow-exemption
  edges) + resolve_pooled, with the wrong=0 obligation test_exempt_only_never_commits
  (a distractor with no multiplicative cue must refuse, not commit 25 -- fails loudly
  if the exemption is made commit-eligible).
- test_adr_0163_f2_confusers.py: baseline tightened wrong 5->2, pair_tells 4->1;
  new test_distractor_0014_refuses_via_pooling + test_disguised_polarity_does_not_misfire.

Stacked on #473 (EX-6); merge #473 first. 0016 + the remaining wrongs are follow-ups.
2026-05-29 13:22:19 -07:00
Shay
53573263cb
feat(adr-0181-p4): audio compiler eval gate lane (sensorium/audio) (#470)
PR-4 of ADR-0181. The acceptance-gate lane that decides whether audio_core_v1's
gate may open. Deterministic synthesis-spec fixtures (no .wav blobs) with
predicted parses, so the gates grade parser semantics as well as determinism.

evals/audio_sensorium/:
- synth.py            deterministic fixture synthesis (PCG64 + float32-at-boundary)
- fixtures.json       5 specs: silence, rising-pitch question, falling statement,
                      noise burst, speech-then-pause
- generate_expected.py reproducible pin generator (uv run -m ...)
- expected_ir.jsonl   frozen canonical_sha256 + ir_sha256 + event_type_counts
- expected_projection.json frozen projection_sha256 + reference versor

tests/test_audio_eval_gates.py (12): the gate table per fixture —
shape/dtype, versor_condition<1e-6, within-run replay, canonical-checksum
stability (hard int/cast-stable pin), IR-replay + frozen ir_sha256, semantic
event_type_counts (parser-accuracy gate), and cross-platform versor stability
within atol=1e-6 of the reference (float-safe per eval plan); plus trace
hygiene and gate-closure refusal.

Verified semantics: rise→prosody.rise, fall→prosody.fall, silence→pause.long+
turn.boundary, noise→nonspeech.noise, speech_then_pause→all three.

Cross-platform note: int/quantized-derived hashes are pinned hard; the float
versor is compared within tolerance rather than hash-pinned, since cos/sin/
geometric_product can differ by a ULP across arches. This is the eval-plan's
"equal within declared numeric tolerance" reading — keeps CI stable.

All audio 44 + arch-invariants 40 + smoke 67 green. No core mutation.
2026-05-29 11:20:31 -07:00
Shay
6a4d356ce9
feat(adr-0163-f2): confuser corpus v1 + discrimination-probe runner (#471)
Builds the corpus from the ADR-0163-F2 spec: 30 hand-curated, real-sourced cases
across the proven misfire categories (disguised-polarity, pseudo-accumulation/
fractions, multi-referent H1, multi-actor-pronoun ADR-0174, distractor-quantity,
temporal-scope H3, comparative-referent H2, unit-confuser) + genuine-positive
minimal-pair twins. Schema carries category/surface_trap/expected/pair_id/source.

The runner scores OPPOSITE to a coverage lane: the bar is `wrong` -> 0 (a confuser
*answered* is a defect regardless of value) plus pair-consistency (solving a twin
but answering its confuser = a surface-matching tell). It runs the realistic sealed
attempt (accumulation -> multiplicative -> chain, first to resolve).

Honest measured baseline (the probe's whole point — these are the defects the
templated corpus hid): 30 cases -> 7 solved / 15 refused / 7 WRONG / 1 spurious;
4 pair-tells (0001/0003/0014/0020). Wrong by category: disguised-polarity 2
(buys-a-toy-for-30 -> +30), pseudo-accumulation 2 (the 0002 cable/fraction),
distractor-quantity 2, temporal-scope 1 (before-giving -> gave the now-value).

Per the overfitting lesson, the composers are NOT reactively patched to pass the
probe (that is the trap). The baseline is pinned as a no-regression gate (wrong
<= 7, pair-tells <= 4, positives keep solving); future fixes must be GENERAL
mechanisms validated on train_sample, driving wrong down. Sealed: serving 3/47/0
byte-identical (lane-SHA 8/8, claims OK); architectural invariants green.
2026-05-29 11:13:49 -07:00
Shay
6611a7017d
feat(adr-0178-gb3b1): single-referent accumulation chaining (practice 0 -> 55) (#465)
The first cross-clause comprehension reading: one actor's quantity changes over
successive clauses ("Sam has 14 apples. He buys 9 more." -> 14 + 9). It is the
safe specialisation of the cross-clause sum that GB-3a refuses wholesale (the
Alice/Tom hazard) — we chain only when (same referent) AND (a licensed change
cue of unambiguous polarity), else refuse.

generate/derivation/accumulate.py — compose_accumulation:
- anchor on clause 1's single quantity; apply +M (gain) / -M (loss) per later
  change clause, operand taken in the anchor's unit (accumulation is same-dimension);
  routed through the unchanged self-verification gate.
- polarity (ordered, so ambiguous "gives" is resolved not guessed): "more" -> gain;
  else unambiguous loss verb -> loss; else gives/gave + to/away -> loss; else
  unambiguous gain verb -> gain; else REFUSE.
- referent guard (the ADR-0174 multi-actor hazard's defensive fix, built minimally
  in the clean lane — NOT the retired gender-blind resolver): a later clause's
  subject token must be a pronoun or the anchor's name; a NEW named subject (Tom)
  -> refuse. Pronoun gender/number is not matched; a new name is the only signal.

evals/.../accumulation_runner.py — practice scorer: on a base refusal, attempt
compose_accumulation and gold-check (mirrors search_runner). Sealed: fires only
on already-refused cases, never alters serving.

Measured (sealed practice additive lane): 0 -> 55 correct, wrong unchanged at 1
(the base scorer's pre-existing one; accumulation added 55 correct, 0 wrong). The
36 still-refused are multi-change (GB-3b.2) or unrecognised verbs (vocab growth) —
conservative, never wrong.

Proof obligations (tests fail under the violation): new-named-actor refuses (H1),
no/ambiguous change cue refuses, list anchor refuses, multi-change refuses,
determinism. 136 targeted tests + architectural invariants green; serving 3/47/0
byte-identical (lane-SHA 8/8, claims --check OK).
2026-05-29 10:41:51 -07:00
Shay
7451e7cd74
feat(adr-0177-cp2a): cue-precision ledger training + measurement (+ unit hygiene) (#461)
CP-2a populates the CP-1 ledger from gold-labelled candidate readings and reports
per-pattern reliability — the measurement the cue-precision thesis rests on. Plus
the function-word unit filter, whose value this measurement makes concrete (clean
unit_shape labelling).

What landed (all sealed; serving 3/47/0 byte-identical):
- generate/cue_precision/trainer.py — train_from_cases(cases, enumerators): folds
  gold-labelled candidate chains into the ledger via record_case. Decoupled (the
  candidate enumerators are injected, so the package still imports nothing from
  search). candidates_for dedupes a reading shared by two enumerators.
- generate/derivation/multistep.py — extracted the enumeration half of search_chain
  into public candidate_chains(problem_text); search_chain now delegates (verified
  byte-identical: ms3 tests + practice counts unchanged). CP-2 needs the readings
  the search weighs, not just the one it resolves.
- generate/derivation/extract.py — function-word unit filter (_NON_UNIT_WORDS):
  blanks spurious function-word units ($0.75 each -> "", 3/4 of -> "") that
  corrupt same-unit detection and unit_shape. Closed lexeme set, ADR-0165-safe.
- evals/gsm8k_math/practice/v1/cue_precision_report.py — trains over 200 sealed
  cases (50 train_sample + 150 ADR-0163-F additive) with the real enumerators and
  prints the per-pattern reliability table.
- tests/test_adr_0177_cp2a_training.py — trainer obligations (credit/dedupe/
  determinism/empty) via synthetic enumerators; real-measurement well-formedness;
  search_chain parity.

Load-bearing finding (recorded in ADR-0177): over 200 cases EVERY (cue,op,unit_shape)
pattern floors at ~0.0 reliability (best: for-multiply-cross_unit 0.0116 at 2/34).
The blunt product/sum-of-all readings are almost always wrong vs gold, so the
conservative floor correctly trusts nothing. => CP-2b (trust reliable cues) is
blocked on candidate GENERATION, not the ledger: candidate readings must get less
crude (clause/referent structure, ADR-0178 GB-3b) before any cue earns reliability.
Cue-precision and compositional structure are coupled; structure comes first.

Verification: 107 targeted tests green (CP-2a/CP-1/extract/ms3/GB-1/2/3/MS-1/2) +
architectural invariants; serving CLAIMS.md sha unchanged; practice 4/1/45 and
0/1/149 unchanged. Inert: trains/reports only, consulted by no search/gate.
2026-05-29 10:21:58 -07:00
Shay
de6df1edc9
feat(adr-0163-f): scale sealed practice case set to 150 additive cases (#459)
Adds `evals/gsm8k_math/practice/v1/cases.jsonl` — 150 GSM8K-style word
problems covering only additive/subtractive operations.  All cases carry
`<<a+b=c>>` / `<<a-b=c>>` annotations; none contain `*` or `/`, so every
case classifies as `"additive"` under `classify_operation`.

Four difficulty bands:
  0001–0030  single add (14 distinct units, 15 entity names)
  0031–0060  single subtract
  0061–0090  two same-direction operations
  0091–0150  mixed add+subtract and multi-step (2–4 steps)

IDs are `gsm8k-practice-v1-NNNN`, deterministically ordered.
`train_sample/v1/cases.jsonl` and its pinned SHA are untouched.
`build_search_report` continues to run unchanged.

Adds `_PRACTICE_CASES_PATH` constant and `_load_practice_cases()` /
`build_practice_report()` to `practice/v1/runner.py` as additive
symbols; `build_report()` and all existing imports are preserved.

New practice case count: 150.
2026-05-29 10:02:00 -07:00
Shay
dfb370a47e
Merge pull request #435 from AssetOverflow/feat/adr-0175-phase3b-mult-search
ADR-0175 Phase 3b: bounded multiplicative search in the sealed practice lane
2026-05-28 15:43:11 -07:00
Shay
872ed3b52d feat(adr-0175-phase3b): bounded multiplicative search in the sealed practice lane
ADR-0175 Phase 3b — the first live attempt generator. Runs only in the sealed
practice lane, only on cases the engine refused; every proposal is gated by the
Phase 3a self-verification gate.

generate/derivation/:
- extract.py: extract_quantities() — lexeme-level (number + unit word; ADR-0165).
- search.py: search_multiplicative() — one in-clause product candidate per
  sentence with >=2 quantities + a present multiplicative cue; gated by
  select_self_verified. Per-sentence scope + multi-candidate disagreement give
  the uniqueness gate real teeth (two qualifying sentences -> refuse). The cue
  set {each,every,for,per,times} is an explicit PROVISIONAL hypothesis the
  practice loop refines, not a claimed-correct grammar.
evals/gsm8k_math/practice/v1/search_runner.py: search_augmented_scorer +
  build_search_report — base scorer, then a practice-only attempt on refusals.

MEASUREMENT (the deliverable, per the breadth-of-impact test):
  practice with search:  correct=4  wrong=9  refused=37   (baseline 3/0/47)
- Flips +1 (0021, the clean in-clause aggregate) and its renumbered/reworded
  variants (ADR-0114a perturbation guard) -> a real capability, not memorisation.
- 9 wrong attempts -> elimination records (§9), the learning signal. The naive
  full-product cue model over-attempts; the eliminations are exactly the signal
  that refines it.

HONEST FINDING: self-verification (grounding ∧ cue ∧ unit ∧ uniqueness) is
NECESSARY but NOT SUFFICIENT — 9/13 self-verified attempts were wrong vs gold.
The gap is cue PRECISION / which-quantities-compose (the knowledge axis), not
'can we multiply' (skill). This is why the search runs sealed: gold catches the
9, and case 0050 (canary) attempted-and-failed IN PRACTICE without touching
serving -> validates the seal.

Invariants: #1 seal (serving still 3/47/0; 0050 refuses in serving; no
generate/chat import of the lane), #3 determinism. Serving wrong=0 untouched.

Verified: 3a+3b 31/31; ruff clean; serving lane 4/4; smoke 67/67.
2026-05-28 15:29:08 -07:00
Shay
d90887b80f feat(adr-0175-phase2): sealed practice lane over GSM8K train
ADR-0175 Phase 2 — a NEW lane (evals/gsm8k_math/practice/v1/), separate from the
wrong=0-pinned serving runner which is NOT modified. Runs the 50 cases in
practice mode: scores correct/wrong/refused as practice metrics, feeds per-class
counts into the Phase 1 ledger, diagnoses every refusal (§8), emits an
elimination record per wrong.

- classify_operation: gold-derived primary op class {multiplicative,divisive,
  additive} from <<a*b=c>> calc annotations (Tier-1 checkable in practice).
- diagnose_refusal (§8): skill_gap / knowledge_gap / genuine_ambiguity router.
- EliminationRecord (§9): wrong attempt gold caught -> pruning signal.
- PracticeReport: counts + per-class ledger + diagnoses + eliminations; as_dict.
- run_practice(cases, scorer=...): injectable scorer for tests; defaults to the
  candidate-graph scorer (read-only — never alters serving).

Live result mirrors serving (3 correct / 0 wrong / 47 refused of 50) because the
engine still refuses rather than guesses — attempts/eliminations go live in
Phase 3. But the diagnosis is already actionable: 35 skill_gap / 12 knowledge_gap
/ 0 genuine_ambiguity — 74% of refusals are skill gaps (Phase 3's search target),
quantifying the skill-vs-knowledge split.

Invariants: #1 seal (serving still 3/47/0; no generate/chat import of the lane),
#3 determinism (report byte-identical across runs). Elimination + wrong-tolerance
paths unit-tested via injected scorer (no live wrongs yet).

Verified: Phase 1+2 53/53, serving train_sample tests 4/4 (seal), smoke 67/67,
ruff clean.
2026-05-28 15:12:33 -07:00