Commit graph

79 commits

Author SHA1 Message Date
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
5b4dcb17ca
feat(ADR-0163.A): refusal taxonomy lane — shape categorization of GSM8K admissibility gaps (#297)
ADR-0163 Phase A measurement. Reads the GSM8K train-sample refusal report
(50 cases, all refused on candidate-graph admissibility) and emits a
histogram of statement shapes. Read-only: no corpus, pack, or proposal
mutation; the categorizer is rules-only with no LLM, embedding, or
learned model.

Lane: evals/refusal_taxonomy/ (auto-discovered by evals.framework)
  - shape_categories.py — ShapeCategory enum + deterministic categorizer
    (9 ADR-mandated baseline categories + UNCATEGORIZED, first-match-wins)
  - runner.py           — pure run_lane(cases) -> LaneReport
  - contract.md         — purpose, doctrine, schema, ADR compatibility
  - public/v1/cases.jsonl — 50 refused statements (sorted by case_id)
  - v1/report.json        — first run output (categorized_rate=72%)

CLI: core teaching refusal-taxonomy [--input PATH] [--json] [--save]
     Accepts a cases JSONL or a raw GSM8K eval report.json directly.

Helper: scripts/build_refusal_taxonomy_cases.py rebuilds the v1 case set
from the GSM8K train-sample report deterministically.

Tests: tests/test_refusal_taxonomy_lane.py (21 passing) cover schema
integrity, lane auto-discovery, enum exhaustiveness, categorizer
determinism + purity + no-ML-imports, histogram correctness, replay
byte-identity, committed report match, helper extraction, and a
read-only invariant snapshot over teaching/, packs/, language_packs/data/.

v1 histogram (50-case sample):
   17  descriptive_setup_no_quantity
   14  uncategorized
    4  temporal_aggregation
    3  rate_with_currency
    3  fractional_rate_of_change
    3  indefinite_quantity
    3  comparative_with_unit
    2  nested_question_target
    1  unit_partition
    0  conditional_quantity
total=50  categorized_rate=72%  uncategorized=28% (below 50% target)

Top three by count (Phase B candidates):
  1. descriptive_setup_no_quantity (17)
  2. temporal_aggregation (4)
  3. tie at 3 — operator selects from {rate_with_currency,
     fractional_rate_of_change, indefinite_quantity, comparative_with_unit}

Phase B is not started in this PR — the ADR explicitly requires the
operator to ratify the top-N selection before any exemplar corpus is
authored.

Invariants verified:
  - tests/test_adr_0131_*.py: 224 passed, 0 wrong on G1..G5 + S1
  - core test --suite smoke -q: 67 passed
  - The refusal_taxonomy/__init__.py and runner do not import openai,
    anthropic, transformers, torch, sklearn, sentence_transformers,
    requests, or httpx — verified by test_categorizer_no_llm_or_ml_imports.

Cross-references: ADR-0163 (parent), ADR-0114a (capability obligations),
ADR-0149 (recognizer pipeline substrate that Phases C–E build on).

Refs: [[thesis-decoding-not-generating]] — the rules-only categorizer
honors the doctrine: the engine learns to find better shapes; this PR
does not stuff it with another found pattern.
2026-05-26 11:27:11 -07:00
Shay
8a24ebe726
feat(W-026): read-only workbench API (ADR-0160 Phase 1) (#292)
* feat(W-026): add read-only workbench API

* fix(workbench): harden read-only API review gaps
2026-05-26 10:16:35 -07:00
Shay
8829529ed0
fix(W-025): polish contemplation-quality eval lane follow-ups (#290)
Three follow-ups raised in the W-025 PR #286 review, completed together so
the lane reaches its full mastery-level contract.

1. ``core eval`` failure-printer is now gated on ``lane_name == "cognition"``.
   Before this fix, every non-cognition lane that returned clean case_details
   without ``intent_correct``/``versor_closure`` keys triggered a spurious
   ``failures (N): <case_id>: intent, versor=0.00e+00`` block at the end of
   the human-readable output, even when every metric passed.  This matched
   the gating pattern already used for the workers preamble at the top of
   ``cmd_eval``.

2. EPILOG examples in ``core/cli.py`` now advertise
   ``core eval contemplation_quality`` and the ``--json --save`` form, so
   the lane is discoverable from ``core --help`` and not only from
   ``core eval --list``.

3. Tightened the learning-arc demo's Scene 5 to thread the demo's
   tempdir-scoped ``engine_state_dir`` into the second ``ChatRuntime``.
   The previous default-constructed runtime checkpointed to the repo's
   ``engine_state/``, which contradicted ADR-0159's read-only claim.
   ADR-0146/0150 still govern the runtime checkpoint path itself.

Tests:

- ``tests/test_contemplation_quality_lane.py`` (35 tests):
  case-set integrity, lane discovery, ``evaluate_report`` purity over
  well-formed / malformed / boundary-violating inputs, ``run_lane``
  invocation-contract enforcement (single case, supported source enum),
  and a read-only invariant snapshot on ``teaching/corpora``, ``packs/``,
  and ``language_packs/data/``.

- ``tests/test_eval_cli_failure_printer.py`` (4 tests): pins the
  cognition-only gating of the failure printer with stubbed
  ``evals.framework`` so the regression cannot return as a lane-blind
  condition.

Validation:

  uv run pytest tests/test_contemplation_quality_lane.py \
                tests/test_eval_cli_failure_printer.py \
                tests/test_learning_arc_demo.py -q   # 50 passed
  uv run core test --suite smoke -q                  # 67 passed
  uv run core eval contemplation_quality              # 9/9 passed, clean output
2026-05-26 09:39:18 -07:00
Shay
e7e28a2fd5
feat(W-019): learning-arc demo — engine-authored proposal from contemplation (ADR-0152) (#276)
Two-session arc where engine derives connective+object from corpus
decomposition; operator ratifies rather than authors. Distinguishes
from learning-loop (operator-authored) and directly exercises W-018
checkpoint contemplation and W-017 auto-proposal provenance path.
2026-05-25 13:03: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
2c5eb2e36b
feat(w019): wire core teaching propose-miner/propose-curriculum CLI commands (#266)
Closes W-019 wiring debt. Per Phase 2 operator decision (path a):
wire CLI — smallest reachability fix, no architectural commitment.

teaching/from_miner.py and teaching/from_curriculum.py (ADR-0095/ADR-0104)
correctly build source-stamped PackMutationProposals but had no CLI or
runtime caller — test-live only. Now reachable:

  core teaching propose-miner  \
      --findings <jsonl> --miner-id <id> [--revision <rev>] [--out <jsonl>]

  core teaching propose-curriculum \
      --findings <jsonl> --curriculum-id <id> [--revision <rev>] [--out <jsonl>]

Changes to core/cli.py:
- _load_findings_jsonl(): deserializes ContemplationFinding records from
  operator-provided JSONL (as_dict() round-trip format).
- _read_jsonl_file(): shared JSONL line reader.
- cmd_teaching_propose_miner(): calls from_miner.from_findings(); writes
  proposals to --out JSONL or stdout; prints proposals/rejections summary
  to stderr. Returns 0 if any proposals built, 1 otherwise.
- cmd_teaching_propose_curriculum(): same shape for curriculum path.
- _current_git_revision(): --revision default, falls back to "unknown".
- _write_miner_curriculum_batch(): shared proposal serialisation + summary.
- Two new subcommands registered: propose-miner, propose-curriculum.

tests/test_teaching_propose_cli.py: 5 tests covering round-trip loading,
stdout output, file output, and empty-findings error path.
2026-05-25 06:09:57 -07:00
Shay
a1a085057e
feat(w013): wire explain_last_turn() into core chat /explain REPL command (#265)
Closes W-013 wiring debt. Per Phase 2 operator decision: wire
core.cognition.explain into the live core chat REPL.

Changes:
- core/cognition/explain.py: add explain_from_intent(intent, correction_text)
  companion to explain() — same dispatch table, skips the full
  CognitiveTurnResult round-trip. Callers with only a DialogueIntent can
  use this directly.
- chat/runtime.py: add _last_intent and _last_input_text instance fields;
  store intent on every classify_intent_from_input() call (pack-grounded
  path and stub/empty-vault path); add explain_last_turn() -> str method
  that calls explain_from_intent(_last_intent, correction_text=_last_input_text).
- core/cli.py: in cmd_chat REPL loop, handle "/explain" command — calls
  runtime.explain_last_turn() and prints the canonical prompt restatement
  (or a "no prior turn" message to stderr if no turn has run yet).
- tests/test_explain_repl.py: 11 tests pinning explain_from_intent dispatch
  for all intent tags and the ChatRuntime.explain_last_turn() contract.

Per ADR-0017 (Responsive-with-Axiology): introspection is per-turn and
operator-invoked, never autonomous — the /explain command is correct
placement for this feature.
2026-05-25 06:09:49 -07:00
Shay
59e8453973
feat(ADR-0120-math): math-expert promotion composer — technical pass on first eval, awaiting reviewer signature (#194)
Final wire-up after all 10 ADR-0114a obligations + ADR-0131.4
composite gate landed. Composes:
  - all 10 obligation verdicts (5 from new auditor modules,
    5 from inline checks over existing infrastructure)
  - ADR-0131.4 composite math gate verdict
  - ADR-0092 reviewer-signed claim entry from docs/reviewers.yaml

into a single deterministic promotion verdict + canonical
signed/unsigned ``expert_claims_math_v1_signed.json`` artifact.

Empirical verdict on current main (first evaluation):
  all_obligations_passed:      True
  composite_gate_passed:       True
  technical_pass:              True
  claim_digest:                d164866975341d9b82503caf50c0404ee140eab21fd60f589536c6daf6e1d706
  reviewer_signature_present:  False
  promote_admitted:            False
  refusal_reason:              awaiting reviewer signature

Every technical gate passes. The PR ships in the architecturally-
correct "awaiting reviewer signature" state — the reviewer's
signature is the separate, auditable operator action that
consummates the promotion.

Operator workflow (post-merge):
  1. Run `core capability math-expert-promote`, confirm verdict,
     capture claim_digest.
  2. Add entry to docs/reviewers.yaml under math_expert_claims:
       - domain_id: mathematics_logic
         signed_by: shay-j
         claim_digest: "d164866975341d9b82503caf50c0404ee140eab21fd60f589536c6daf6e1d706"
  3. Re-run — promote_admitted flips to True.
  4. Separate ledger-flip PR (out of scope here) consumes the
     signed artifact and writes the capability ledger.

Safety property: if the evidence bundle changes after signing
(B-lane re-run, pack edit, obligation report shift), the digest
changes and the existing signature stops matching. The verdict
reports the mismatch explicitly and the operator must re-inspect
and re-sign — a ledger flip can't survive a silent evidence change.

New files:
  - core/capability/expert_promotion_math.py — the composer
  - tests/test_adr_0120_math_expert_promotion.py — 18 tests
  - docs/decisions/ADR-0120-math-expert-promotion-wireup.md — ADR

Modified:
  - core/cli.py — new `core capability math-expert-promote` cmd
  - docs/reviewers.yaml — added math_expert_claims: [] section
    with documentation comment

Tests: 18/18 covering each inline obligation evaluator
(#1/#3/#4/#7/#9 pass + failure modes), composer integration
against current main, reviewer-signature path (matching → admitted;
mismatched → refused with explicit diagnostic), digest
reproducibility, artifact byte-equality. All pass in 0.49s.

Trust boundary: read-only access to 4 B-lane reports +
GSM8K probe + 5 obligation auditor reports (transitively) +
frontier dir + docs/reviewers.yaml; single deterministic write
to the artifact path; no dynamic imports, no shell, no network.

This is the last PR before the first mathematics_logic -> expert
ledger flip attempt. The actual flip is reserved for a separate
small PR that consumes the signed artifact.
2026-05-23 16:44:56 -07:00
Shay
1babef946e
feat(ADR-0114a.2): OOD-ratio auditor — Obligation #2 wired for B3, ratio=1.00 (#193)
35-case OOD set (ood-001..ood-035): surface-varied siblings of B3's 35
solved_correct public cases.  Entity-name pool: Maya/Liam/Noah/Diana/Felix/
Priya/Omar/Rosa/Jun/Kai.  Unit-noun pool: oranges/marbles/pencils/books/
stamps/coins/balls (all parser-allowed count nouns).  Every case in-grammar
per ADR-0131.3 and parseable without error.

Auditor (core/capability/ood_ratio.py): reads B3 public report.json + OOD
report.json, computes ood_ratio = ood_accuracy / public_accuracy, enforces
two independent gates — ratio ≥ 0.95 and wrong == 0.

CLI: core capability ood-ratio (exit 0 iff both gates pass).

Measured: public 50/50=1.000, OOD 35/35=1.000, ratio=1.000. Obligation #10
and B3 public lane unchanged.
2026-05-23 16:25:28 -07:00
Shay
1f90cb6cf6
feat(ADR-0114a.6): depth-curve auditor — Obligation #6 wired for B3 (assertion holds, coverage gap named) (#190)
Implements the external auditor for ADR-0114a Obligation #6:
"depth_curve.py produces a per-bucket curve;
accuracy(N) >= accuracy(depth_1) * (1 - eps)^(N - 1) for eps = 0.05."

Mirrors PR #189's auditor pattern (re-runs lane via the candidate-
graph pipeline, aggregates over committed cases, emits deterministic
report). Uses len(trace.steps) as the authoritative depth — the
engine's actually-executed reasoning, not the case's declared depth.

New module core/capability/depth_curve.py:
  - Bucket schema mirrors ADR-0119.6: depth_1, depth_2-3,
    depth_4-5, depth_6-8. Depth > 8 raises rather than silently
    extending. Depth == 0 (initial-only problems) skipped — nothing
    to decay.
  - representative_depth = min(bucket) — most permissive bound
    convention; tightening requires an ADR amendment.
  - epsilon = 0.05 pinned per ADR-0120 §Threshold rationale.
  - Two-axis verdict: obligation_6_mechanism_wired (always true if
    auditor ran), obligation_6_assertion_holds (every populated
    bucket satisfies the decay bound), coverage_sufficient (>=2
    buckets populated AND >=3 cases each — required for the
    assertion to be statistically meaningful).

CLI: core capability depth-curve (added to core/cli.py).
Writes evals/obligation_6_depth_curve/<lane_id>.json.

Empirical verdict on current main:
  lane:                B3_bounded_grammar
  cases_total:         50
  cases_solved:        22
  mechanism_wired:     True
  assertion_holds:     True
  coverage_sufficient: False
  populated:           [depth_1 (21/21=1.0000), depth_2-3 (1/1=1.0000)]

Both populated buckets satisfy the decay bound. Coverage gap is
honestly named in the refusal_reason: depth_2-3 has only 1 case,
depth_4-5 and depth_6-8 have none. This is B3-owner work (case
authoring under the existing grammar contract), not auditor work;
reserved as a B3 v1.1 follow-up PR.

Honest scope-limit: B3 only. B1 (algebra, no trace) and B2 (chain
validation, not problem-solving) need different metrics — separate
sub-ADRs.

Trust boundary: read-only access to B3 cases + transitive pack
reads via the pipeline; single deterministic write to artifact path.

Tests: 24/24 covering bucket schema closure (depth 1..8 + raise on
9+), decay bound math (epsilon pinned, formula correct, depth_1 has
no bound), coverage-sufficient policy (thresholds pinned), lane
evaluation (passes on real B3 + refuses on missing cases),
coverage-sufficient distinction (B3 today vs synthetic 5+5 fixture
showing both pass), determinism (report identical + artifact
byte-equal).
2026-05-23 16:19:58 -07:00
Shay
9b45e23973
feat(ADR-0114a.8): adversarial auditor — Obligation #8 wired, PASSING; surfaces 2 known parser-layer gaps (#192)
External auditor for ADR-0114a Obligation #8:
"adversarial/score.py reports wrong == 0 across all families;
>= 30 cases x >= 8 families."

Verdict on current main:
  cases_total:         36
  families_total:      9
  cases_refused:       28
  cases_solved:        8
  cases_wrong:         0  <-- the gate
  obligation_8_passed: True

New module core/capability/adversarial.py mirrors PR #189/#190/#191
auditor pattern. Pure function over the committed cases set; broad
exception capture (correctly classified as refused — engine
couldn't process the input) makes the auditor robust to upstream
typed-refusal gaps.

New dataset evals/obligation_8_adversarial/v1/cases.jsonl — 36
cases x 9 families, closed taxonomy:
  - paraphrase (verb outside initial-anchor whitelist)
  - unrecognized_unit (not in en_units_v1)
  - conditional (if/would/suppose)
  - pronoun_coref (cross-sentence he/she/they)
  - hedged_quantity (about/almost/approximately)
  - ordinal_confusion (the 5th/third in cardinal position)
  - implicit_subject (no named entity)
  - self_reference (actor as comparison ref or transfer target)
  - distractor_noise (adjectival/temporal/irrelevant siblings)

CLI: core capability adversarial. Writes
evals/obligation_8_adversarial/<lane_id>.json. Exit 0 iff
obligation passes.

Honest disclosure — 8 of 36 cases solved rather than refused;
none produced wrong answers. Two parser-layer gaps surfaced:

  Gap A (pronoun_coref, 4/4 solved): unbound sibling sentences
  silently drop; engine returns last-asserted state. Faithful but
  semantically poor. Reserved follow-up: tighten admissibility so
  unbound sentences refuse the whole case.

  Gap B (unrecognized_unit, 4/4 solved): _canonicalize_unit
  falls back to '+s' plural rule when pack doesn't recognize
  the unit. Reserved follow-up: opt-in strict mode behind a flag
  (some B3 units aren't in en_units_v1 either; strict mode
  requires parallel pack extension).

  Bug caught: adv-self-reference-003 ("Sam gives 3 apples to
  Sam.") raises uncaught MathGraphError from
  Operation.__post_init__. Auditor catches it as
  refused-via-exception; ~3-line follow-up in
  _build_op_candidate fixes the parser side.

Trust boundary: read-only access to cases + transitive pack reads;
single deterministic write to artifact path.

Tests: 11/11 in tests/test_adr_0114a_8_adversarial.py covering
threshold pinning (>= 30 cases / >= 8 families), closed taxonomy
(every documented family has cases; no unknown families),
obligation-passes snapshot, per-family wrong=0 invariant, failure
modes (missing file, below-threshold count), determinism (report
identical + artifact byte-equal).
2026-05-23 16:11:37 -07:00
Shay
29111b7762
feat(ADR-0114a.5): reasoning-isolation perturbation suite — Obligation #5 wired for B3, PASSING 130/130 preserving, 68/68 breaking (#191)
Discharges ADR-0114a Obligation #5 for the B3 bounded-grammar lane.

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

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

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

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

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

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

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

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

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

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

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

Tests: 19/19 in tests/test_adr_0114a_10_pack_provenance.py
covering lemma-id parser (well-formed + malformed), lexicon loader
(real pack + every failure mode), lane validator (passes on real
B3 + refuses on missing pack/cases + skips refusal-expected cases
without false violation), determinism (report identical across
calls + artifact byte-equal).
2026-05-23 15:44:53 -07:00
Shay
4b59f3daf7 feat(ADR-0131.4): composite math-expert promotion gate — wired, evaluated, PASSING
Implements ADR-0131's revision of the ADR-0120 expert-promotion
contract for mathematics_logic: replaces the single-benchmark
GSM8K-coverage check with a composite B1+B2+B3 requirement.

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

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

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

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

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

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

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

ADR-0131.4 module note: the parent ADR-0131 plan named
formation/ratify.py + formation/promote.py as the wire-up site —
that was a misidentification (those govern teaching-example
SPECULATIVE→COHERENT bridging per ADR-0021, not domain-tier
promotion). Correct site is core/capability/, where audit-passed
gate already lives.
2026-05-23 15:23:14 -07:00
Shay
04eb5626ea feat(packs): ADR-0127.1+0127.2 — en_units_v1 + loader
Exhaustive units pack: 13 dimensions (7 base + 6 derived), ~150 unit
lemmas, ~25 containers, ~80 conversion edges (within-dimension
exhaustive, NIST/ISO sourced), affine temperature offsets, multi-
word structural rules.

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

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

No parser/solver changes (deferred to 0127.3-0127.7).
2026-05-23 07:04:06 -07:00
Shay
9d19b8176f feat(gsm8k): ADR-0126 P6 — train-sample runner + exit-criterion gate
Wraps existing math pipeline (parser -> solver -> verifier) against
PR #159's 50-case train sample. Emits deterministic report.json with
per-case verdicts. CLI exit code reflects exit criterion
(correct >= 10 AND wrong == 0).

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

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

Depends on PR #159 (gemini/adr-0126-train-sample). Rebase onto main
after #159 lands.
2026-05-23 06:33:06 -07:00
Shay
696f62abdd feat: ADR-0113 rename expert-demoaudit-passed; reserve expert namespace (ADR-0114 GSM8K roadmap)
The word "expert" in the previous status name implied raw-capability parity
with frontier LLMs on the same benchmark — which the gate does NOT verify.
What the gate actually verifies is CORE *claim-shape compliance*:

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

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

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

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

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

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

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

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

What it does

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

Surface

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

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

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 14:59:27 -07:00
Shay
bfb54fb015 feat(demos): implement ADR-0099 — Public Showcase Demo
Single 30-second artifact composing four CORE invariants
(determinism, honest unknown, reviewed learning, multi-hop with
trace) by delegating to existing DemoCommand adapters. **No new
mechanism** — every claim is backed by an already-shipped,
separately-tested adapter. Closes the 8-ADR scale-up slate.

- new core/demos/learning_loop_adapter.py: LearningLoopDemo wraps
  ADR-0056 reviewed-teaching loop; _strip_volatile_paths drops
  transient temp-dir paths from raw before serialization so the
  adapter's report_sha256 is content-stable across runs
- new core/demos/showcase_adapters.py:
  - FabricationControlPublicDemo: re-runs ADR-0096 public split,
    produces 3 claims (refusal_recall_meets_threshold,
    fabrication_rate_below_threshold, trace_evidence_present)
  - MultiHopTraceDemo: runs 'Does light reveal truth?' with
    transitive_surface=True + composed_surface=True against
    cognition pack; surfaces a 3-hop walk light→truth→knowledge→
    evidence; produces 3 claims (grounded_answer, depth_two_or_more,
    walk_evidence_present)
- new core/demos/showcase.py: run_showcase() composes 4 scenes,
  emits showcase.json + per-scene artifacts; render_html() produces
  presentation-only static HTML with no JS injection vector;
  ShowcaseScene dataclass; MAX_RUNTIME_SECONDS=30 hard ceiling
  with DemoContractError if exceeded
- CLI: 'showcase' added to demo target choices; --output-dir flag
  added; cmd_demo dispatch branch writes showcase.json + showcase.html
- new evals/public_demo/ lane with 4 cases:
  - all_claims_supported (each scene + composite)
  - determinism_run_to_run_byte_equality (two runs identical after
    stripping volatile keys: total_runtime_ms, json_path,
    transient_corpus)
  - runtime_under_budget (≤30s)
  - pure_composition_no_new_mechanism (grep gate over showcase
    imports — must come from core/chat/generate/language_packs/
    teaching/evals or allowed stdlib only)
- lane is itself byte-identical across runs (sha256 5707db8efc6a..);
  runtime case omits exact runtime_ms (it varies near bucket
  boundaries) but still asserts ≤ budget
- 8 unit tests with module-scoped fixture (showcase runs once,
  ~13s total) covering payload shape, scene order, runtime budget,
  HTML render absence of <script>, and the pure-composition import
  gate independently of the lane
- ADR-0099 measured: total_runtime_ms ~12.8s, well under 30s budget
- smoke 67/67, cognition eval byte-identical 100/100/100/100;
  all 6 ADR-0092..0099 lanes byte-identical:
    reviewer_registry        681a2aab..
    miner_loop_closure       9f071733..
    domain_contract_validation f9c06cde..
    fabrication_control sum  01e1b6b7..
    demo_composition         27d83824..
    public_demo              5707db8e..
2026-05-21 19:44:48 -07:00
Shay
7784c39f9f feat(capability): implement ADR-0093 — Domain Pack Contract v1 wired in
Promotes ADR-0091 from proposed-but-unenforced to enforced. The CLI
command core capability domain-contract now runs the nine ADR-0091
predicates plus eval-lane artifact resolution; legacy structural-only
output remains available via --structural-only.

- new core/capability/domain_contract_predicates.py:
  evaluate_domain_contract(pack_id, *, data_root, chain_inventory,
  reviewer_registry) → DomainContractPredicateReport
- predicates wired:
  P1 manifest/checksum valid (via language_packs.compiler.load_pack)
  P2 gloss checksum (gloss-bearing packs only; otherwise vacuously pass)
  P3 domain_id ∈ DOMAIN_PACKS
  P4 teaching_chains entries ∈ TEACHING_CORPORA ∪ DOMAIN_CAPABILITY_CORPORA
  P5 ≥ 8 reviewed chains per claimed operator family from chain_report
  P6 ≥ 3 populated intent shapes per domain
  P7 every eval_lanes entry covers dev/public/holdout
  P8 reviewers resolve via ADR-0092 registry (consults can_review with
     scope='pack' and domain_id from contract)
  P9 known_gaps reference docs/gaps.md entries marked closed [x]
- _parse_gap_states reads docs/gaps.md format (- [x] / - [ ]) → {gap_id: closed?}
- _resolve_eval_lane_artifacts walks declared eval_lanes and surfaces
  per-split report path + SHA-256 (ADR-0093 item 4)
- CLI: cmd_capability_domain_contract now exits non-zero on any
  predicate failure; --structural-only preserves legacy behavior
- core.capability package re-exports new symbols (PredicateResult,
  DomainContractPredicateReport, evaluate_domain_contract)
- 24 unit tests covering contract presence/absence, each predicate
  positive + negative, gap parser, eval lane artifact surfacing,
  CLI default + structural-only paths, and determinism
- new evals/domain_contract_validation/ lane: 9 cases (positive +
  one negative per semantic predicate P3-P9 + determinism) passing
  9/9 byte-identical across runs (sha256 f9c06cde…)
- smoke 67/67, teaching 17/17, cognition 120/121 (pre-existing skip),
  ADR-0092..0095 tests 101/101; cognition eval byte-identical
  100/100/100/100
2026-05-21 18:33:23 -07:00
Shay
3d922a1532
Add chain-first capability ledger and domain seeds (#97) 2026-05-20 21:33:24 -07:00
Shay
8f1903e8e7
chore(evals): contracts + bench json + Lane B viewer + chart + audit + demo schema (#62)
* chore(evals, cli): contract standardization + bench --json stdout cleanliness

End-of-session shippability pass.  Three concrete fixes:

1. core/cli.py — bench --json no longer pollutes stdout
   Several bench paths call scripts.run_pulse.run_pulse which prints
   verbose [pulse] traces unconditionally to stdout, breaking jq /
   programmatic consumers of --json output.

   New _bench_stdout_guard() redirects stdout → stderr for the
   duration of the bench run when --json is set.  Operator still sees
   the pulse trace (on stderr), but --json consumers get a clean JSON
   document on stdout.  Applied to all four bench paths: cost,
   articulation, default suite, and --suite all.

   Verified: core bench --suite determinism --json now produces
   parseable JSON; human path still shows 1140 [pulse] lines.

2. evals/{frontier_compare,realizer_guard}/contract.md (new)
   core/contemplation/contract.md (new)

   Each new contract follows the established pattern (37 contracts
   already exist under evals/<lane>/contract.md):

     - What it measures
     - Why it matters (structural win)
     - How to run
     - How to read the output
     - Pass criteria table
     - When it has failed and why
     - Runner / module layout

   Coverage:
     - frontier_compare: both Lane A (CORE-only suites) and Lane B
       (cross-provider prompt_battery) with explicit guardrails
       against mixing — operator asks for the wrong lane combination,
       runner exits 2 with helpful error.
     - realizer_guard: C1/C2 articulation safety boundary — synthetic
       illegal candidates rejected directly by check_surface AND
       former-bug runtime prompts now produce legal articulations.
     - contemplation (ADR-0080): not under evals/ since it's runtime
       infrastructure that consumes eval reports — contract lives at
       core/contemplation/contract.md.  Documents the read-only +
       SPECULATIVE-only + deterministic-replay invariants and the
       shared DiscoveryCandidateSink plumbing convergence (ADR-0080).

3. evals/CLAIMS.md — Tier 2 rows added

   - frontier_compare Lane A: determinism.primary_score, max_versor_condition
   - frontier_compare Lane B: prompt_battery.primary_score (CORE adapter),
     cross-provider artifact persistence
   - realizer_guard: all_claims_supported
   - contemplation: SPECULATIVE-only invariant, deterministic replay,
     additive sink path, no pack mutation (all CI-pinned by tests)

Verification
------------
$ core test --suite smoke -q
67 passed in 27.22s    (no regression)

$ uv run pytest -q tests/test_contemplation_loop.py \
    tests/test_contemplation_pipeline_convergence.py \
    tests/test_frontier_compare_cross_provider.py
27 passed in 4.87s

$ core bench --suite determinism --json 2>/dev/null | jq .results[0].passed
true        (was: JSONDecodeError on prior [pulse] pollution)

* feat(evals/ui): report viewer renders Lane B cross-provider + pass-rate chart

Stop-hook caught that #62 only covered contracts — the 929-line
report_viewer.html was never audited against the new cross-provider
report shape from #61.  Two real gaps:

1. Lane-aware observation drawer
   The drawer hardcoded Lane A (CORE-native) fields: surface,
   grounding_source, anchor_lens_mode_label, versor_condition.
   Lane B (cross-provider) observations carry different fields:
   provider, model, elapsed_ms, error_type, error_message.

   Loading a cross-provider report rendered only the surface row
   with empty `grounding` — the provider + model + timing data
   was unreachable without expanding "Show raw JSON".

   Fix: detect Lane B (presence of `obs.provider`) and render the
   appropriate field set.  Lane A still renders identically (now
   also surfaces trace_hash + register_id when present, which were
   silently buried in the raw JSON before).

2. Pass-rate chart per suite
   The summary strip showed one aggregate Primary % across all
   suites, with no way to see WHICH suite is dragging the score.
   Multi-suite runs (e.g. --suite all) had to expand each panel
   individually to find the failing one.

   Fix: new .passrate-chart element below the summary strip,
   one horizontal bar per suite showing passed/total.  All-pass =
   solid green, all-fail = solid red, partial = green/red split
   at the pass fraction.  CSS only — no new dependencies.

3. SUITE_PREAMBLES gains the prompt_battery entry so the sidebar
   shows the "side-by-side surface evidence across providers"
   description when loading a Lane B report.

Verified
--------
- Brace/paren/div balance unchanged (308/308 / 380/380 / 54/54)
- One <script> tag pair preserved
- Generated a real Lane B report via
  `python -m evals.frontier_compare --provider core --suite prompt_battery`
  for visual confirmation

Out of scope (noted for future PR)
----------------------------------
Sampled 3 `core demo` targets:
- register-tour: clean schema (all_claims_supported, claims, grid)
- audit-tour: both scene_1_* keys AND an empty scenes:[] array — inconsistent
- anti-regression: no all_claims_supported key, uses all_gates_held instead

Demo schema standardization deserves its own PR — operator tooling
would benefit from a uniform top-level success field across demos.

* docs(evals) + chore(demos): systematic audit + uniform success field

Stop-hook caught two real gaps after the contract+UI PR:
- demos had divergent success-field names (all_gates_held vs
  learning_loop_closed vs claim_supported vs nested claims_supported)
- no systematic look at the 48 eval directories had been done

Both addressed concretely; remaining work captured in audit doc
rather than vaguely deferred.

1. Demo schema standardization — uniform all_claims_supported field
----------------------------------------------------------------------
All 9 ``core demo`` targets now emit a top-level
``all_claims_supported: bool`` field.  Existing per-demo fields
(``all_gates_held``, ``learning_loop_closed``, ``claim_supported``,
nested ``claims_supported``) are preserved for backwards compat —
the new field is an alias derived from the demo's existing success
signal, not a replacement.

Operator tooling and the CI gate can now target
``all_claims_supported`` without knowing each demo's idiomatic
field name.

Files touched:
- evals/anti_regression/run_demo.py — adds AND of all_gates_held +
  active_corpus_byte_identical
- evals/learning_loop/run_demo.py — adds AND of learning_loop_closed +
  active_corpus_byte_identical
- scripts/publish_pack_measurements.py — adds AND of the three
  entries in the nested claims_supported dict
- evals/long_context_cost/comparison_runner.py — adds alias for
  claim_supported (singular)

The 5 demos already using ``all_claims_supported`` (audit-tour,
register-tour, anchor-lens-tour, orthogonality-tour, articulation)
are unchanged.

Verified across all 9 demos:
  audit-tour              : True
  register-tour           : True
  anchor-lens-tour        : True
  orthogonality-tour      : True
  pack-measurements       : True   ← new alias
  anti-regression         : True   ← new alias
  learning-loop           : True   ← new alias
  articulation            : True
  long-context-comparison : True   ← new alias

2. docs/EVAL_AUDIT_2026-05-20.md — systematic 48-lane audit
------------------------------------------------------------
Replaces the "future PR" deferral with a concrete document.

Contains:
- Method (what was inspected for each lane).
- Summary (40/48 have contract.md; 18/48 have saved results;
  empty results/ ≠ broken — most lanes regenerate on demand).
- Cross-provider relevance triage:
    * 9 lanes are cross-provider-relevant and could benefit
      from the prompt_battery-style adapter pattern (cognition,
      english_fluency_ood, hebrew_fluency, koine_greek_fluency,
      grammatical_coverage, inference_closure, multi_step_reasoning,
      discourse_paragraph, foundational_*_ood, etc.).
    * 29 lanes are CORE-only by design (versor closure, anchor
      lens, identity divergence, provenance, etc.) — wiring
      providers would be category-erroneous.
- Demo schema standardization status (this PR closes that).
- UI/UX coverage matrix.
- 5 concrete follow-up items, each focused enough for a single
  PR, none requiring architectural change.

Regenerated reports
-------------------
evals/long_context_cost/results/comparison_v1.json and
evals/results/phase2_pack_measurements.json now contain the new
all_claims_supported field (auto-regenerated when validating the
schema change).

evals/frontier_compare/results/sample_core_promptbattery.json
added as a reference Lane B report so the new viewer always has
something to load on first open.
2026-05-20 13:53:13 -07:00
Shay
83c18e4641
fix(cli, tests): wire core contemplation + restore INV-02 allowlist (#60)
Two follow-up fixes from end-of-session verification of recent merges:

1. core/cli.py — wire `core contemplation` subcommand
   PR #55 + #58 added the contemplation CLI at python -m core.contemplation
   but never registered it under the `core` umbrella command, so
   `core --help` didn't show it.  Adds a subparser mirroring the existing
   pattern (chat/test/check/.../doctor) that delegates to the existing
   core.contemplation.__main__:main() — no duplication of arg parsing.

   Surface preserved verbatim: reports (positional, 1+), --lane
   {frontier_compare, contradiction_detection}, --pack-id, --note,
   --report, --sink-root.

2. tests/test_architectural_invariants.py — restore INV-02 allowlist
   PR #57's evals/lab/phi_separation_probe.py imports normalize_to_versor
   for construction-time experimental rotor + embedding work, which
   triggered INV-02's AST-scan failure (the test enforces that
   normalize_to_versor is only called from a small allowed file set).

   evals/lab/ is research-only, never imported by runtime — adding the
   probe to allowed_files doesn't weaken the runtime invariant the
   test enforces.

Verification
------------
$ core test --suite smoke -q
67 passed in 26.63s   (was 66 passed / 1 failed before)

$ core contemplation --help
... shows the new subcommand surface

$ core contemplation evals/contradiction_detection/results/v1_public_*.json \
    --lane contradiction_detection \
    --sink-root /tmp/sink \
    --report /tmp/run.json
... 4 SPECULATIVE findings; sink writes to /tmp/sink/2026/2026-05.jsonl
2026-05-20 13:10:29 -07:00
Shay
0eaba474ed
parallel eval runner (#46) 2026-05-19 23:51:59 -07:00
Shay
4426f387d1 feat(demo): ADR-0074 — orthogonality tour (anchor-lens × register)
A single demo that walks the full 3 × 3 × 2 matrix (register × lens
× prompts, 18 cells) and pins five claims simultaneously, packaging
both single-axis invariants into one composition gate.

The single-axis tours assert opposite invariants:

  register-tour    : per (lens, prompt), trace_hash CONSTANT across
                     registers (R5 / ADR-0072).
  anchor-lens-tour : per (register, prompt), engaged lens diverges
                     in trace_hash from the unanchored baseline
                     (L1.4 / ADR-0073d).

Orthogonality-tour packages both claims simultaneously across the
full matrix, plus three surface-level claims that pin the markers
operators actually see.

Composed claims (all five must hold)

  A) inner_register_invariant_within_lens
     For each (lens, prompt) cell, the three register runs share an
     identical trace_hash.  (R5 register-tour, applied 6 times:
     3 lenses × 2 prompts.)

  B) outer_lens_distinctness_within_register
     For each (register, prompt) cell where any non-unanchored lens
     engages, that engaged lens's trace_hash differs from the
     unanchored baseline at the same (register, prompt).
     (L1.4 anchor-lens-tour, applied 6 times: 3 registers × 2 prompts.)

  C) surface_carries_register_marker_under_convivial
     Every convivial cell with a non-empty surface has a non-empty
     register_variant_id.

  D) surface_carries_lens_annotation_when_engaged
     Every engaged cell carries [lens(<id>):<mode>] in surface AND
     a non-empty anchor_lens_mode_label.

  E) no_substrate_glyph_leak_across_grid
     No cell's surface contains Greek/Hebrew/Syriac/Arabic glyphs.
     (ADR-0073c gate re-asserted across the full matrix.)

CLI wiring

  core demo orthogonality-tour            human-readable grid + claims
  core demo orthogonality-tour --json     structured report

Exit code 0 iff all five claims hold.

Files

  evals/orthogonality_tour/__init__.py             NEW
  evals/orthogonality_tour/run_tour.py             NEW
  core/cli.py                                       EDIT
    - cmd_demo handler wires orthogonality-tour
    - demo choices + EPILOG examples updated
  tests/test_orthogonality_tour_demo.py             NEW (9 tests)
  docs/decisions/ADR-0074-orthogonality-tour.md     NEW

Sanity check baked into tests
  test_engaged_cells_appear_for_both_non_trivial_lenses pins that
  grc_logos_v1 engages on knowledge in all 3 registers (3 cells)
  and he_logos_v1 engages on truth in all 3 registers (3 cells).
  Prevents the lift claims being vacuously satisfied by a future
  engagement regression.

Lane evidence

  - 9 new orthogonality-tour tests pass.
  - core demo register-tour      → all_claims_supported: True
  - core demo anchor-lens-tour   → all_claims_supported: True
  - core demo orthogonality-tour → all_claims_supported: True
  - python -m core.cli eval cognition → byte-identical 100/100/91.7/100.
  - Full lane: 2745 passed / 4 skipped / 1 pre-existing failure
    (+9 over L1.4's 2736; the one failure remains
    test_all_preamble_explains_combined_run, unrelated).

No runtime / composer / loader / pack / schema changes.  Pure demo
consumer of existing telemetry contracts.
2026-05-19 20:33:33 -07:00
Shay
1feec74b1c feat(anchor_lens): ADR-0073d — L1.4 telemetry, CLI flag, tour demo
L1.4 closes the anchor-lens inside-out arc (L1.1→L1.4 mirroring
R1→R5).  Substantive axis is now operator-observable,
operator-driven, and demo-falsifiable — exactly what R5 did for
the register subsystem.

Telemetry extension
  - TurnEvent + ChatResponse gain anchor_lens_id +
    anchor_lens_mode_label (both default "" → pre-L1.4
    byte-identical).
  - serialize_turn_event surfaces both fields in every JSONL line.
  - Mode-label extracted via _ANCHOR_LENS_ANNOTATION_RE from the
    PRE-decoration surface (so register decoration cannot interfere
    with anchor-lens telemetry).  Composer remains the sole source
    of truth for engagement; the runtime helper is read-only.

Operator surface
  - core chat --anchor-lens <id> CLI flag threads into
    RuntimeConfig.anchor_lens_id.
  - Invalid id → AnchorLensError caught at cmd_chat and surfaced
    as _die("invalid --anchor-lens pack id: ...", code=2) before
    the REPL launches.
  - Composes with --register (both flags wire through
    _runtime_config_from_args).

Narrative demo
  - evals/anchor_lens_tour/run_tour.py walks 2 prompts × 3
    ratified lenses ({default_unanchored_v1, grc_logos_v1,
    he_logos_v1}).  Asserts four claims:
      * lens_ids_recorded_per_turn
      * trace_hashes_distinct_across_lenses (OPPOSITE of
        register-tour's identical-hash claim)
      * surface_propositions_distinct_across_lenses
      * no_substrate_glyph_leak (block-scoped Greek/Hebrew/
        Syriac/Arabic; stylistic punct allowed)
  - Exit code 0 iff all four hold.
  - Bundled into `core demo` choices + EPILOG.

Tests (30 new)
  - tests/test_anchor_lens_telemetry.py (16) — TurnEvent shape,
    serializer keys, runtime emits per lens / per engagement
    state, ChatResponse mirrors event, mode-label extractor unit.
  - tests/test_anchor_lens_cli.py (9) — _runtime_config_from_args
    threading, invalid id fail-fast, parser flag wiring, parser
    composes with --register.
  - tests/test_anchor_lens_tour_demo.py (9) — four seam claims
    pinned individually + all_claims_supported + per-cell
    anchor_lens_id + unanchored cells empty mode + engaged cells
    carry mode label.

Lane evidence
  - 30 new L1.4 tests pass.
  - core demo anchor-lens-tour --json → all_claims_supported: True.
  - core demo register-tour --json    → all_claims_supported: True.
    Both tours pass simultaneously — orthogonality CI-pinned.
  - python -m core.cli eval cognition → public 100/100/91.7/100
    byte-identical (lens=None / default_unanchored_v1).
  - Full lane: 2736 passed / 4 skipped / 1 pre-existing failure
    (+30 over L1.3's 2706; the one failure remains
    test_all_preamble_explains_combined_run, unrelated).

Live demo (canonical proof)
  P1: 'What is knowledge?'
    default_unanchored_v1  trace=17c9aabe…  mode=(none)
    grc_logos_v1           trace=0198ad4c…  mode=systematic
    he_logos_v1            trace=17c9aabe…  mode=(none)
  P2: 'What is truth?'
    default_unanchored_v1  trace=2557f3e8…  mode=(none)
    grc_logos_v1           trace=2557f3e8…  mode=(none)
    he_logos_v1            trace=ec8d84aa…  mode=covenant-verity

  Engagement is substrate-scoped: grc never touches truth, he
  never touches knowledge.  Trace hashes diverge exactly where the
  lens engages.

Trust boundaries
  - --anchor-lens flag does not bypass ratification; loader still
    enforces companion mastery report self-seal + ratify-time
    substrate-atom existence check (ADR-0073b/c).
  - Mode-label extraction is read-only regex parse; can't forge
    annotations the composer didn't emit.
  - Telemetry stays redact-safe — both fields are identifiers /
    mode labels, not content.  include_content=False emits them
    unconditionally.
  - No new mutation surface; pack files unchanged.

Closes the anchor-lens inside-out arc
  L1.1  content prerequisite                  ✓ (ADR-0073a)
  L1.2  class + loader + unanchored sentinel  ✓ (ADR-0073b)
  L1.3  first lenses + composer wiring        ✓ (ADR-0073c)
  L1.4  telemetry + CLI + tour demo           ✓ (this commit)

  Mirrors the R1→R5 register cadence exactly.  Both axes are now
  operator-observable, CI-falsifiable, audit-traceable, and
  composable via the orthogonality claim pinned in both tours.
2026-05-19 20:21:41 -07:00
Shay
7f0bad3e20 feat(register): R5 — operator-visible register telemetry + tour demo
ADR-0072 ratified + implemented.  Closes the register subsystem
inside-out arc (R1 ADR-0068 → R5 ADR-0072): the presentation axis is
now operator-visible, CI-falsifiable, and audit-traceable.

Telemetry extension
  - TurnEvent + ChatResponse gain register_id + register_variant_id
    (12-char SHA-256 prefix of selected (opening, closing) pair;
    empty string for UNREGISTERED / no-decoration registers).
  - serialize_turn_event surfaces both fields in every audit JSONL
    line.  Pre-R5 callers stay byte-identical (defaults are "").

Decoration result widened
  - chat/register_variation.py: decorate_surface now returns
    DecorationResult(surface, opening, closing, variant_id).
  - decorate_surface_str alias preserves the pre-R5 string-only API
    for off-runtime callers.
  - chat/runtime.py updated at both call sites (stub + main).

Operator surface
  - core chat --register REGISTER_ID threads into
    RuntimeConfig.register_pack_id via _runtime_config_from_args.
  - Invalid id ⇒ RegisterPackError caught at cmd_chat and surfaced
    as a clean _die(...) before the REPL launches.

Narrative demo
  - evals/register_tour/run_tour.py walks 4 prompts × 3 ratified
    registers ({default_neutral_v1, terse_v1, convivial_v1}) and
    asserts three load-bearing seam claims:
      * all_grounding_sources_identical
      * all_trace_hashes_identical (ADR-0069 invariant C, falsifiable)
      * surfaces_vary_at_least_once (ADR-0071 seeded variation lift)
  - core demo register-tour exit code = 0 iff every claim holds.

Tests
  - tests/test_register_telemetry.py (6) — TurnEvent default,
    serializer keys, runtime emits register_id/variant_id for
    convivial/terse/unregistered, ChatResponse mirrors event fields.
  - tests/test_register_cli.py (7) — _runtime_config_from_args
    threading, invalid-id fail-fast, parser wires --register.
  - tests/test_register_tour_demo.py (7) — three seam claims pinned
    individually + all_claims_supported + per-cell register_id +
    variant_id discipline (empty for neutral/terse, non-empty for
    convivial).
  - tests/test_register_variation.py extended (4 new) — DecorationResult
    shape, decorate_surface_str alias, variant_id stability,
    bijection between non-trivial marker pairs and variant_ids.

Lane evidence
  - Full lane: 2632 passed / 4 skipped / 1 pre-existing failure
    (tests/test_cli_demo.py::test_all_preamble_explains_combined_run,
    unrelated to R5).
  - Cognition eval byte-identical: public 100 / 100 / 91.7 / 100.

Trust boundaries (per CLAUDE.md)
  - --register flag does not bypass ratification; loader validates the
    pack id through _find_pack and the ratify gate at load time.
  - variant_id is content-addressed; no raw markers leak into audit.
  - Telemetry stays redact-safe — register_id and variant_id are
    identifiers, not content, so include_content=False emits them
    unconditionally.
  - No new mutation surface; pack files on disk are not modified.
2026-05-19 19:03:07 -07:00
Shay
ece7e3d2b1 feat(demo): core demo conversation — layperson-facing chat transcript
A live walkthrough that shows CORE actually being used.  Four scenes,
five turns, rendered as a chat transcript ('You: …' / 'CORE: …') with
plain-English captions between turns.

Streamed by default (per-character prompt, per-word response, brief
"thinking" pause) so the layperson sees the answer arriving live.
--no-stream disables delays for CI / tests / fast capture.

Scenes:

  1. Pack lookup        — "What is truth?"
                          Shows deterministic lexicon-grounded answer.

  2. Teaching-chain     — "Walk me through recall."
                          Shows CORE chaining reviewed facts.

  3. Compound prompt    — "What is truth, and why does it matter?"
                          Shows compound decomposition + composition.

  4. Cold turn → learn  — "Why does narrative exist?"
                          Shows CORE refusing to fabricate, an operator
                          teaching it one new chain (real propose →
                          replay-gate → accept), then re-asking the same
                          prompt and getting a grounded answer.

The learning-loop scene reuses the production learning_loop demo so
the underlying machinery is exactly what ships — active corpus is
byte-identical pre/post.

Test gate: tests/test_conversation_demo.py (9 tests — per-scene
grounding source + content checks, learning loop closes,
active-corpus byte-identical, stable JSON shape).

Usage:
  core demo conversation              # live streamed transcript
  core demo conversation --no-stream  # instant rendering
  core demo conversation --json       # structured report (no chat output)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 14:07:48 -07:00
Shay
f2724beb90 feat(cli): core demo all — runs every demo, consolidated PASS/FAIL
Renames the original phase5+phase6 combo to its more honest name
'adr-0024-chain' and repurposes 'all' to mean what users expect: every
demo (eight in total) in one shot.

Demos covered:
  1. phase5                  — stratified mechanism isolation
  2. phase6                  — three-condition head-to-head
  3. audit-tour              — pack-layer story
  4. pack-measurements       — pack-layer claims → numbers
  5. long-context-comparison — exact NIAH vs transformer baselines
  6. anti-regression         — three-gate defense
  7. learning-loop           — cold turn → grounded surface
  8. articulation            — discourse-planner spine

Per-demo runners retain their native preambles + reports.  The
aggregator captures each demo's load-bearing boolean (already pinned
by that demo's test gate) and prints a consolidated PASS/FAIL table.
Exits non-zero if any demo fails.

Under --json, sub-runner stdout is suppressed and a single
consolidated JSON object is emitted with one key per demo plus
'passed' and 'all_demos_passed'.

'core demo adr-0024-chain' preserves the historical phase5+phase6
combined-summary semantics for callers who depended on it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 13:52:00 -07:00
Shay
dc4b565b5a feat(demo): core demo articulation — discourse-planner spine, end-to-end
Four-scene investor/operator-facing walkthrough proving the discourse-
planner spine is load-bearing.  Each scene runs the same prompt under
flag-off (BRIEF baseline) and flag-on (RuntimeConfig.discourse_planner)
and pins a falsifiable lift assertion.

  S1.  EXPLAIN       — Explain truth.
                       Flag-on: pack→teaching upgrade + 2 chain
                                continuation sentences over baseline.
  S2.  COMPOUND      — What is truth, and why does it matter?
                       Flag-on: 9 grounded sentences across two sub-
                                plans; flag-off routes to OOV.
  S3.  WALKTHROUGH   — Walk me through recall.
                       Flag-on emits the CLOSURE chain hop
                                'Recall reveals memory.'; flag-off
                                does not.
  S4.  Determinism   — N=3 reruns × 3 prompts, unique(surface)=1.

Read-only against live packs + active corpus.  Demo is test-gated
(7 tests, all green) and ships a stable JSON contract for downstream
consumers.

Wired into CLI as `core demo articulation [--json]` alongside the
existing trilogy (audit-tour / anti-regression / learning-loop).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 13:41:24 -07:00
Shay
28219c31e2 feat(cli): core bench --suite all — run every benchmark in one shot
Adds an aggregate ``all`` choice to ``core bench --suite`` that
exercises every benchmark CORE ships:

  [1/4] Core six   — determinism / latency / speedup / versor /
                     convergence / realizer  (via run_benchmarks)
  [2/4] Teaching-loop determinism
  [3/4] Articulation suite — breadth / determinism / footprint /
                             cross-topic / discourse-planner /
                             ollama
  [4/4] Cost — measurement bench (no PASS/FAIL by design)

Behavior:

* Each section prints its native report shape (run_benchmarks rows,
  articulation summary, cost summary).  Final consolidated tally
  prints ALL PASSED / FAILURES DETECTED across the three pass/fail
  groups; cost is reported separately as a measurement section so
  it can't false-positive the gate.
* JSON mode emits a single consolidated object with one key per
  section so a downstream report consumer gets every artifact from
  one command.
* psutil is treated as optional: when missing, the articulation
  footprint sub-bench is skipped (new ``skip_footprint`` kwarg on
  ``run_articulation_suite``) instead of aborting the whole run.
  The other three articulation sub-benches all run, so the spine's
  determinism + planner-on capability evidence is preserved.

CLI surface:

  core bench --suite all
  core bench --suite all --runs 50
  core bench --suite all --json --report bench_all.json

Defaults for ``core bench`` (no suite) are untouched — still runs
the six core benches exactly as before.

EPILOG examples updated; ``--suite`` ``choices`` extended with
``"all"``.

Validation:
* core bench --suite all --runs 3: 4 sections run end-to-end;
  consolidated tally reports per-bench PASS/FAIL.  Pre-existing
  backend_speedup FAIL (0.9999x — Rust kernel not built locally)
  surfaces correctly; every other bench PASS including
  articulation_suite_overall.
* core bench --runs 3 (no --suite): unchanged behavior, same six
  benches as before.
* tests/test_articulation_bench.py + test_cli*.py: 25 passed.
* smoke suite 67/67.
2026-05-19 13:08:39 -07:00
Shay
4670e391ec feat(phase5+bench): cross-pack supersede + articulation benchmark suite
Phase 5 (ADR-0067 follow-up):
  teaching/cross_pack_supersede.py — supersede_cross_pack_chain()
  CLI: core teaching supersede ... --cross-pack
    --subject-pack-id ... --object-pack-id ...
  Strict per-chain residency, anti-leakage, byte-identical rollback
  on any post-append re-load failure.  9 new tests.

Articulation benchmark suite (Phase 4 capability proof):
  benchmarks/articulation.py — 5 sub-benches
    [1] breadth        — every intent shape (9 + OOV + cross-pack)
    [2] determinism    — N reruns / unique-surface count
    [3] footprint      — psutil RSS profile across T turns
    [4] cross-topic    — thread context across mixed subjects
    [5] ollama-compare — opt-in side-by-side with local Ollama
  CLI: core bench --suite articulation
    --runs N (det rerun count)
    --turns N (footprint sample window)
    --ollama-model MODEL --ollama-reruns N
  Full operator preamble + JSON report path.
  10 new tests cover the bench shape (psutil import-skipped).

Documentation:
  benchmarks/README.md — full operator manual: catalogue of every
    bench suite, how to read good/neutral/bad results for each sub-
    bench, why CORE vs Ollama comparisons are valid on the
    determinism axis and not on linguistic quality, workflow guide.
  README.md — articulation bench listed in the live-demo grid and
    quick-start examples.

Reference run (llama3:8b, 100 turns, 5 reruns):
  determinism_all_identical=True
  per-turn ΔRSS ≈ 23 KiB
  CORE byte_identical_on_every_prompt=True
  Ollama unique_surfaces≥2 on every prompt

Verification:
  18 new tests pass
  Full lane: 2116 passed, 2 skipped, 0 failed in 2:38
2026-05-18 17:44:59 -07:00
Shay
ea298bdc28 feat(teaching): OOV signal flywheel — sink, aggregator, auto-promotion (Phase 2.3)
Mirrors the chain-gap pipeline (Phase 1.1+1.2) for vocabulary gaps:
the OOV invitation surface (P2.1) now emits structured signals that
operators can aggregate, rank, and auto-promote into reviewed
PackMutationProposal candidates — closing the OOV loop the same way
Phase 1 closed the chain loop.

Three new modules + two new CLI surfaces:

teaching/oov_sink.py.
  OOVCandidate dataclass mirroring teaching.discovery.DiscoveryCandidate.
  OOVBufferSink (in-memory) + OOVMonthlyFileSink (append-only JSONL
  under <root>/<YYYY>/<YYYY-MM>.jsonl — same layout as discovery sink
  so the aggregator reuses the file-walk machinery).
  hash_oov_candidate_id(token, intent, trace_hash) — deterministic
  32-char hex id matching DiscoveryCandidate's replay invariant.
  format_oov_candidate_jsonl — sorted-keys compact JSONL line.

teaching/oov_gaps.py.
  aggregate_oov_gaps(root, since, sample_limit) groups emitted
  candidates by token, tracks intent-shape union (a token asked under
  multiple intents is a stronger curriculum signal), splits
  boundary_clean from boundary_tainted counts, supports --since
  YYYY-MM filtering via the sink's file naming convention.
  Pure reader; never mutates the sink.  Deterministic ordering:
  (count desc, token asc).

teaching/oov_promotion.py.
  promote_oov_gaps(gaps, threshold, include_tainted, suggested_packs)
  lifts threshold-crossing tokens to OOVPromotion records.
  - boundary_clean_count gates promotion by default (tainted-only
    tokens may indicate the prompt hit a safety axis rather than a
    vocab gap).
  - --include-tainted flag for operator override.
  - threshold < 1 raises.
  - queue_id deterministic: ``oov:<token>@<threshold>`` — diffable
    across runs.
  - suggested_packs lists mounted packs but does NOT recommend one
    — domain inference is out of scope (would require a stochastic
    classifier).  Operator picks the destination.

Runtime wiring:
  ChatRuntime.attach_oov_sink(sink) mirrors attach_discovery_sink.
  Runtime emits one OOVCandidate JSONL line per turn whose
  grounding_source == "oov", no-op when no sink is attached.
  Intent classifier is now invoked when EITHER sink is attached
  (was: only discovery sink) — both downstream paths need it.

CLI:
  core teaching oov-gaps [--top N] [--since YYYY-MM] [--root PATH]
                          [--sample-limit N] [--json]
  core teaching oov-queue [--threshold N] [--include-tainted]
                          [--root PATH] [--since YYYY-MM] [--json]

ADR-0065 documents the full design (five-tier honesty gradient,
P2.1-P2.4 architecture).  README.md updated with the ADR-0065
index entry.

Verification:
  tests/test_oov_pipeline.py                      24 passed
  Operator workflow round-trip verified live:
    > rt.attach_oov_sink(sink); rt.chat("What is photosynthesis?")
    → sink receives:
      {"boundary_clean":true,"candidate_id":"f51bf8...",
       "intent":"definition","token":"photosynthesis","trigger":"unresolved_subject",
       "source_turn_trace":"","review_state":"unreviewed"}
    > core teaching oov-gaps --root /tmp/oov_demo
    → ranked table by count, intent-set per token
    > core teaching oov-queue --root /tmp/oov_demo --threshold 2
    → promoted tokens + suggested mounted packs

Full lane: 2005 passed, 2 skipped, 0 failed in 2:34 (xdist).
2026-05-18 16:42:26 -07:00
Shay
34295e55ce perf(test-infra): pytest-xdist + module-scoped demo fixtures
Full lane wall-time: 6:35 → 2:25 (2.7× speedup).  No behavioral
changes; same 1933 passed, 2 skipped.

Three wins, biggest first:

1. pytest-xdist as a project dependency.

   ``pyproject.toml`` gains ``pytest-xdist>=3.6``.  ``cmd_test``
   injects ``-n auto`` for ``--suite full`` when xdist is importable;
   curated suites stay single-process because worker-spawn overhead
   is net-negative on the smaller suites.  Operator can override
   via passing ``-n <N>`` or ``--dist`` explicitly.

   Verified: ``core test --suite full -q`` prints ``bringing up
   nodes...`` and parallelises across the runner's CPUs.

2. Module-scoped fixture for run_demo() in test_learning_loop_demo.py.

   The 7 demo tests each previously called ``run_demo(emit_json=True)``
   from scratch — and ``run_demo`` itself runs the cognition lane
   twice via the replay-equivalence gate.  ~15s/file → ~3s/file.

   Module scope (not session) is intentional: pytest-xdist
   distributes by test, so a session-scoped fixture would still be
   re-evaluated per worker that picks up a test from this file.
   Module scope keeps the cost paid once per worker per file, which
   is the actual lower bound.

3. Module-scoped fixture for the teaching-loop bench.

   ``test_teaching_loop_bench.py``'s 5 tests previously each ran
   ``run_teaching_loop_determinism(runs=2 or 3)`` — 12 pipeline
   invocations across the file.  One ``runs=3`` invocation shared
   across all 5 tests covers every assertion: ~25s → ~7s.

For local iteration, ``core test --suite cognition -q`` etc. remain
fast (no xdist overhead).  The full-lane speedup is most visible
under CI / pre-merge runs.
2026-05-18 16:12:27 -07:00
Shay
84e74eede8 feat(teaching): discovery gaps aggregator + auto-promotion queue (Phase 1.1+1.2)
Closes the corpus flywheel.  ADR-0055 Phase B emits DiscoveryCandidate
JSONL to the discovery sink, but until now there was no operator-facing
view: candidates accumulated to disk, no one grepped them, the system's
"I would have grounded this if I had a chain" signal went into a void.

P1.1 — Discovery aggregator (teaching/gaps.py).

  Pure reader over the discovery-sink monthly-rollover layout
  (<root>/<YYYY>/<YYYY-MM>.jsonl).  aggregate_gaps(root, since,
  sample_limit) groups emitted candidates by (subject, intent) cell
  and returns a deterministic ranked tuple of Gap records.

  - count: total emissions
  - boundary_clean_count: subset whose boundary_clean flag held
    (refusal/hedge-tainted emissions split out so operators can filter)
  - sample_candidate_ids: up to N retained ids per cell, sorted
  - months_seen: every month token where the cell appeared

  --since YYYY-MM filters by file naming convention (no timestamp
  dependency).  Malformed lines silently skipped.  Default root:
  teaching/discovery_log.

  CLI: core teaching gaps [--root PATH] [--since YYYY-MM] [--top N]
                          [--sample-limit N] [--json]

P1.2 — Auto-promotion queue (teaching/promotion.py).

  promote_gaps(gaps, threshold, include_tainted) lifts cells whose
  effective count meets the threshold into GapPromotion records.

  - Default mode: boundary_clean_count gates promotion.  Tainted-only
    cells (count > 0 but all emissions refusal/hedge-tainted) do not
    auto-promote — those may indicate the prompt hit a safety axis,
    not a curriculum gap.
  - include_tainted=True counts every emission (operator override).
  - Threshold must be >= 1 (zero threshold defeats the queue).
  - queue_id is stable + deterministic (gap:<intent>:<subject>@<N>).
  - No content synthesis — promotion never invents connective or
    object; only an operator can author a complete chain via the
    propose/replay/accept pipeline.

  CLI: core teaching queue [--threshold N] [--include-tainted]
                           [--root PATH] [--since YYYY-MM] [--json]

Operator workflow (closed loop):

  operator → core chat                            # asks question
           ← cold turn emits DiscoveryCandidate
  operator → core teaching gaps --top 10          # ranked gaps
  operator → core teaching queue --threshold 3    # auto-promoted
  operator → authors candidate JSONL
  operator → core teaching propose <path>         # replay gate runs
  operator → core teaching review <id> --accept   # corpus mutates

24 new tests (13 gaps + 11 promotion), all pure / no I/O dependencies,
fast (<1s combined).  Full lane: 1933 passed, 2 skipped.
2026-05-18 16:04:39 -07:00
Shay
7c80b791ec fix(tests): retire 13 stale failures from full lane — corpus saturation drift
The full lane carried 13 long-standing red tests whose premises were
invalidated by reviewed-corpus growth that landed in earlier commits.
None reflected runtime bugs — all four classes are corpus-state drift
where the test fixture became stale.  Curated lanes were green, full
lane stayed quietly red.  Closes that gap.

1. test_teaching_audit (2 tests).
   * test_audit_real_corpus_runs_clean asserted dropped == () and
     lines_on_disk == lines_loaded — premise written before any
     supersession existed.  Curriculum saturation v2 (commit a0edbb4)
     ratified the wisdom_grounds_judgment → wisdom_requires_knowledge
     supersession; the audit now correctly shows 1 dropped line.
     Rewritten as the line-conservation invariant:
       lines_loaded + len(dropped) == lines_on_disk
     plus a typed-reason check on every dropped entry.
   * test_default_superseded_by_is_null_in_loaded_entries asserted
     ALL loaded entries have superseded_by == None.  Wrong even by
     ADR-0055 design: the replacement entry IS loaded and carries
     the back-pointer to the retired chain.  Rewritten as the
     active-set invariant: any non-null superseded_by on a loaded
     entry must reference a dropped (retired) chain id, never a live
     one — no double-live state.

2. test_learning_loop_demo (7 tests).
   The demo's headline prompt was "Why does thought exist?", and the
   ADR-0057 demo trilogy (commit 82dac4b) chose (thought, cause) as
   the cold cell.  Cognition saturation v2 (commit a0edbb4) ratified
   cause_thought_reveals_meaning into the active corpus — so the
   cold turn now grounds, no discovery candidate is emitted, every
   demo scene breaks.  Rotated the cold subject to ``narrative``
   (pack-resident, no chain, same thematic shape, same affirming
   evidence pointer cause_creation_reveals_meaning).  Demo headline,
   evals/learning_loop/run_demo.py, core/cli.py preamble, and the
   test assertions all updated together so the demo reads cleanly:
       before: [none]     I don't know — insufficient grounding...
       after : [teaching] narrative — teaching-grounded ... narrative
                          reveals meaning ...

3. test_discovery_candidates (4 tests).
   Test fixture used (judgment, CAUSE) as the still-cold pair.
   Epistemology v1 (commit 2acf71f) ratified
   cause_judgment_requires_wisdom — (judgment, cause) is no longer
   cold.  Rotated to ``principle`` (pack-resident, no chain on either
   intent today).  Added a pytest.skip self-guard so when a future
   curriculum unit ratifies a (principle, *) chain the test rotates
   cleanly instead of going red.

Full lane: 1892 passed, 2 skipped, 0 failed (was 4 failed pre-fix,
13 failed pre-ADR-0063).  Cognition eval unchanged: public 100/100/
91.7/100, holdout 100/100/83.3/100.
2026-05-18 15:23:22 -07:00
Shay
d24e98906e docs(adr-0055-0057): preambles + README index for the demo trilogy
Three external-facing demos / benchmarks now match the existing
audit-tour / pack-measurements / long-context-comparison treatment:
preamble printed before the run, README index entries, claims table.

- core/cli.py — _ANTI_REGRESSION_PREAMBLE, _LEARNING_LOOP_PREAMBLE,
  _TEACHING_LOOP_BENCH_PREAMBLE.  Each lists reference ADRs, what to
  expect, trust boundary, test gate, and machine-readable invocation.
  Wired through _print_preamble in the demo dispatch + bench dispatch
  (suppressed under --json).
- README.md — new "Inter-Session Memory — Reviewed Learning" section
  between Teaching Order and Architecture: the three-gate trust
  property table, the three live-demo table, and the operator-surface
  command list.  Quick-start block lists `core demo anti-regression`,
  `core demo learning-loop`, and `core bench --suite teaching-loop
  --runs 100` alongside the existing demos.

No code paths changed — preambles are stdout-only when not under JSON.
Tests unchanged; 17/17 green (5 anti-regression + 7 learning-loop + 5 bench).
2026-05-18 11:08:55 -07:00
Shay
82dac4b16f feat(adr-0055-0057): teaching-loop determinism benchmark — replayable learning
`core bench --suite teaching-loop [--runs N]` runs the full reviewed-
corpus extension pipeline (propose → real replay-equivalence gate →
operator accept) N times against an identical input and asserts
byte-identical artifacts every run:

  - proposal_id          (SHA-256 of canonical-JSON payload)
  - replay_baseline      (cognition lane metrics on active corpus)
  - replay_candidate     (cognition lane metrics on transient corpus)
  - regressed_metrics    (sorted tuple)
  - chain_id_written

Also reports per-iteration latency (mean / p50 / p95) and total wall.

100-run result against today's main:
  unique(proposal_id)=1  unique(baseline)=1  unique(candidate)=1
  unique(chain_id)=1     active_corpus_byte_eq=True
  mean=1.849s  p50=1.838s  p95=1.851s

The full learning loop is replayable bit-identically across N
independent invocations.  Pairs naturally with ADR-0045's 100% exact-
NIAH recall numbers — same epistemic class of guarantee, applied to
the *learning loop* itself rather than only to retrieval.  No LLM
provider can publish equivalent numbers on a learning path.

- benchmarks/teaching_loop.py — `run_teaching_loop_determinism(runs)`
  returns a typed `TeachingLoopBenchReport` with uniqueness counts,
  determinism flag, byte-identical-active-corpus flag, and latency
  distribution (mean / p50 / p95 / total).  Pure-stdlib percentile —
  no numpy dep on this path.
- benchmarks/run_benchmarks.py — `bench_teaching_loop_determinism`
  shim + `_SUITES["teaching-loop"]` registration + runs= passthrough.
- core/cli.py — `--suite teaching-loop` choice added to bench parser.
- tests/test_teaching_loop_bench.py — 5 tests pin determinism at
  small N, proposal_id SHA-256 shape, canonical chain_id layout,
  latency stats well-formedness, JSON serialisation.

Trust boundary: every write is confined to a tempdir created inside
the bench loop; the active corpus is read once at start, once at end,
and any byte difference would fail the bench.
2026-05-18 11:03:48 -07:00
Shay
a71b321a9a feat(adr-0055-0057): learning-loop demo — cold turn to grounded surface, end-to-end
`core demo learning-loop` (+ `--json`) walks a single prompt through the
full ADR-0055..0057 inter-session-memory architecture:

  S1. Cold turn          → universal disclosure, grounding_source=none
  S2. Discovery emission → DiscoveryCandidate to attached sink
  S3. Operator proposal  → real replay-equivalence gate, no regression
  S4. Operator accept    → TRANSIENT corpus only; active untouched
  S5. Same prompt        → teaching-grounded surface with the new chain

Before / after on the deterministic prompt "Why does thought exist?":

  before: [none]     I don't know — insufficient grounding for that yet.
  after:  [teaching] thought — teaching-grounded (cognition_chains_v1):
          cognition.thought; logos.internal. thought reveals meaning
          (cognition.meaning). No session evidence yet.

The active corpus on disk is byte-identical pre/post.  The demo writes
only to a transient corpus, then swaps `_CORPUS_PATH` for the after
turn — the same pattern the replay-equivalence gate uses.

- evals/learning_loop/run_demo.py — `run_demo(emit_json=False)` returns
  a structured `DemoReport` with both surfaces and per-scene detail.
- core/cli.py — `core demo learning-loop` target wired.
- tests/test_learning_loop_demo.py — 7 tests pin: full loop closes,
  before is ungrounded, after contains new chain atoms (thought /
  reveal / meaning), discovery emits ≥1, replay gate reports no
  regression, S4 byte-identical active + 1 line on transient, same
  prompt drives both surfaces.

Lane state: learning-loop-demo 7 new — green.  Demo runs in ~15s
end-to-end (cognition lane runs twice via replay gate).

No LLM provider has a published equivalent of this loop: per-fact
provenance from operator accept to surface, replay-equivalence gate
proving non-regression, byte-identical active state regardless of
outcome, full audit trail back to the originating cold turn.
2026-05-18 10:57:41 -07:00
Shay
6f4b2b7b2c feat(adr-0057): anti-regression demo — three-gate defense against learning harm
`core demo anti-regression` (+ `--json`) is a self-contained walkthrough of
the three independent gates that every reviewed-corpus extension must pass.
Designed for showcasing CORE's epistemic discipline to reviewers / industry
observers — no LLM provider has a published equivalent.

Scenes:
- S1. Eligibility predicate refuses an undetermined-polarity candidate
  before any replay is invoked.  ProposalError raised; no log row.
- S2. Replay-equivalence gate auto-rejects a regressing candidate with
  the named regressed metrics in the operator note.  Uses the documented
  `run_replay=` kwarg of `propose_from_candidate` to inject a controlled
  regression of the same `ReplayEvidence` shape the real gate produces.
- S3. Real `teaching.replay.run_replay_equivalence` runs the cognition
  public lane.  A replay-equivalent candidate reaches 'pending' — operator
  `--accept` is still required to write.

Each scene asserts the active corpus is byte-identical pre/post.

- evals/anti_regression/run_demo.py — `run_demo(emit_json=False)` returns
  a structured `DemoReport`; verbose human output by default, JSON on flag.
- core/cli.py — `core demo anti-regression` target wired alongside
  audit-tour / pack-measurements / long-context-comparison.
- tests/test_anti_regression_demo.py — 5 tests pin each scene's
  load-bearing claim + the corpus-byte-identical invariant.

Lane state: anti-regression-demo 5 new — green.  Demo runs in ~10s end-to-end.
2026-05-18 10:52:23 -07:00
Shay
3cad6686cc feat(adr-0057): operator supersession history view — closes the supersede loop
`core teaching supersessions` (+ `--json`) pairs each retired chain with its
active replacement.  Derived view over `audit_corpus()`; pure, read-only.

- teaching/audit.py — `SupersessionRecord` + `supersession_history(report)`
  returns retired→replacement pairs ordered by retired-line (disk order,
  oldest first).  Orphan supersessions (retired with no live entry carrying
  the matching `superseded_by` — e.g. chained retirements where the middle
  link itself was retired) surface as `replacement=None` so silent corpus
  drift is inspectable.
- core/cli.py — `core teaching supersessions [--json]`.  Exit 1 if any
  orphan is detected (catches silent drift in CI); 0 otherwise.
- tests/test_supersession_history.py — 7 tests pin empty-history,
  single-pair shape, chained-supersession surfaces both pairs, line-no
  ordering, orphan detection, JSON round-trip, no corpus mutation.

Lane state: smoke 67 / cognition 121 / supersession-history 7 new / supersede 13 /
audit 23 — green.  `core eval cognition`: unchanged (intent 100% / surface 100% /
term 91.7% / versor 100%).  Real corpus today reports `(no supersessions)`.
2026-05-18 10:40:38 -07:00
Shay
8d2c84a041 feat(adr-0057): operator supersede CLI — retire active chain by appended replacement
`core teaching supersede <old_chain_id> --subject ... --intent ... --connective ...
--object ... --review-date YYYY-MM-DD` is the second corpus mutation surface
(alongside accept_proposal). No replay gate — it's a deliberate operator action
that replaces a hand-authored or previously discovery-promoted chain.

- teaching/supersede.py — `supersede_chain()` orchestrator with pre-checks
  (review_date format, intent whitelist, pack-consistency via re-audit,
  no double-supersede, no self-supersede, no new-chain-id collision) and
  byte-identical rollback on post-audit failure.
- teaching/proposals.py — extended `append_chain_to_corpus` with optional
  `superseded_by` kwarg; remains the only function in the codebase that
  writes to the active teaching corpus.
- core/cli.py — `core teaching supersede` subcommand wired to the live
  `_CORPUS_PATH`; EPILOG updated with example.
- tests/test_supersede.py — 13 tests pin every gate, byte-identical
  rollback on rejection, append-only at disk level, audit-and-runtime
  parity after supersession, hand_authored provenance with
  `supersede(<old_chain_id>)` tag.

Lane state: smoke 67 / cognition 121 / teaching 17 / supersede 13 / audit 23 /
proposals 16 / contemplation 16 / contemplation-wiring 6 / discovery 24 — green.
`core eval cognition`: intent 100% / surface 100% / term 91.7% / versor 100% — unchanged.
2026-05-18 10:35:49 -07:00
Shay
e03ab4b609 feat(adr-0057): Phase C2 — TeachingChainProposal + replay gate + review CLI
The only path by which CORE extends its own active teaching corpus.
Closes ADR-0055 Phase C alongside ADR-0056's cognitive surface.

Three load-bearing calls (recorded in ADR-0057):
  1. Replay-equivalence is a precondition, not a permission;
     operator --accept remains required.
  2. Eligibility = polarity in {affirms, falsifies} AND at least
     one source='corpus' evidence pointer AND boundary_clean AND
     claim_domain != evaluative (unless --allow-evaluative) AND
     proposed_chain complete.
  3. Append-only proposal log; corpus history append-only too.

Changes
- teaching/proposals.py — TeachingChainProposal, ReplayEvidence,
  ProposalLog (event-sourced replay → current_state), eligibility
  predicate, propose_from_candidate, accept/reject/withdraw,
  append_chain_to_corpus (the sole corpus-write surface).  Uses
  TYPE_CHECKING guards to break the circular import with
  chat.pack_grounding.
- teaching/replay.py — run_replay_equivalence; swaps _corpus_index
  path to a tmp file, runs cognition lane on the active corpus
  AND a transient copy with the proposed chain appended, returns
  regressed-metrics list; trust-boundary assertion that the active
  corpus bytes are byte-identical pre/post.
- teaching/discovery.py — moved chat.pack_grounding /
  chat.teaching_grounding imports inside extract_discovery_candidates
  to break the cycle (was masked when chat.runtime was the entry
  point; surfaced by CLI entry).
- core/cli.py — three new subcommands:
    core teaching propose <candidate-jsonl-path> [--allow-evaluative]
    core teaching proposals [--state pending|accepted|rejected|withdrawn] [--json]
    core teaching review <proposal_id> --accept --review-date YYYY-MM-DD
    core teaching review <proposal_id> --reject [--note ...]
    core teaching review <proposal_id> --withdraw [--note ...]
- tests/test_teaching_proposals.py — 16 tests covering: every
  eligibility gate, proposal_id idempotency, append-only log,
  replay-equivalent stays pending, regression auto-rejects with
  named regressed metrics, --accept appends one line with typed
  Provenance, --accept refused on non-equivalent, state-machine
  blocks double-accept, real replay gate runs cognition lane
  twice and asserts byte-clean active corpus pre/post.

Invariants preserved
- versor_condition(F) < 1e-6 — C2 touches no algebra path.
- Active corpus bytes byte-identical regardless of replay outcome.
- No clock-time reads, no LLM, no async.
- Proposal-only — accept_proposal is the sole corpus-write path.

Lanes: smoke 67 / cognition 121 / runtime 19 / teaching 17 /
new proposals 16.  Cognition eval unchanged.

Open follow-ups (not in scope):
- supersession via operator review action
- cross-pack falsification arbitration (ADR-0056 Call 2 deferred)
- pack-data migration of frame-dependent connectives

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:23:14 -07:00
Shay
7aa77806f9 feat(adr-0055): Phase A — teaching corpus audit, supersession, typed provenance
Lands the three load-bearing pieces of ADR-0055 Phase A so later
phases (DiscoveryCandidate, TeachingChainProposal) have a safe
substrate to write into.

- teaching/audit.py: pure, deterministic re-parse of the reviewed
  corpus with same gates as the runtime loader but keeps drop
  reasons (invalid_json, missing_required_field:*, unsupported_intent,
  pack_missing_subject, pack_missing_object, superseded_by:*).
- teaching/provenance.py: typed Provenance(adr_id, source,
  review_date, raw); legacy "reviewed" maps to "hand_authored" so
  current corpus reports the canonical enum without a file rewrite.
- chat/teaching_grounding._corpus_index honors superseded_by —
  active view drops superseded entries while disk preserves history.
- core teaching audit CLI subcommand (--json optional); exits 1 on
  any drop so CI catches silent corpus shrinkage from pack swaps.

Observable behaviour unchanged: corpus is 10/10 loaded, all five
core lanes green (smoke 67, cognition 121, runtime 19, teaching 17,
packs 6), cognition eval metrics identical on dev / public /
holdout splits. versor_condition < 1e-6 invariant untouched.

Tests: tests/test_teaching_audit.py — 23 tests covering provenance
parser, real-corpus determinism, every drop-reason path,
supersession semantics, runtime/audit parity, read-only contract.
2026-05-18 08:15:23 -07:00
Shay
6b25069da8 feat(adr-0054): vault recall indexing/batching + holdout split wired
Two doctrine-aligned CLAUDE.md items closed together.

Part 1 — vault indexing + batching (item #4):
- VaultStore lazy _matrix_cache (invalidated on store / reproject /
  eviction); vault_recall(prebuilt_matrix=...) skips deque→ndarray
  rebuild on hot path
- New vault_recall_batch + VaultStore.recall_batch — B queries
  scored in one component-serial sweep, bit-identical to per-query
  vault_recall (3 seeds × 7 queries × N=137 parity test)
- No approximation, no hot-path repair, scoring arithmetic
  unchanged

Part 2 — holdout split wired:
- LaneInfo.holdout_cases_path resolves plaintext holdouts in fixed
  priority; sealed (.age) holdouts stay in holdout_runner
- framework.run_lane(split="holdout") + argparse --split choices
- First official cognition holdout numbers: 19 cases, intent 100%,
  surface 94.7%, term_capture 70.8%, versor 100% — single miss is
  predicted correction_truth_040 (ADR-0053 scope-limit)

Tests: 21 new vault tests + 10 new framework tests. Lanes: smoke
67, cognition 121, runtime 19, teaching 17, packs 6, algebra 132 —
all green. versor_condition < 1e-6 invariant preserved.
2026-05-18 07:58:57 -07:00
Shay
283680f110 feat(adr-0044, adr-0045): domain ethics pack + long-context comparison
ADR-0044 — Medical / clinical ethics pack (worked-example domain pack).
Ships packs/ethics/medical_clinical_ethics_v1.json with six commitments
partitioned across all three remediation tiers:
  - refuse: no_dosing_recommendation, no_emergency_triage_authority
  - hedge:  defer_diagnosis_to_clinician, surface_evidence_grade
  - audit:  disclose_no_clinician_relationship, respect_patient_autonomy

Ratified end-to-end through scripts/ratify_ethics_pack.py (PACK_IDS
extended).  Production-mode load via load_ethics_pack succeeds.
ChatRuntime composition includes universal safety floor + every medical
commitment.  tests/test_medical_clinical_ethics_pack.py (8 tests) gates
file existence, sealed report, disjoint refusal/hedge lists, and
pack-swap visibility (default pack does NOT carry medical commitments).

ADR-0045 — Long-context recall: CORE vs transformer baselines.
Adds evals/long_context_cost/comparison_runner.py with a deterministic
needle-in-a-haystack measurement at N ∈ {100, 1_000, 10_000, 100_000}.
CORE recall = 100% at every tested N by exact cga_inner scan.

Paired with frozen citations of published transformer NIAH numbers in
evals/long_context_cost/baselines/transformer_long_context.json:
Claude 2.1 (200k, 50%), GPT-4 Turbo 128k (~71%), Gemini 1.5 Pro (99.7%),
NVIDIA RULER (varies).  Each citation carries source + url.

The two components measure different inputs (synthetic versors vs NL
needles) and are not directly comparable benchmark-for-benchmark.  The
comparison is at the architectural level — exact-scan recall vs
attention-based probabilistic recall.  Scope and limits documented in
the ADR.  tests/test_long_context_comparison.py (5 tests) gates schema,
CORE recall == 100%, and baseline citation presence.

CLI integration: two new demo targets with study-grade preambles.
  - core demo pack-measurements          (ADR-0043 — wired)
  - core demo long-context-comparison    (ADR-0045)
README + docs/PROGRESS.md cheatsheets updated.  docs/decisions/README.md
index extended with ADR-0044 + ADR-0045; pack-layer chain title now
"ADR-0027 through ADR-0045".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:31:47 -07:00
Shay
294cfc3576 feat(adr-0042): audit-tour demo — pack-layer story in four scenes
Ships `core demo audit-tour` as the first investor-facing
walkthrough of the ADR-0027→0041 pack-layer architecture.  Four
scenes, each making one falsifiable claim no transformer-LLM
wrapper can reproduce:

  S1. Identity is geometric, not prompt-veneer.
      Three identity packs load three structurally distinct
      manifolds (ADR-0027).  Distinct alignment thresholds +
      distinct hedge phrases from JSON pack files, not prompts.

  S2. Safety is the universal floor.
      Runtime-checkable safety violation produces a deterministic
      typed refusal string (ADR-0036).  walk_surface preserved
      for audit.  Byte-identical across runs.

  S3. Ethics commitments choose their remediation.
      Per-commitment opt-in (ADR-0037 / ADR-0038): pure-helper
      evidence (should_inject_hedge + inject_hedge worked
      example) against a synthetic violation.  Default pack
      returns False; deployment pack (with acknowledge_uncertainty
      in hedge_commitments) returns True.  Pack JSON drives the
      policy tier.

  S4. Deterministic replay across runtime instances.
      Two fresh ChatRuntime instances, same input, same packs.
      Byte-identical JSONL audit lines (ADR-0040).

Load-bearing evidence over surface inspection: the draft compared
response.surface across packs.  Cold-start hits stub path; pack
differences don't manifest at the surface by design.  Shipped
version pulls evidence from structural surfaces (manifold fields,
opt-in lists, pure helpers) — what actually distinguishes the
packs.  No fake claims.

Scene 3 uses synthetic verdict (not chat()) because ADR-0038
specifies stub path skips hedge by design.  Main-path end-to-end
is asserted in tests/test_hedge_injection.py and referenced in
the tour's evidence comment.

Test gate: tests/test_audit_tour.py asserts
result["all_claims_supported"] is True.  Any scene flipping to
False fails the test and catches the regression.

CLI integration:
  core demo audit-tour          # narration to stdout
  core demo audit-tour --json   # structured report, no narration

Files:
- evals/audit_tour/__init__.py + run_tour.py (new) — 4-scene tour
- core/cli.py — audit-tour target on demo subcommand;
  _AUDIT_TOUR_PREAMBLE; --json suppresses narration
- tests/test_audit_tour.py (new) — 8 tests gating all four claims
- docs/decisions/ADR-0042-audit-tour-demo.md (new) — decision record
- docs/decisions/README.md — ADR index now lists ADR-0027..0042
  + Pack-Layer chain section describing the three-tier composition,
  remediation tiers, and verification surface
- docs/PROGRESS.md — adds core demo audit-tour to verify cheatsheet
- README.md — adds core demo audit-tour to commands cheatsheet

Verification:
- Combined pack-layer + telemetry + tour suite: 220 green
  (was 212 after ADR-0041; +8)
- CLI suites unchanged: smoke 67, runtime 19, cognition 121
- core eval cognition: intent 100%, versor_closure 100% (baseline)
- Manual: core demo audit-tour and --json both correct;
  all_claims_supported = true
2026-05-17 22:06:45 -07:00
Shay
417f71917c feat(adr-0041): core chat --show-verdicts + FanOutSink
Two thin layers closing the audit story end-to-end:

- core chat --show-verdicts prints format_verdict_summary(verdicts)
  to stderr after each turn.  Stdout stays clean for piped
  consumers.  Format is dense and terse; designed to skim, not
  machine-parseable (the JSONL sink owns that contract).

- FanOutSink forwards every emitted line to N sinks in declaration
  order.  Fail-fast on first error — consistent with ADR-0040's
  single-sink contract (audit failures surface).  Composes with
  any combination of JsonlFileSink / JsonlBufferSink / future
  sinks.

Two formatters, one bundle: format_turn_event_jsonl (machine,
ADR-0040) and format_verdict_summary (operator, ADR-0041) both
consume the same TurnVerdicts.  No risk of drift.

Summary format:
  [identity=0.83 safety=ok ethics=VIOLATED:foo refusal=- hedge=YES]

Audit story now reads end-to-end:
  - TurnVerdicts bundle (ADR-0039)
  - Machine JSONL sink (ADR-0040)
  - Fan-out + operator CLI (ADR-0041)

Files:
- chat/telemetry.py — FanOutSink dataclass, format_verdict_summary,
  _format_verdict_short helper
- core/cli.py — --show-verdicts on chat subparser; cmd_chat prints
  summary to stderr when set
- tests/test_telemetry_fanout_and_summary.py (new) — 13 tests
- docs/decisions/ADR-0041-cli-verdicts-and-fanout.md (new)

Verification:
- Combined pack-layer + telemetry suite: 212 green (was 199; +13)
- CLI suites unchanged: smoke 67, runtime 19, cognition 121
- core eval cognition: intent 100%, versor_closure 100% (baseline)
- Manual smoke: echo "light is" | core chat --show-verdicts prints
  expected bracketed audit line to stderr alongside response.
2026-05-17 21:47:47 -07:00
Shay
7c839d2e12 feat(cli): core chat --list-identity-packs + companion-file filter
Adds the discovery flag callers have been asking for since ADR-0027.
Short-circuits before the REPL launches; supports both a human-readable
table and `--json` machine output.  Drives the loader's existing
`available_packs()` helper.

Bug fix on the way: `available_packs()` was globbing every `*.json`
in the search path, so the Phase-5 companion `<pack_id>.mastery_report.json`
files were leaking into the list as fake packs with empty fields.  The
helper now skips any file ending in `.mastery_report.json` and rejects
JSON that lacks the required `schema_version` / `value_axes` fields.

CLI output:

  pack_id              version  ratified  description
  -------------------  -------  --------  -----------
  default_general_v1   1.0.0    yes       Balanced general identity...
  generosity_first_v1  1.0.0    yes       Generosity-first specialization...
  precision_first_v1   1.0.0    yes       Precision-first specialization...

Tests: +3 (CLI table, CLI JSON, companion-file filter regression).
test_identity_packs.py: 23 -> 26.  cognition / smoke green.

Docs: docs/identity_packs.md CLI usage block updated; memory
'identity-packs.md' closes that follow-up.
2026-05-17 19:47:13 -07:00