Commit graph

73 commits

Author SHA1 Message Date
Shay
65405f1128
feat(derivation): Gate A2a unit partition injection (#809)
* feat(derivation): Gate A2a unit partition injection

Add typed unit_partition primitive with PartitionChunk/result_unit
contract, recognizer-injector bridge, DCS yield guard, and pronoun
lookback support. Closes unit_partition recognized_no_injection on live
train_sample (0002 partition stmt reclassifies); wrong=0 preserved.

* test(gsm8k): harden unit partition confusers

* test(gsm8k): add unit partition pronoun safety regressions

* chore(gsm8k): fix unit partition exemplar file ending

* chore(derivation): type unit partition solution step operand
2026-06-17 18:14:24 -07:00
Shay
bb0830046a
feat(gsm8k): Gate A1 multiplicative comparative recognizer injection (#805)
* feat(gsm8k): Gate A1 multiplicative comparative recognizer injection

Add COMPARATIVE_WITH_UNIT matcher/injector emitting compare_multiplicative
for the closed v1 template family (twice/thrice/N-times/half/quarter/third).
DCS yields comparative surfaces instead of detection-only fallback.

Includes ratified exemplar corpus + accepted recognizer proposal, 19 unit
tests, and live frontier proof that comparative_with_unit no-injection = 0.
wrong=0 preserved; no report.json rebaseline.

* fix(gsm8k): tighten Gate A1 N-times factors and confuser tests

Restrict comparative matcher N-times factors to plain digits and
single-word cardinals; refuse money, slash-fraction, hyphenated, and
indefinite surfaces. Strengthen confuser tests to assert empty injection
for any recognizer category; add graph-level refusal checks. Add Gate A1
lookback doc and EOF hygiene fixes.

* docs(analysis): pin Gate A1 lookback head SHA

* chore(gsm8k): fix Gate A1 file endings
2026-06-17 14:05:06 -07:00
Shay
80240ea9b8
feat(derivation): Workstream A inc 1 — lexeme reader + cleaned exemplars + proxy rebaseline (#796)
* feat(derivation): Workstream A inc 1 — lexeme-only reader components for fractions/comparatives + cleaned Phase B exemplars + proxy rebaseline (ratified, post-review fixes)

- generate/derivation/extract.py: lexeme-only passes (surface tokens only; no synthesized values or non-surface source_tokens for "half of", "X/Y of", "X more/less than"). Composition deferred per module contract (ADR-0165). Hygiene and EX-6 preserved. Docstring updated.
- teaching/admissibility_exemplars/*_v1.jsonl: grown + cleaned (removed category-polluted seeds, deduplicated ma IDs, fixed comparative exemplar to surface tokens per lexeme rule).
- evals/gsm8k_math/train_sample/v1/report.json: rebaselined after fixes (runner executed; wrong=0 held).
- docs/analysis/*-2026-06-1[6-7].md + lookback: ratif + honest post-review lookback (local paths removed, scope claims match actual clean diff, extractor/exemplar fixes documented).

Governing: the two 2026-06-16/17 ratifications (included; local session path replaced with stable ref). Scope per ratif: reader (lexeme) + exemplar growth (clean) + rebaseline + tests + lookback. No sealed, no CLOSE (deferred), 0 wrong.

Added tests in test_adr_0179_extract.py for new lexeme paths + grounding.

Branch prepared clean from origin/main; diff limited to scoped files only (no posture ratif, no runtime_contracts drift).

See updated lookback and PR body for full evidence (6/44/0 or current post-fix,  tests green, etc.).

(Addresses review: Semantic Rigor restored, branch hygiene, exemplar quality, tests, docs.)

* fix(review): resolve all verifiability blockers

- generate/derivation/extract.py: conflict markers removed (was from stash); lexeme-only implementation confirmed clean.
- tests/test_adr_0179_extract.py: added TestWorkstreamAReaderLexemeOnly class with direct tests for half-of, fraction-of, more/less components, source_token grounding, no-synthesis invariants.
- evals/gsm8k_math/train_sample/v1/report.json: fresh rebaseline via runner with fixed code (6/44/0, wrong=0).
- docs/analysis/gsm8k-workstream-a-increment-1-lookback-2026-06-17.md: updated to exactly match the actual diff and actual report numbers on this head.
- No conflict markers remain in extract, tests, lookback, ratifs, or exemplars (verified by grep).
- Branch head now clean and matches all claims in PR body/lookback.

This is the verifiable head for the lead engineer review.

* fix(test): make lexeme-only half test case-insensitive on source_token to match actual extraction (preserves input casing like other EX tests)

* fix(review): commit current report.json (6/44/0 from runner) and updated lookback so they appear in branch diff; lookback now exactly matches actual files in git diff --name-only and actual test count (29) and report numbers.

* fix(review): precise fractional doc in extract contract (normalizes factors while preserving source_token; comparative deferred); remove stale comparative sum/diff comments; lookback exact reality (29/29 tests, no deep-dive in diff, no sum/diff claim); mark remaining exemplars REVIEW-REQUIRED/ambiguous with explicit notes. Small final patch.

* fix(exemplars): remove three REVIEW-REQUIRED ambiguous-but-machine-admissible seeds (rwc-v1-0022, rwc-v1-0023, ma-v1-0024) from ingested JSONL to eliminate synthesis-poisoning risk per final review. Update lookback wording for actual seed count and rationale. Small final patch.

* docs(lookback): align exemplar category list with actual diff (only dcs/rwc/ma touched in this increment; no ca/ta/dsnq changes)
2026-06-16 21:57:05 -07:00
Shay
e473f5b55f feat(teaching): ADR-0218 P3 — proof-carrying coherence promotion
The ratified capability, through the existing mutation owner only:

- teaching/proof_promotion.py — pure decider. Fresh-reads every premise AND
  the claim from the store (claim_entry_index; forms never proposer-supplied),
  strict status compares (no parse-defaulting), reading_certified must be the
  boolean True, engine certification via the pinned deductive engine,
  proposer_payload accepted and provably never read (del before use; poisoned-
  mapping test), certificate digest emitted for D4 trace-hash folding. Zero
  mutation; zero vault.store calls; zero status writes (proven with the
  INV-21/INV-29 detectors themselves).
- vault/store.py::apply_certified_promotion — the single transition site.
  Independently re-verifies: byte-for-byte replay under DEDUCTIVE_ENGINE_PIN,
  promotion_positive, live claim form/reading/status cross-checks, live
  premise form/reading/status cross-checks (staleness refuses). Fabricated or
  tampered certificates flip nothing; authority is live store state plus
  recomputation, never the artifact.
- generate/proof_chain/engine_pin.py — DEDUCTIVE_ENGINE_PIN mirrors the
  deductive_logic_v1 lane SHA; sync pinned by AST test against
  scripts/verify_lane_shas.py.
- Consistency fix found in lookback: both promotion sites now stamp
  epistemic_state alongside epistemic_status (the ADR-0148 site left the
  stored state tag stale; recall recomputed it, but the stored key lied).

Obligations: all six strict-xfail markers retired and live (O1/O2
strengthened to prove the transition through the vault owner); the
module-existence pin deleted per its own docstring; the two remaining
honesty pins kept. INV-21 allowlist unchanged; INV-29 allowlist unchanged
({vault/store.py}); INV-29's 29c visibility floor raised 2 -> 3 to cover the
new site.

No runtime turn path calls promotion; the deterministic demo is PR D.

Validation: new suite 49 passed; obligations 8 passed (0 xfail remaining);
invariants 66 passed; certificate 27 passed; 0148 6 passed; epistemic 20
passed; smoke files green; lane SHAs verified.
2026-06-11 17:02:45 -07:00
Shay
5a221a7e50 docs(epistemic): correct §3.4 promotion doctrine + de-claim enum docstring
§3.4 said promotion "requires a curator-mediated coherence judgment." That
over-states a necessity. ADR-0021 §3 only requires promotion be a function of
coherence with the reviewed field. For claims *deductively entailed* by
already-COHERENT facts, the entailment proof IS that coherence judgment and can
be machine-certified by the sound deductive_logic_v1 engine — the logical arm of
the "structural coherence metric" ADR-0021's v2 gap already names.

- position_paper.md §3.4: split "own asserted output" (no standing — kept) from
  "deductively entailed from COHERENT facts" (proof = coherence judgment);
  relocate review to the *reading*, not the deduction; flag the proof-carrying
  path as specified-but-unwired with a link to the new issue.
- teaching/epistemic.py: the EpistemicStatus docstring claimed transitions are
  "computed from coherence" — they are not; review_correction carries status as
  a curator-supplied input. Docstring now states this honestly.
- docs/issues/proof-carrying-coherence-promotion.md: thorough spec for the
  unbuilt capability (scope, the reading hazard, INV-21 impact, trust boundary,
  falsification tests, phased plan, proposed ADR-0218).

No runtime behavior change. Green: test_epistemic_invariants,
test_epistemic_phase3_state_tagging, TestINV21OneMutationPath (15 passed).
2026-06-11 13:52:51 -07:00
Shay
5903cba08a feat(teaching): proposal-only failure-artifact emitter (N5)
core/comprehension_attempt/proposal.py: emit_proposal writes a content-addressed teaching/proposals/comprehension_failures/<hash>.json ONLY for growth-surface families (proposal_allowed). Deliberately toothless: status always proposal_only, mounted always false, requires_review always true (enforced in __post_init__); the problem text is hashed (sha256), never stored; filename = sha256(family:text_sha) so the same failure is idempotent.

No proposal for a correct wrong=0 boundary. Serving never reads the sink (scanned: generate/stream, field/propagate, vault/store, generate/derivation, core/reliability_gate). Routes through the existing proposal-only flywheel (ADR-0055/0056/0057), never a parallel correction path. README pins the contract. 6 tests.
2026-06-07 08:48:45 -07:00
Shay
6d1f6bba0c feat: implement CORE Workbench W3 Ratification Corridor 2026-06-03 20:13:36 -07:00
Shay
3fd317290b feat(adr-0174-phase5a): retire inert GSM8K scoring-path reader
The recognizer/candidate-graph path is the single canonical reader.
Retires the flag-gated incremental-reader dispatch that admitted 0/50 on
train_sample and only added a dead fall-through:

- remove _try_comprehension_reader, _try_reader_for_question, _tokenize_sentence
  and both dispatch blocks from generate/math_candidate_graph.py
- delete generate/comprehension/lifecycle_runtime_adapter.py (402 LOC,
  used only by the question-reader dispatch)
- drop the comprehension_reader_questions config flag and the parse_and_solve
  / _score_one_candidate_graph config threading
- remove the --use-reader runner plumbing + flag-ON/OFF delta report from
  the train_sample runner; refresh report.json (drops stale use_reader field
  and a stale refusal-reason; verdicts unchanged at 3/47/0)
- remove the now-dead use_reader field from teaching/coverage.py
  CoverageReport + the core teaching coverage CLI flag
- delete tests/test_reader_coexistence.py (flag-ON/OFF premise dissolved);
  fix 3 ADR-0174 build_report calls and 2 subprocess invocations

lifecycle.py and audit.py are KEPT — they are load-bearing for the ADR-0172
math-contemplation teaching corridor (audit_problem -> teaching/math_*),
which a pre-deletion trace surfaced. The parent ADR's plan to delete
lifecycle.py was wrong; only its GSM8K scoring dispatch was inert.

Net -1,038 LOC (code + tests). Behavior-preserving:
- train_sample 3/47/0, byte-identical verdicts to pre-5a baseline
- determinism holds; smoke/packs/runtime/cognition/teaching lanes green
- contemplation corridor + lifecycle/audit tests pass

Pre-existing (NOT introduced here; reproduce on base with changes stashed):
5 out-of-curated-lane stale committed-artifact / stale-assertion failures
(test_math_evidence_e2e, test_adr_0126_runner_wiring, G3/coverage_probe
report-match, test_refusal_taxonomy_lane rebuild).
2026-05-28 13:38:44 -07:00
Shay
3f3f90ef11 feat(demo): core demo flywheel — public-proof reproduction of the loop
The repo is public. The thesis is *decoding, not generating* with
wrong=0 as the load-bearing invariant. The demo any visitor can run
to see the loop turn end-to-end on the canonical pack:

    git clone https://github.com/AssetOverflow/core
    cd core && uv pip install -e .
    core demo flywheel

Four falsifiable scenes:

  1. RATIFY    — apply_composition_claim writes source JSONL; RAT-1
                 auto-compile regenerates compositions.jsonl + bumps
                 manifest.composition_checksum
  2. LOAD      — composition_registry picks up the new entry on the
                 next runtime turn
  3. SOLVE     — "Lilibeth fills 6 baskets where each basket holds
                 50 strawberries. How many strawberries does Lilibeth
                 have?" admits via matcher → injector → admission →
                 candidate-graph and produces answer=300
  4. HAZARD    — case 0050 (wrong=0 canary) remains refused; no SAFE
                 composition category can convert it

All four scenes byte-deterministic. The canonical pack is read-only
throughout; the demo mutates only a synthetic test pack in a
tempfile.TemporaryDirectory. One-time recognizer seed is idempotent
(same content_digest each run → no duplicate proposal log entries).

Exit code 0 iff all scenes pass; --json for CI integration.

Also adds:
- README "Watch the flywheel turn — one command" section pointing
  to the demo + the coverage CLI (per-shape histogram + hazard pin)
- ProposalLog entry for the multiplicative_aggregate recognizer
  with extract_values=True (one-time operator seed)

Files:
- evals/flywheel_demo/run_tour.py (new) — the four-scene tour
- evals/flywheel_demo/__init__.py (new)
- core/cli.py — `flywheel` added to `core demo` choices + dispatch
- README.md — new "Quick Start" subsection
- teaching/proposals/proposals.jsonl — seeded recognizer
2026-05-27 21:33:54 -07:00
Shay
36f3dbfc4e fix(D): address Sourcery review findings on PR #410
Three review fixes:

1. Security: validate lane/split/version against ^[a-z0-9_]+$ before
   building the runner module name. The runner_args list is passed to
   subprocess.run without shell=True (no shell injection possible),
   but defense-in-depth blocks arbitrary token characters from
   reaching Python's -m module loader. Bad input now errors at the
   CLI boundary with a clear message.

2. Bug-risk: _classify_refusal docstring referenced a
   no_admissible_candidate bucket that the implementation never
   emitted. Aligned docstring with actual buckets
   (no_admissible_question / no_admissible_statement). Also made all
   matching consistently case-insensitive (was mixed — some checks
   used raw reason, one used .lower()).

3. Bug-risk: fetch_committed_baseline wrote to
   .git/coverage_baseline_tmp.json. Replaced with tempfile.mkstemp in
   the system temp dir — avoids (a) failures in non-git worktrees
   where .git is a file pointer, (b) concurrent-access collisions
   between simultaneous operators.

Tests (+3 new):
- test_classify_refusal_is_case_insensitive
- test_classify_docstring_matches_implementation_buckets
- test_fetch_committed_baseline_uses_system_temp

All 16 coverage tests green. Verified the validation:
  core teaching coverage --lane 'evil; rm -rf /'
  → ERROR: lane='evil; rm -rf /' must match ^[a-z0-9_]+$
2026-05-27 21:20:01 -07:00
Shay
d91ea3d36e feat(D): core teaching coverage — per-shape admission histogram
Brief D from PR #407. Closes the "flying blind on per-shape coverage"
gap identified in RAT-1's audit (finding 6).

After this PR, every operator can run a single command to see exactly
which refusal modes their work moved (or didn't), without re-eyeballing
report.json by hand.

Modules
-------
- teaching/coverage.py — pure aggregator:
  - _classify_refusal — maps each per-case refusal reason to a
    stable bucket (recognizer_empty_injection(<ShapeCategory>),
    no_admissible_question, no_admissible_statement,
    unexpected_question_count, other)
  - build_coverage_report — reads a lane's report.json + emits a
    CoverageReport with counts, refusal_taxonomy (sorted by count
    desc), case_0050_verdict, optional delta vs baseline
  - fetch_committed_baseline — uses `git show HEAD:<relpath>` to
    pull the baseline report.json for delta computation

- core/cli.py:
  - cmd_teaching_coverage — formats the report for terminal output
  - core teaching coverage [--lane gsm8k_math] [--split train_sample]
    [--version v1] [--use-reader] [--run] [--delta] [--json]

CLI output example
------------------
  Lane: gsm8k_math/train_sample/v1 (use_reader=True)
  Counts: correct=3 refused=47 wrong=0

  Refusal taxonomy:
     21  recognizer_empty_injection(discrete_count_statement)
      6  no_admissible_statement
      5  recognizer_empty_injection(multiplicative_aggregation)
      4  no_admissible_question
      4  recognizer_empty_injection(currency_amount)
      3  recognizer_empty_injection(rate_with_currency)
      2  recognizer_empty_injection(descriptive_setup_no_quantity)
      2  recognizer_empty_injection(temporal_aggregation)

  Wrong=0: ✓
  Case 0050 hazard pin: refused ✓

Tests (13 new)
--------------
tests/test_teaching_coverage_cli.py — classification narrowness,
counts aggregation, case 0050 verdict capture, delta computation,
missing-baseline path, missing-report error, taxonomy sort order,
wrong=0 invariant visibility via as_dict.

Suite results
-------------
core test --suite teaching -q → 106 passed (93 → +13)
core test --suite runtime  -q → 20 passed
core test --suite packs    -q → 127 passed
core eval gsm8k_math --split public → 150/150, wrong=0

Note on Brief E (lexical auto-compile): the audit was WRONG. The
lexicon loader (generate/comprehension/lexicon.py::load_lexicon)
reads from the per-category source files directly; the compiled
lexicon.jsonl is only a manifest-checksum pin, not the source of
truth at runtime. apply_lexical_claim() writes a new entry → next
turn the loader sees it. Brief E is a non-issue; closing without a
code PR.

Verified by direct test: stage a clone of the math pack, write a
synthetic lemma to drain_token.jsonl, clear the lexicon cache, load
again → new entry present. So 3 of the 5 audit gaps closed (A, D,
E-as-correction); B and C remain as the next operator dispatch
targets.

Independent of PR #406 (RAT-1) and PR #408 (WAVE-A). Based on main.
2026-05-27 21:20:00 -07:00
Shay
a092d2e8c2
Merge pull request #411 from AssetOverflow/feat/contemplation-ratifiable-claims
feat(brief-B): enrich contemplation payload — composition_reclassification directly ratifiable
2026-05-27 21:06:36 -07:00
Shay
7441b42bf5 feat(wave-a): first non-DCS injector — multiplicative_aggregation w/ value extraction
Addresses 5 of 47 train_sample "recognizer matched but produced no
injection" refusals (the largest single failure-mode bucket
identified in RAT-1's audit).

Modules
-------
- generate/recognizer_match.py:
  - _MULT_AGG_EACH_WEIGHING_RE — regex for "<Subject> <bake-verb>
    <M> <outer-noun>, each <weigh-verb>ing <N> <unit>" pattern
  - _try_extract_each_weighing_anchor — extracts M, N, subject,
    inner unit; emits pre-composed CandidateInitial(value=M*N) with
    composition_evidence so RAT-1's _composed_initial_admissible
    gate verifies INPUT tokens ground (preserves wrong=0)
  - _match_multiplicative_aggregation dispatches to the value
    extractor when spec carries extract_values=True; specs without
    that flag get the existing detection-only return path
    (byte-identical legacy behavior)

- generate/recognizer_anchor_inject.py:
  - inject_multiplicative_aggregation — new per-category injector;
    narrow by anchor.kind so ME-3/ME-4 additive/subtractive anchors
    (which share the same matcher entry point) continue to flow
    through composition_registry consult instead of WAVE-A's direct
    path
  - registered in _INJECTORS dict (2nd entry after DCS)

- core/cli.py:
  - seed-recognizer CLI gains --extract-values flag to opt the
    canonical_pattern into the value-extracting matcher path

Seeded artifacts
----------------
- proposals.jsonl: rat1-seed-4dc30608fb783bc7 — multiplicative_
  aggregation recognizer with anchor_kind=multiplicative_aggregate,
  extract_values=True, observed_units covering ounces/strawberries/
  questions/etc.

Live result on train_sample
---------------------------
- wrong == 0 preserved (3/47/0 baseline)
- Case 0050 hazard pin held
- public 150/150 preserved
- packs suite: 127 → 131 (+4 new WAVE-A tests, all green)
- teaching suite 93 unchanged
- runtime suite 20 unchanged

End-to-end synthetic solve (FIRST WAVE-A admission):
  "Lilibeth fills 6 baskets where each basket holds 50 strawberries.
   How many strawberries does Lilibeth have?"  → answer=300

Cases that moved (statement now admits; refusal shifted downstream):
- Case 0025 (Lilibeth): statement admits via WAVE-A; refusal moved
  to question parser ("If three of Lilibeth's friends pick the same
  amount, how many strawberries do Lilibeth and her friends pick in
  all?")
- Case 0047 (John bakes 12 macaroons): statement 1 admits; refusal
  moved to statement 2

Eval correct count unchanged because the QUESTION parser (and
multi-statement cross-sentence reasoning) is the next bottleneck.
RAT-1's audit identified that gap; WAVE-A closes the injector half.

The remaining 3 multiplicative_aggregation refusals (0006, 0013,
0045) have different shape patterns the WAVE-A regex does not yet
cover; they're follow-up matcher extensions in the same architecture.

Tests
-----
- tests/test_wave_a_multiplicative_aggregation_injector.py (10
  tests): each-weighing + each-basket-holds admission shapes,
  detection-only path preserved when extract_values absent,
  unobserved unit / pronoun / zero count refusals, end-to-end
  inject_from_match dispatch, the Lilibeth canary solve,
  wrong=0 preserved, case 0050 hazard pin

Stacks on PR #406 (RAT-1).
2026-05-27 20:50:04 -07:00
Shay
193764e3fd feat(brief-B): enrich composition_reclassification payload to be directly ratifiable
Adds surface_pattern, composition_category, and polarity to the
proposed_change_payload for composition_reclassification proposals so
operators can call apply_composition_claim() without field synthesis.

Dispatch by missing_operator:
- quantity_extraction → multiplicative_composition + bound(count) × bound(unit_cost)
- multi_quantity_composition → additive_composition + bound(qty_a) + bound(qty_b)

All other change kinds (matcher_extension, injector_sub_shape,
frame_reclassification) keep the existing evidence-aggregation payload.
Legacy fields (evidence_count, group_key, modal_sub_type) preserved.

Adds tests/test_contemplation_ratifiable_payload.py with 11 tests
including a round-trip from decompose_audit → apply_composition_claim.
2026-05-27 20:46:10 -07:00
Shay
d5c91e1ac1 feat(RAT-1): close ratify→runtime gap + first live composition admission
The user's question — "shouldn't we be running it multiple times so
it can learn? or is that part broken?" — exposed that the math
teaching loop's `ratify → admit` closure had been structurally
broken at the connector between operator ratification and runtime
visibility. The handlers wrote source files (compositions/, frames/)
that the runtime loader never read because no compile step
regenerated the runtime artifacts.

This PR fixes the gap end-to-end AND fires the first live composition
admission on the canonical pack.

Modules
-------
- language_packs/compile_pack.py — unified compile step that
  regenerates frames.jsonl + compositions.jsonl + updates
  manifest.{frame,composition}_checksum atomically. Idempotent.

- teaching/math_composition_ratification.py — apply_composition_claim
  now calls compile_pack at end of successful ratification. Closes
  the source-file→runtime-artifact gap.

- teaching/math_frame_ratification.py — same auto-compile wire for
  apply_frame_claim.

- generate/math_candidate_parser.py — CandidateInitial gains optional
  composition_evidence Mapping field. When populated, signals the
  candidate was produced by a registry-gated composition (ADR-0169);
  the value/unit/entity are DERIVED arithmetic over grounded inputs.

- generate/math_candidate_graph.py — new _composed_initial_admissible
  predicate that branches on composition_evidence. Wrong=0 preserved
  by requiring each composition INPUT token (count, amount) to ground
  in source_span literally; the derived value is admitted because the
  arithmetic over grounded inputs is deterministic.

- generate/math_candidate_graph.py — discourse-level prior_subject
  tracking: capture proper-noun subjects from ALL statement sentences
  (including ADR-0136.S.0 context-filler sentences that get filtered
  out before the candidate loop). Without this, "John adopts a dog"
  (no numbers) is dropped and the cross-sentence subject resolver for
  case 0019 sees prior_subject=None.

- generate/recognizer_match.py — all four composition matchers
  (ME-1 currency-per-unit same-sentence, ME-2 cross-sentence, ME-3
  additive, ME-4 subtractive) now populate composition_evidence in
  CandidateInitial. Also added standalone " each " / " apiece " to
  _PER_UNIT_TOKENS so currency_amount detection-only matcher refuses
  per-item costs instead of swallowing them.

CLIs
----
- core teaching compile-pack — explicit operator surface for
  regenerating runtime artifacts. JSON output for CI integration.

- core teaching seed-recognizer — operator surface for seeding a
  RatifiedRecognizer entry in the proposal log for a given
  (shape_category, anchor_kind). Writes created + transition(accepted)
  events directly via ProposalLog._append.

Seeded artifacts (the actual loop closure)
------------------------------------------
- proposals.jsonl: new rat1-seed-48dd2673d6ad673d RatifiedRecognizer
  entry for shape_category=rate_with_currency,
  anchor_kind=currency_per_unit_composition.

- compositions/multiplicative_composition.jsonl: ratified
  "bound(count) × bound(unit_cost)" affirms entry sourced from
  case 0019 evidence.

- compositions.jsonl + manifest.composition_checksum: compiled
  runtime artifact + manifest pin (RAT-1 auto-compile).

Live result on train_sample
---------------------------
- wrong == 0 preserved (3 correct / 47 refused / 0 wrong)
- Case 0050 hazard pin holds (refused)
- public split 150/150 preserved
- Case 0019 sentence 1 ("requires 3 vet appointments, which cost
  $400 each") NOW ADMITS via composition. Previously refused with
  "recognizer matched but produced no injection". The refusal moved
  downstream to sentence 2 (a different currency_amount detection
  bottleneck that is its own follow-up).

This is the first time a composition ratification on the canonical
pack actually reaches the runtime. The flywheel turned one
revolution.

Tests
-----
- tests/test_rat1_end_to_end_admission.py — 4 new live tests:
  composition statement admits on isolated synthetic problem, case
  0019 cross-sentence admission, wrong=0 preserved on train_sample,
  case 0050 hazard pin.

- tests/test_consumption_empty_registry_no_op.py — refactored to use
  isolated synthetic packs (the canonical pack may now carry ratified
  entries).

- tests/test_math_{frame,composition}_ratification.py — updated
  "manifest checksum unchanged" tests to "lexicon checksum
  preserved" semantics: RAT-1 auto-compile may add the new optional
  checksum fields; pre-existing lexicon checksum stays untouched.

Suite results: teaching 93, packs 131 (+4), runtime 20. All green.
2026-05-27 20:09:47 -07:00
Shay
44c0aa2896
feat(ADR-0169/CC-2+CC-3): CompositionClaim ratification handler + decomposer heuristic tightening (#393)
PR-β of the CompositionClaim wave (CC-2 + CC-3 bundled per the brief
pack — CC-3's heuristic depends on CC-2's new change_kind Literal value).
Mirrors the F1 / ADR-0168 FrameClaim template 1:1 with composition-specific
substitutions.

CC-2 — handler implementation
  - teaching/math_composition_proposal.py — MathCompositionClaimProposal
    adapter per ADR-0169.1 §"Data shape". Frozen dataclass, deterministic
    proposal_id / claim_signature, source="math_audit" pin at the
    proposal layer (rejects "corpus" laundering).
  - teaching/math_composition_ratification.py — apply_composition_claim()
    handler. SAFE_COMPOSITION_CATEGORIES = {multiplicative,
    additive, subtractive}_composition per ADR-0169 §"Initial safe
    category scope". New WrongCompositionCategory exception per
    ADR-0169.1 §"Trip-wires" #8. Writes only to
    language_packs/data/en_core_math_v1/compositions/{category}.jsonl;
    no solver / parser / decomposer / runtime mutation.
  - workbench/readers.py — _HANDLER_DISPATCH now routes
    composition_reclassification → CompositionClaim; suggested_cli
    branch added for both read_math_proposal and ratify_math_proposal.
  - teaching/math_contemplation_proposal.py — ChangeKind Literal +
    _VALID_CHANGE_KINDS frozenset extended with
    composition_reclassification.
  - language_packs/data/en_core_math_v1/compositions/.gitkeep —
    reviewed-pack scaffold.
  - tests/test_math_composition_ratification.py — 22 tests including
    case 0050 hazard pin, cross-process replay equivalence, queue-order
    independence, partition, no-corpus-laundering, dispatch wire,
    Literal acceptance, JSONL round-trip.
  - tests/test_adr_0172_w1_shape_proposal.py — parametrize round-trip
    over all 5 change_kinds.
  - core/cli.py — teaching suite tuple includes new test file.

CC-3 — decomposer heuristic tightening
  - teaching/math_contemplation.py::_CHANGE_KIND_BY_PAIR:
    + (incomplete_operation, quantity_extraction)         → composition_reclassification
    + (incomplete_operation, multi_quantity_composition)  → composition_reclassification
    - (unexpected_category, multi_subject_sentence)       demoted to injector_sub_shape
      (was frame_reclassification; FrameClaim SAFE_FRAME_CATEGORIES doesn't
       cover this — needs ReferenceClaim/CompositionClaim)
    - (unexpected_category, descriptive_frame_question)   demoted to injector_sub_shape
      (was frame_reclassification; needs SlotClaim, not FrameClaim)
    Updated hypothesis-step justification text to reflect new dispatch
    table.
  - tests/test_adr_0172_w2_decomposer.py — distribution assertion
    tightened from "≥3 matcher, ≥2 frame" to exact counts:
    3 matcher / 2 composition / 3 injector / 0 frame. New
    per-pair tests for the four CC-3 dispatch changes.

Verification on real audit_brief_11.json (20 of 47 highest-leverage
refusals now routable):

  2  composition_reclassification   (12 quantity_extraction + 8 multi_quantity_composition)
  3  injector_sub_shape             (2 multi_subject + 2 descriptive_frame + 4 unattached_quantity)
  3  matcher_extension              (9 pre_frame_filler + 4 fraction_percentage + 4 pronoun)
  0  frame_reclassification         (the two prior misroutes are gone)

Workbench POST /math-proposals/{id}/ratify on either composition
proposal now returns 200/routed with a real apply_composition_claim()
command instead of 501.

Suites green:
  - core test --suite teaching -q  → 71 passed
  - core test --suite runtime -q   → 20 passed

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 15:15:11 -07:00
Shay
c6a9bb0096
feat(ADR-0168/F1): FrameClaim ratification handler (Tier 1.5) (#389)
Implements the FrameClaim ratification handler per ADR-0168 doctrine
and the ADR-0168.1 MathFrameClaimProposal adapter.  Mirrors the
ADR-0167 W2-D LexicalClaim template (apply_lexical_claim) but lifts
the safe surface from drain-class lexical entries to allowlisted
frame-opening categories — the next sub-type up the wrong=0 hazard
ladder.

teaching/math_frame_proposal.py
  + MathFrameClaimProposal dataclass (ADR-0168.1 §"Data shape")
  + MathReaderRefusalEvidencePointer with source pinned to "math_audit"
  + build_evidence_pointer() — only sanctioned constructor; rejects None
    missing_operator
  + build_frame_claim_proposal() — enforces:
      • surface_form non-empty after normalization
      • frame_category in the ADR-0168 allowlist
      • polarity in {affirms, falsifies}
      • ≥1 evidence pointer with source="math_audit"
      • source="corpus" rejected as schema-illegal (ADR-0168.1
        §"Evidence floor")
  + compute_claim_signature() / compute_proposal_id() / canonical_bytes()
    — deterministic identity per ADR-0168 §"Replay obligations" #1

teaching/math_frame_ratification.py
  + SAFE_FRAME_CATEGORIES = {increment_frame, decrement_frame,
    transfer_frame, remainder_frame} — no other categories
  + Error hierarchy: RatificationError, WrongClaimSubType,
    WrongZeroViolationCandidate, AlreadyRatified, EvidenceTampering,
    UnknownCategory, InvalidPolarity, EvidenceLaundering
  + FrameRatificationReceipt dataclass with before/after SHA + evidence_hash
  + apply_frame_claim(claim, frame_category, polarity, reviewer, pack_root,
    evidence_source="math_audit"):
      • rejects evidence_source != "math_audit" (ADR-0168.1 §"Evidence floor")
      • rejects polarity outside {affirms, falsifies}
      • rejects claim.sub_type != "frame"
      • rejects evidence_hash tampering (recomputes from audit_row)
      • rejects frame_category outside SAFE_FRAME_CATEGORIES
      • writes language_packs/data/en_core_math_v1/frames/{category}.jsonl
      • idempotent: same (surface, category, polarity, evidence_hash)
        raises AlreadyRatified
      • duplicate evidence appends evidence_hash to existing row
        (ADR-0168.1 §"Idempotency" path #1)
      • polarity=falsifies records non-opener; never appends to compiled
        lexicon or manifest

language_packs/data/en_core_math_v1/frames/.gitkeep
  Directory scaffold for the reviewed frame source files.

workbench/readers.py
  _HANDLER_DISPATCH gains "frame_reclassification" → "FrameClaim".
  GET /math-proposals/{id} detail and POST /math-proposals/{id}/ratify
  now return suggested_cli pointing at apply_frame_claim().

core/cli.py
  teaching test-suite tuple gains tests/test_math_frame_ratification.py.

tests/test_math_frame_ratification.py — 14 tests:
   1. SAFE_FRAME_CATEGORIES is exactly the ADR-0168 allowlist
   2. apply writes a frame entry for a safe category
   3. receipt records before/after sha + evidence_hash
   4. idempotent same-evidence → AlreadyRatified
   5. rejects non-frame sub_type → WrongClaimSubType
   6. rejects categories outside SAFE_FRAME_CATEGORIES → WrongZeroViolationCandidate
   7. rejects invalid polarity → InvalidPolarity
   8. rejects evidence_hash tampering → EvidenceTampering
   9. rejects source="corpus" → EvidenceLaundering (ADR-0168.1 §"Evidence floor")
  10. case 0050 hazard pin — after ratification, case 0050 still refuses
  11. polarity=falsifies branch records non-opener; affirms+falsifies coexist
  12. duplicate evidence appends evidence_hash, does not create a second row
  13. manifest.json checksum unchanged by frame ratification
  14. alphabetical sort by surface_form preserved across writes

Suite verification
  core test --suite teaching -q → 47 passed (was 33; +14 new)
  core test --suite runtime  -q → 20 passed
  tests/test_math_lexical_ratification.py → 15 passed (untouched, regression-clean)
  tests/test_adr_0172_w4_workbench_e2e.py → 7 passed (existing dispatch tests still hold)

Doctrine invariants preserved
  - wrong=0: case 0050 still refuses after ratification
  - replay equivalence: claim_signature and proposal_id are deterministic
    (sha256 of canonical identity, clock-time-independent)
  - refusal-first: no runtime mutation; handler is the only mutation
    boundary and writes only the reviewed frames/ source tree
  - ADR-0167 partition: math-audit evidence stays math-domain; corpus
    evidence is rejected loudly

Brief-correction note: the brief named the scaffold path
"packs/en_core_math_v1/frames/.gitkeep" but the existing math pack lives
at language_packs/data/en_core_math_v1/ (no top-level packs/en_core_math_v1
exists).  Scaffold placed at language_packs/data/en_core_math_v1/frames/
to mirror the existing lexicon/ source-tree convention; apply_frame_claim
defaults pack_root to that location.
2026-05-27 14:10:43 -07:00
Shay
3109fdcbd1
feat(ADR-0172/W5): MathReaderInferenceProposal schema (Tier 2) (#388)
teaching/math_inference_proposal.py
  - MathReaderInferenceProposal frozen dataclass + ArmResult record
  - build_inference_proposal() enforces all 9 invariants:
      ≥3 evidence rows, ≥6 trace steps including {abstraction,
      test_design, test_application, test_result}, both-REJECT guard,
      arm2 PASS requires cases_changed_answer==0,
      ratification_effect_kind Literal=="canonicalization_bridge",
      JSON-serializable payload, wrong_zero ≥40 chars
  - canonical_bytes() for content-addressable inference_id
  - to_jsonl_record() / from_jsonl_record() self-contained JSONL
    persistence — mirrors post-#386 pattern from W1

tests/test_adr_0172_w5_inference_proposal.py — 21 tests across 11 obligations

core/cli.py — teaching suite tuple updated to include W5 test file
2026-05-27 14:01:50 -07:00
Shay
131e711054
feat(ADR-0172/tightening): three follow-ups — self-contained JSONL, widened dispatch, shape_category gap (#386)
Bundles three post-Tier-1 follow-ups into one PR (no scope change, no
new ADR — implementation tightening on the already-shipped corridor).

(1) Standalone JSONL self-containment
  teaching/math_contemplation_proposal.py
    + to_jsonl_record() — emits proposal_id + full evidence_pointers
      (nested dicts including audit_row) + full reasoning_trace.steps
    + from_jsonl_record() — inverse; goes through build_proposal()
      so all invariants are re-validated; raises on proposal_id mismatch
    canonical_bytes() UNCHANGED (still the content-hash function;
    trace_id/proposal_id stability preserved)
  core/cli.py W3 lane now writes to_jsonl_record() output instead of
    canonical_bytes() — same compact-JSON encoding (sort_keys=True,
    ensure_ascii=False, separators=(",", ":"))
  workbench/readers.py loads via self-contained record fields directly;
    decompose_audit() re-run removed.  read_math_proposal() now reads
    reasoning_trace.steps and evidence_pointers from the JSONL record.

(2) Widened change_kind heuristic dispatch
  teaching/math_contemplation.py
    + _CHANGE_KIND_BY_PAIR table on (refusal_reason, missing_operator):
      (unexpected_category, pre_frame_filler_sentence) → matcher_extension
      (unexpected_category, multi_subject_sentence)    → frame_reclassification
      (unexpected_category, fraction_percentage_literal) → matcher_extension
      (unexpected_category, descriptive_frame_question) → frame_reclassification
      (unresolved_pronoun, pronoun_resolution)         → matcher_extension
    Single-key fallback (lexicon_entry/narrowness_violation/
    frame_unrecognized) retained for completeness.
    hypothesis-step justification text updated to reflect new table.

  Result on audit_brief_11.json:
    3  matcher_extension       (was 0)
    2  frame_reclassification  (was 0)
    3  injector_sub_shape      (was 8)
    0  vocabulary_addition     (no unknown_word group ≥2 in train sample)

(3) shape_category structural gap
  MathReaderRefusalEvidence does not carry shape_category, so the
  proposal cannot derive it.  All proposals continue to emit
  ShapeCategory.UNCATEGORIZED with a structural-gap comment.  No
  invented values — handler dispatch decision (per ADR-0167-FOLLOWUPS
  §1) drives ratification routing today, not shape_category.

Tests
  + W1: 5 new tests (to_jsonl_record self-containment, round-trip,
    byte stability, proposal_id mismatch rejection, canonical_bytes
    unchanged invariant)
  + W2: 3 new pair-dispatch tests + real-audit change_kind distribution
    test + shape_category-uncategorized test
  + W3: 2 new tests (records are self-contained, round-trip via
    from_jsonl_record); existing byte-comparison test updated to use
    proposal_id ordering instead of canonical_bytes
  + W4: existing 6 tests updated to build JSONL via to_jsonl_record;
    + 1 new decoupling test that drops teaching.math_contemplation from
    sys.modules and verifies the workbench still loads + serves detail

Verification
  - core eval math-contemplation produces the expected 3/2/3 distribution
  - core test --suite teaching -q → 33 passed
  - core test --suite runtime  -q → 20 passed
  - All 57 ADR-0172 W1-W4 tests pass (49 existing + 8 new)

Determinism / invariants preserved
  - canonical_bytes() byte-stable (test pins this)
  - to_jsonl_record() byte-stable via sort_keys=True + no floats
  - wrong=0 invariant: proposals stay evidence-only; no auto-apply
  - ChangeKind Literal unchanged (4 values; no new ones invented)
2026-05-27 13:43:16 -07:00
Shay
fbbc57edff
feat(ADR-0172/W3): core eval math-contemplation CLI lane (#384)
Wires `decompose_audit()` into a new `core eval math-contemplation`
subcommand:

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

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

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

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

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

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

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

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

Tests: 12 new, full teaching suite green (17 passed).
2026-05-27 12:21:59 -07:00
Shay
00c3968937
fix(ADR-0167): route contemplation and proposal replay by candidate domain (#363)
* fix(teaching): select proposal replay gate from candidate domain

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

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

* test(teaching): pin domain-aware contemplation partition
2026-05-27 09:43:16 -07:00
Shay
e2e53362f5
feat(ADR-0167/W2-D): lexical ratification handler (#354) 2026-05-27 06:57:37 -07:00
Shay
85bfa188ed
feat(ADR-0167/W2-B): lexical claim signature + dedup (#353)
Adds `teaching/math_claim_signature.py` with `lexical_claim_signature()`:
sha256 hex of a normalised lexical token, collapsing two refusal cases on
the same surface token into one teaching-corpus candidate.

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

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

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

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

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

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

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

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

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

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

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

Wave 1 only — no runtime mutation, no teaching-store integration, no
admission path.  Downstream W2-A/B/C/D type-import from this module.
2026-05-27 06:30:21 -07:00
Shay
72fac59029
feat(ADR-0161.3): submission-time invariants — duplicate + dependent_on_pending auto-reject (#313)
Adds two pre-gate checks to propose_from_candidate that fire after the
Step 2 capacity check and before the replay gate.  No log entry is
written on either refusal — the append-only invariant holds.

Check order at function entry (ADR-0161 §3):
  1. Capacity (Step 2)          → RefusedAtCapacity
  2. Duplicate                  → RefusedAsDuplicate
  3. Dependent_on_pending       → RefusedAsDependent
  4. Replay gate                → auto-reject on regression

New frozen dataclasses:

  @dataclass(frozen=True, slots=True)
  class RefusedAsDuplicate:
      proposal_id: str
      existing_state: str        # covers all states: pending/accepted/rejected/withdrawn
      reason: str = "duplicate"

  @dataclass(frozen=True, slots=True)
  class RefusedAsDependent:
      candidate_id: str
      dependent_on: tuple[str, ...]       # pending proposal_ids that block
      overlapping_lemmas: tuple[str, ...] # normalised lemmas that triggered
      reason: str = "dependent_on_pending"

Lemma-overlap rule: case-insensitive exact-match on strip().lower().
Conservative — over-reject rather than admit-with-hidden-dependency.
False positives are recoverable (re-emit after blocker is ratified);
false negatives silently couple ratification choices.

CLI surfaces both outcomes in cmd_teaching_propose and
cmd_teaching_propose_from_exemplars (exit code 1).

Step 2 backpressure tests updated: made pre-populated candidates use
unique objects to avoid triggering the new dependency check, and
updated idempotency assertions to reflect the new RefusedAsDuplicate
return for re-submitted content.

Co-references: ADR-0161 §3, Step 1 PR #296, Step 2 PR #311,
ADR-0057, ADR-0151.
2026-05-26 16:46:25 -07:00
Shay
76032db9a0
feat(ADR-0161.2): HITL queue backpressure — pending-count cap + queue_full reports (#311) 2026-05-26 16:16:08 -07:00
Shay
ac77b88864
chore(ratify): accept four Phase C round-2 recognizers (round 2) (#309)
* chore(ratify): accept four Phase C round-2 recognizers (round 2)

Operator ratification of the four Phase B round-2 proposals per
ADR-0163:

- 8c7645b4 — discrete_count_statement
- 03627f6f — multiplicative_aggregation
- 00547671 — currency_amount
- 4d47a247 — temporal_aggregation (v2 widening)

All four passed Phase C's admissibility replay gate at propose-time:
replay_equivalent=True, wrong_count_delta=0.  Each acceptance also
appends the synthetic admissibility chain to teaching/cognition_chains.

Post-ratification empirical signal (verified by running the
train_sample lane):
- correct: 3 (unchanged)
- refused: 47 (unchanged)
- wrong: 0 (unchanged — invariant holds)

The case-level lift did not materialize because the architectural
bottleneck migrated from STATEMENT admission to QUESTION admission.
44 of 47 cases now refuse on a QUESTION (vs 7 pre-ratification).
The four new recognizers' matchers fire on 36 of 47 first-failed
sentences, but the cases then refuse on a different (later)
sentence — typically the question itself.

The unlock for this round is Phase D.3 (conditional-prefix question
recovery, PR #308) + a follow-up parser-grammar extension to handle
mass nouns (how much), modal verbs (will be able to), and pronoun
entity resolution.  Those touch grammar surface, not admission
wiring; separate ADR.

This PR commits the ratification audit trail.  The lift composes
when Phase D.3 lands and the grammar layer follows.

wrong=0 invariant: preserved by Phase D's skip-only construction.
Statement-level recognizer matches contribute zero math state to
the Cartesian product; no recognizer can introduce a wrong answer
under skip-only semantics.

Cross-references: ADR-0163, Phase A PR #297, Phase B round 1 PR
#298, Phase C PR #301, Phase D PR #302, ratify round-1 PR #304,
docs PR #305, Phase B round 2 PR #306, Phase C round-2 extension
PR #307, Phase D.3 PR #308.

* chore(ratify): re-pin public_demo lane SHA after round-2 ratification

The four round-2 ratifications appended synthetic admissibility
chains to teaching/cognition_chains/cognition_chains_v1.jsonl,
which is consumed by the public_demo lane.  The lane's deterministic
output SHA changed accordingly — drift confirmed by CI on origin
PR #309 (`✗ public_demo  e323adb35ea17987..  expected 888ddd0d12635d70..`).

Re-pin per the standard remediation:

  python scripts/verify_lane_shas.py --update
  python scripts/generate_claims.py

This is the expected corpus-mutation cycle following ratification.
No code change, no test change.  The new public_demo SHA reflects
the engine's new admissibility surface; the lane runner's output
is byte-stable under the new corpus.

Cross-references: ratify round-2 PR #309 (this branch), Phase D
PR #302, Phase C PR #301.
2026-05-26 16:03:01 -07:00
Shay
1f5ffcf6c7
feat(ADR-0163.C.2): extend exemplar ingest + synthesis + matchers for round-2 categories (#307)
Unblocks the four Phase B round-2 exemplar corpora (PR #306) so they
can flow through `core teaching propose-from-exemplars`.  The corpora
were committed in #306 but Phase C's ingest validator + synthesizer
were hard-coded to round-1 categories; this PR closes that gap.

Extends three modules with the three new categories
(discrete_count_statement, multiplicative_aggregation, currency_amount):

- teaching/exemplar_ingest.py — per-category validator dispatch +
  _SUPPORTED_CATEGORIES.  The file-stem rule loosens from
  exact ``<category>_v1`` to ``<category>_v<N>`` so the
  temporal_aggregation v2 widening from #306 ingests.
- teaching/recognizer_synthesis.py — per-category synthesizers
  following the same observed_*-set + coverage-histogram pattern as
  round 1.  Determinism, narrowness rule (narrower-not-broader),
  rules-only — same discipline.
- generate/recognizer_match.py — per-category matchers shipped as
  DETECTION-ONLY (return empty parsed_anchors).  Consistent with
  Phase D's current skip-only wiring (PR #302).  Real value
  extraction lands when Phase D.2 plumbs parsed_anchors into the
  solver; until then, detection-only is the right shape and
  preserves wrong=0 by construction.

  graph_intent Literal expanded to include "count" and "amount".

Test updates:
- tests/test_exemplar_ingest.py: extend _ROUND_1 with _ROUND_2;
  test_list_corpora_loads_every_round_1_file now asserts every
  committed corpus (round 1 + round 2) loads.
- tests/test_recognizer_registry.py: rename + repair
  test_live_proposal_log_has_phase_c_pending_proposals →
  test_live_proposal_log_has_phase_c_proposals.  The original
  asserted state=="pending"; PR #304 ratified the three, so the
  test now asserts state=="accepted" and registry length matches.
  Pre-existing failure on main, fixed here.

Validation:
- 132 passed across exemplar_ingest, recognizer_synthesis,
  recognizer_match, recognizer_registry, candidate_graph_wiring,
  admissibility_exemplars, refusal_taxonomy_lane,
  admissibility_replay_gate
- 222 capability-axis tests passed / 2 pre-existing main failures /
  3 skipped — G1..G5 + S1 wrong=0 invariant intact
- 67 smoke passed
- End-to-end CLI sanity check: `core teaching propose-from-exemplars
  teaching/admissibility_exemplars/discrete_count_statement_v1.jsonl
  --log /tmp/test.jsonl` produced proposal_id 8c7645b4..., state
  pending, replay_equivalent=True, wrong_count_delta=0

Empirical projection: of 47 still-refused GSM8K train_sample
statements, ~22 match the discrete_count_statement recognizer, ~2
match multiplicative_aggregation, plus 3 rate_with_currency + 3
temporal_aggregation + 18 descriptive_setup_no_quantity recognized
under the existing round-1 wiring.  After operator ratifies round-2
proposals, the candidate-graph skip-only wiring will drop those
sentences from the math state and a meaningful lift is projected.
wrong=0 preserved at every level by Phase D's skip-only
construction.

Scope: enables the round-2 pipeline; does NOT ratify anything;
does NOT modify generate/math_candidate_graph.py.  Operator runs
propose-from-exemplars + review --accept after merge.
2026-05-26 15:08:41 -07:00
Shay
47c0a03d3b
feat(ADR-0163.B.2): four new exemplar corpora — discrete_count_statement, multiplicative_aggregation, currency_amount, plus temporal_aggregation v2 widening (#306)
Phase B round 2.  Categorizing the post-#304 GSM8K train_sample's
still-refused 47 set surfaced three coherent sub-shapes in the previously
UNCATEGORIZED tail plus five ratified-but-narrowness-blocked temporal
cases; this PR ships the operator-authored exemplar seeds + Phase A
categorizer extension that prove the corridor scales beyond round 1.

Exemplar corpora (70 new exemplars across 4 files):
- discrete_count_statement_v1.jsonl (20)
- multiplicative_aggregation_v1.jsonl (20)
- currency_amount_v1.jsonl (20)
- temporal_aggregation_v2.jsonl (10, widening)

Each corpus carries ≥3 verbatim train-sample citations, ≥12 (≥5 for v2)
novel operator-authored statements, and ≥1–3 edge cases.  Statements are
disjoint across all 7 round-1 + round-2 corpora; tests enforce.

Phase A categorizer (evals/refusal_taxonomy/shape_categories.py)
extends ShapeCategory with three new members and inserts their rule
predicates AFTER the existing more-specific categories:
- rate_with_currency before currency_amount
- multiplicative_aggregation before discrete_count_statement
Each new rule predicate cites ≥3 train_sample case_ids in its docstring
(ADR-0163 §Risks).  No LLM, no embedding, no learned classifier.

Refusal-taxonomy histogram empirical signal (public 50 sample):
- pre-round-2: 14 UNCATEGORIZED (categorized_rate 0.72)
- post-round-2: 1 UNCATEGORIZED (categorized_rate 0.98)

The single residual is case 0044 ("10% simple interest" — percentage
without change verb), an honest tail outside the three round-2 shapes.

wrong=0 holds on capability axes G1..G5 + S1; no runtime code shipped.
Smoke suite green (67/67).

Cross-refs: ADR-0163, #297 (Phase A), #298 (Phase B round 1),
#301 (Phase C), #302 (Phase D), #304 (round-1 ratify), #305 (session
recap).

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:36:59 -07:00
Shay
062d53f151
chore(ratify): accept three Phase C exemplar_corpus recognizers (round 1) (#304)
Accepts:
  - 59223f13... — descriptive_setup_no_quantity
  - 46ce297f... — rate_with_currency
  - a3b89254... — temporal_aggregation

  All three carry replay_equivalent=true and wrong_count_delta=0
  from Phase C's admissibility gate (PR #301).  Per ADR-0161 §5,
  ratification is operator-only; this is the round-1 ratification.
2026-05-26 13:35:57 -07:00
Shay
e9b7eb0b1f
feat(ADR-0163.D): wire ratified RecognizerSpecs into math_candidate_graph admissibility surface (#302)
* chore(ADR-0163.C): land three Phase C pending proposals in live log

Phase C (#301) shipped the CLI but its PR dry-run wrote to a tmp log
path.  This commit moves the three Phase C proposals into the live
teaching/proposals/proposals.jsonl so the Phase B→C audit trail is
visible in the proposal log and the proposals are ready for the
operator to ratify after Phase D ships.

Proposals (all state=pending, kind="exemplar_corpus"):
- 59223f13722f906a1cf9b65d9b01c990 — descriptive_setup_no_quantity
- 46ce297f797ff16da12db5de422ca3c9 — rate_with_currency
- a3b892546977c5f0f64c578d6052adbd — temporal_aggregation

Produced by `core teaching propose-from-exemplars --all` against the
live Phase B corpora.  No ratification (ADR-0161 §5 — only the repo
owner ratifies).  The Phase D admissibility-replay gate confirmed
replay_equivalent=true, wrong_count_delta=0 for all three.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(ADR-0163.D): wire ratified RecognizerSpecs into math_candidate_graph admissibility surface

Phase D is the first PR to extend the math admission surface.  The
audit (#294) said the gap was admission, not operators, algebra,
substrate, or packs.  Phase A measured the refusal taxonomy.  Phase B
authored seeds.  Phase C synthesized recognizers.  Phase D wires
those recognizers into generate/math_candidate_graph.py.

Modules
- generate/recognizer_registry.py — pure projection over the proposal
  log.  Only proposals with source.kind="exemplar_corpus" AND
  review_state="accepted" enter the tuple.  Sorted by
  (review_date, proposal_id).  In-process cache keyed on log
  (mtime, sha256) — no filesystem cache (ADR-0161 §1).  Malformed
  accepted specs raise RegistryLoadError citing the offending
  proposal_id; silent drops are forbidden.
- generate/recognizer_match.py — per-category rules-only matchers
  (no LLM, no embedding, no learned classifier).  Honors the Phase C
  synthesizer's narrowness rule: out-of-corpus currency symbols,
  window units, and per-unit values do NOT match.  Three matchers:
  _match_descriptive_setup_no_quantity (zero-quantity surface),
  _match_temporal_aggregation (event_count_per_window with
  observed_window_units/quantifiers honored), _match_rate_with_currency
  (currency_per_unit_rate with observed currency/per-unit/amount-kind
  honored).
- generate/math_candidate_graph.py — narrowest-edit guard at the
  per-statement choice loop.  Before the existing
  "no admissible candidate for statement" refusal, consult the
  ratified registry.  Recognized statements are dropped from
  per_sentence_choices (zero math state) so the Cartesian product is
  identical to "this statement was never there."  Empty registry is
  a no-op — backward compatibility preserved byte-identically.
  Downstream consumption of parsed_anchors (turning recognized
  rate/temporal surfaces into solver state that produces concrete
  answers) is Phase E follow-up.

Tests (32 new)
- tests/_phase_d_fixture.py — synthetic in-memory ratified registry
  built from the three Phase C pending proposals' content.  Per
  ADR-0161 §5 the agent does NOT ratify the live log; the synthetic
  registry round-trips the real RecognizerSpec bytes the operator
  will ratify after Phase D ships.
- tests/test_recognizer_registry.py (9) — empty/pending/wrong-kind
  filtering, sort order, malformed-spec rejection, cache hit +
  invalidation, live-log Phase C audit check.
- tests/test_recognizer_match.py (14) — per-category positive cases,
  narrowness (out-of-corpus surface forms rejected), no-LLM import
  check.
- tests/test_candidate_graph_recognizer_wiring.py (7) — empty registry
  preserves existing refusal; synthetic registry: recognized
  statements no longer trigger per-statement refusal;
  wrong_count_delta == 0 on GSM8K train_sample; capability axes G1..
  G5+S1 wrong=0 unchanged; per-category admission counts on the
  refused-set; unrecognized statements still refuse with the
  existing reason.
- tests/test_phase_d_replay_evidence.py (2) — full admissibility
  replay gate under synthetic registry: replay_equivalent=true,
  wrong_count_delta=0, every capability axis wrong=0; each
  ratified recognizer admits >= 1 train_sample statement (wiring
  is consequential).

Per-category fixture-based admission counts (synthetic registry vs
GSM8K train_sample refused-set sentences):
- descriptive_setup_no_quantity: 40
- rate_with_currency:             2
- temporal_aggregation:           7

Narrowness-invariant negative case results (matcher correctly
returns None on out-of-corpus / load-bearing-math surfaces):
- rate_with_currency:           "She paid $5 for the book." (no per-unit)
- temporal_aggregation:         "On Saturday she went to the store." (single day token)
- descriptive_setup_no_quantity: "There are some kids in camp." (indefinite quantifier)

Candidates for Phase B round 2 (3 of 20 temporal seeds match the
spec's structural commitment but not my surface regex — author_notes
explicitly flagged these as schema-gap edge cases):
- ta-v1-0004 "Mark does a gig every other day for 2 weeks."
- ta-v1-0012 "Robin walks 4 dogs every other day around the park."
- ta-v1-0019 "The pump fills the tank with 80 gallons over 6 hours."

Three landed wirings DO NOT shift the GSM8K train_sample baseline
counts under fixture (correct=3, wrong=0, refused=47 unchanged) —
Phase D's narrow wiring is wrong=0 safe by construction; lift to
"correct" requires Phase E's downstream parser-side consumption of
parsed_anchors.  Capability axes G1..G5+S1 wrong=0 unchanged.

Cross-refs: ADR-0163 (Phase D), ADR-0057 (proposal review),
ADR-0151 (auto-proposal), ADR-0161 §5 (ratification boundary),
Phase A PR #297, Phase B PR #298, Phase C PR #301.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:11:47 -07:00
Shay
08c5e0e82f
feat(ADR-0163.C): contemplation ingests admissibility exemplars and emits DerivedRecognizer proposals through the HITL corridor (#301)
Phase C is the first phase where operator-authored exemplar corpora
become engine-derived recognizer proposals automatically.  The math
thesis ("decodes, not generates") manifests in the math lane here.

Modules
- teaching/exemplar_ingest.py — pure-function loader for Phase B
  exemplar JSONLs.  ExemplarCorpus carries a sha256 digest over its
  canonical (sorted-by-exemplar_id, sort-keyed) bytes.
- teaching/recognizer_synthesis.py — per-category synthesizers
  (_synthesize_descriptive_setup_no_quantity / _temporal_aggregation /
  _rate_with_currency) distil a corpus into one RecognizerSpec.
  Determinism: same corpus -> byte-identical spec.  Narrowness: the
  spec records only observed sub-shapes; an out-of-corpus currency
  symbol or window unit does not match.  Phase B author_notes surface
  in canonical_pattern.unresolved_notes — never silently dropped.
- teaching/contemplation.py — contemplate_exemplar_corpus(corpus)
  returns a DiscoveryCandidate whose proposed_chain encodes the
  RecognizerSpec as a synthetic four-field chain plus the full
  recognizer_spec submap.  Evidence cites every exemplar's case_id.
- teaching/replay.py — run_admissibility_replay_gate(spec, *,
  active_corpus_path=None) runs cognition + G1..G5+S1 + GSM8K
  train_sample.  In-process baseline cache keyed on the active
  corpus digest.  WRONG-COUNT INVARIANT: if a candidate run lifts
  the GSM8K train_sample wrong count, gate returns
  replay_equivalent=False with
  regressed_metrics=["gsm8k_train_sample_wrong_count"].
- teaching/source.py — ProposalKind widened with "exemplar_corpus";
  exhaustive-match docs + tests updated.

CLI
- core teaching propose-from-exemplars <path> [--all] [--review-date]
  [--log] [--json].  Routes the candidate through the existing
  propose_from_candidate path with the admissibility gate substituted
  for the cognition-only run_replay_equivalence.  Never auto-accepts;
  proposals land as pending for operator review.

Tests (38 new)
- tests/test_exemplar_ingest.py (12) — load, digest stability,
  malformed-record rejection, file-name binding, read-only purity.
- tests/test_recognizer_synthesis.py (16) — determinism, purity,
  per-category subsumption, narrowness (out-of-corpus seeds rejected),
  author_notes surfaced.
- tests/test_admissibility_replay_gate.py (6) — happy path, cache
  hit/invalidation, WRONG-COUNT INVARIANT regression, capability-axis
  regression, cognition regression.
- tests/test_propose_from_exemplars_cli.py (4) — single corpus, --all,
  determinism, read-only snapshot.

Acceptance evidence (dry run)
- All three Phase B corpora produce replay_equivalent=true,
  wrong_count_delta=0.  Proposal IDs:
    descriptive_setup_no_quantity: 59223f13722f906a1cf9b65d9b01c990
    rate_with_currency:            46ce297f797ff16da12db5de422ca3c9
    temporal_aggregation:          a3b892546977c5f0f64c578d6052adbd
- G1..G5+S1 wrong=0 unchanged; GSM8K train_sample 3/47/0 unchanged.
- core test --suite smoke -q: 67 passed.
- uv run core eval refusal_taxonomy: case_digest
  d030f826cb0f4088771d90c52c8be2ff75054ab27c7d47eae8dbfe1225b2eea1
  unchanged.

Cross-refs: ADR-0163 (Phase C), ADR-0057 (gating discipline),
ADR-0151 (auto-proposal), ADR-0152 (learning-arc), ADR-0149/0154
(recognizer pipeline), ADR-0094 (ProposalSource), Phase A PR #297,
Phase B PR #298.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:26:56 -07:00
Shay
1bff5689db
feat(ADR-0163.B.1): exemplar corpora — descriptive_setup_no_quantity, temporal_aggregation, rate_with_currency (#298)
Round 1 of ADR-0163 Phase B: hand-author seed exemplars for the top three
refusal shape categories surfaced by the Phase A histogram. These corpora
are INPUT to the Phase C contemplation runner, which will derive
DerivedRecognizer proposals from them; this PR ships no recognizer logic,
no proposal logging, and no runtime change.

Per-category breakdown:
- descriptive_setup_no_quantity_v1.jsonl — 20 exemplars (5 train + 12 novel + 3 edge)
- temporal_aggregation_v1.jsonl          — 20 exemplars (4 train + 13 novel + 3 edge)
- rate_with_currency_v1.jsonl            — 20 exemplars (3 train + 14 novel + 3 edge)

Train-sample citations resolve against
evals/gsm8k_math/train_sample/v1/report.json (the 50-case sample only;
public/holdout/full splits NOT mined per ADR-0163 §Constraints).

Each file is sorted by exemplar_id, byte-canonical, and disjoint from the
others. Statements are surface-preserved verbatim from the train sample
where cited.

Validation:
- tests/test_admissibility_exemplars.py: 20/20 passed (schema, enum
  binding, per-category quantity_anchor dispatch, cross-file disjointness,
  >=3 train-sample citations per category, sort/byte-canonical determinism,
  read-only import invariant)
- tests/test_adr_0131_*.py: 224 passed / 3 skipped — capability axes
  G1..G5 + S1 remain wrong=0
- core test --suite smoke: 67 passed
- core eval refusal_taxonomy: case_digest unchanged
  (d030f826cb0f4088771d90c52c8be2ff75054ab27c7d47eae8dbfe1225b2eea1)
- Phase A categorize() agrees with the file's category for all 60
  statements (sanity check; not pinned in tests since the rules-only
  categorizer is coarser than the recognizer Phase C will derive)

Author notes on quantity_anchor annotation calls flagged for operator
review are embedded in provenance.author_note where ambiguous (notably:
'in N minutes' / 'over N hours' window framings collapsed to
window_quantifier='per', 'every other day' approximated as 'every',
day-of-week labels not captured in the schema, 'for one X' / slash-form
per-unit framings, non-USD currencies, and discrete-occurrence per_unit
values like 'event' and 'session').

Refs: ADR-0163 §Phase B; depends on the Phase A lane shipped in #297.
Cross-refs: ADR-0057 (proposal review), ADR-0149/0154 (recognizer
pipeline), ADR-0161 (HITL queue), [[thesis-decoding-not-generating]].
2026-05-26 11:52:23 -07:00
Shay
ec5d6f5ac7
feat(ADR-0161.1): core teaching queue list|show — read-only queue projection (#296)
* docs(math): ADR-0163 — path to GSM8K mastery via candidate-graph admissibility (proposed)

Audit reframes the math roadmap entirely.

State of main: every named math capability axis (G1..G5, S1) passes
at 100% with wrong=0 on its controlled lane.  binding_graph,
math_versor_arithmetic, math_symbolic_equivalence, math_parser,
math_candidate_parser, math_solver, math_verifier, math_realizer,
math_problem_graph — all landed.  The worktrees on disk are stale
forks.

State of GSM8K (50-case train sample): correct=0, refused=50, wrong=0.
Every refusal reason is identical: "candidate_graph: no admissible
candidate for statement: <STATEMENT>".

The reframe: the gap is NOT in operator algebra, NOT in binding graph
internals, NOT in symbolic equivalence.  The gap is in
generate/math_candidate_graph.py — the admissibility surface that
turns a natural-language statement into a candidate the downstream
pipeline can consume.  The capability axes pass at 100% because they
test statement shapes the candidate-graph already admits.  GSM8K
refuses at 100% because its statements span shapes the candidate-graph
has never been taught.

Six-phase plan to lift GSM8K under the thesis "decodes, not generates":

A. Refusal taxonomy (measure before building)
B. Exemplar corpora per shape category (≤20 statements each, ≤3 per round)
C. Contemplation runner ingests exemplars; emits DerivedRecognizer
   proposals
D. Operator ratifies through ADR-0161 HITL queue (no new surface)
E. Re-baseline GSM8K train sample.  Round 1 exit: correct ≥ 10, wrong = 0.
   Round 2: ≥ 25.  Round 3: ≥ 35.
F. Scale to public/v1 (200 cases, target correct ≥ 100), then
   holdout (measurement-only — never tune against).

Three non-negotiables:
- wrong = 0 at every phase.  Auto-rejected by replay gate, not by
  operator vigilance.
- No hand-rolled recognizers in generate/.  Every recognizer lands
  via contemplation → proposal → review corridor.
- Active corpus mutation only via accept_proposal.

Status: proposed.  Implementation lands as three PRs starting with
Phase A scaffolding.

Scope discipline: docs-only.  No code, no eval changes, no corpus
mutation.

* feat(ADR-0161.1): core teaching queue list|show — read-only queue projection

* fix(ADR-0161.1): restore gap-queue CLI + rename new commands to hitl-queue + R1..R5 refinements
2026-05-26 11:42:51 -07:00
Shay
df6c9a3206
feat(W-017): load-time auto-proposal pipeline from enriched candidates (ADR-0151) (#275)
Wires contemplation-enriched DiscoveryCandidates into the ADR-0057 proposal
gate at _load_engine_state(). Proposals land in ProposalLog with
source.kind="contemplation"; operator ratification via existing
core teaching review path unchanged.
2026-05-25 12:46:10 -07:00
Shay
9bbdcc96aa
feat(W-008): L10 Shape B hybrid engine-state persistence (#271)
* ci: re-trigger full-pytest

* docs: ADR-0146 — L10 Shape B hybrid engine-state persistence

* feat(W-008): Shape B engine-state persistence spike (ADR-0146)

* fix(W-008): eval isolation + env-var path + empty-manifest guard

- evals/run_cognition_eval.py: all ChatRuntime() calls pass no_load_state=True
  so parallel eval workers never touch engine_state/ checkpoints
- engine_state/__init__.py: honour CORE_ENGINE_STATE_DIR env var (ADR-0146 spec)
- engine_state/__init__.py: load_manifest() skips empty file instead of crashing
  (defensive against partial writes in concurrent contexts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 11:45:54 -07:00
Shay
a118977f0f
chore(teaching): MetricSet dataclass (ADR-0142 debt #3) (#231) 2026-05-24 15:15:02 -07:00
Shay
6e072f95be
content(en_core_relations_v1): +14 kinship/social lemmas + +14 chains, cognition eval byte-identical, ratify-idempotent (#211)
* content(packs): update relations checksum

* revert transient relations manifest checksum

* content(packs): extend relations lexicon additively

* content(teaching): extend relations chains additively

* content(packs): ratify relations manifest checksum

* test(packs): accept additive relations lemma extension

* test(packs): add relations v1 extension regressions

* fix(tests): align relations extension lemma set

* content(packs): add relations mastery report

* content(packs): drop unused .mastery_report.json sidecar

Language packs do not consume mastery reports — the pattern is from
identity packs (packs/identity/) and has no consumer in language_packs/
loader.py or compiler.py. The added sidecar's self-seal hash also did
not validate against sha256(json.dumps(body, sort_keys=True,
separators=(',', ':'))).

Drop the file. The actual ratification surface for this pack is the
manifest.json lexicon_checksum, which still matches lexicon.jsonl
bytes (verified).
2026-05-23 22:47:53 -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
ed759d1b43
feat(ADR-0131.2): teaching-corpus math eval — lane PASSED 30/30 (#172) 2026-05-23 10:44:25 -07:00
Shay
5f149340cc
feat(contemplation): land ADR-0080 phase 1 (#119) 2026-05-22 13:10:03 -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
7dc7e9d5eb feat(teaching): implement ADR-0095 — Miner-Sourced Teaching Proposals
Closes the Phase-5 contemplation loop in code. Articulation-quality,
contradiction-detection, and frontier-compare miners (already shipping)
now have a route to file PackMutationProposal candidates that traverse
the single reviewed teaching path. Construction-only; never promotes
to coherent.

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

- new teaching/source.py: frozen ProposalSource dataclass with sealed
  ProposalKind Literal["operator","miner","curriculum"], runtime
  invariants (operator → empty source_id; miner/curriculum → non-empty),
  serialize() ("operator" / "miner:<id>" / "curriculum:<id>"),
  as_dict/from_dict round-trip, ProposalSource.operator() helper
- TeachingChainProposal.source field added (proposals.py)
- PackMutationProposal.source field added (store.py)
- build_proposal() accepts optional source kwarg; default uses
  _default_operator_source() pinned at cached git HEAD SHA
- ProposalLog.current_state() now strictly requires source on every
  created event; raises ProposalError with migration pointer if missing;
  validates via ProposalSource.from_dict so malformed payloads reject
- teaching/migrate_proposals_source_field.py: deterministic one-shot
  migration script using PRE_MIGRATION_SENTINEL ("pre-adr-0094-migration")
  as the emitted_at_revision so re-runs across commits produce identical
  bytes
- migration applied to live proposals.jsonl: 11 created events gained
  source field; 33 non-created events untouched; idempotent verified
- 29 unit tests in test_proposal_source.py covering construction,
  serialization, exhaustive-match pattern with assert_never,
  migration determinism (3 idempotence/cross-run tests), strict-parse
  rejection, live-log loads
- 2 test fixes in test_epistemic_invariants.py for new required source param
- smoke 67/67, teaching 17/17, cognition 120/121 (1 pre-existing skip),
  runtime 19/19; cognition eval byte-identical 100/100/100/100
2026-05-21 18:11:09 -07:00
Shay
3d922a1532
Add chain-first capability ledger and domain seeds (#97) 2026-05-20 21:33:24 -07:00