Commit graph

265 commits

Author SHA1 Message Date
Shay
f7680e96ea
feat(teaching): ADR-0104 — curriculum-sourced teaching proposals (#107)
* feat(teaching): add curriculum-sourced proposal builder

* test(teaching): cover curriculum proposal construction

* test(evals): add curriculum loop closure contract

* test(evals): add curriculum loop closure runner

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

* ci(lanes): pin curriculum loop closure lane

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

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

* docs(teaching): mark curriculum source activation

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

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

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

* feat(packs): attach fluency lanes to he_core_cognition_v1

* feat(packs): attach fluency lanes to he_logos_micro_v1

* feat(packs): attach fluency lanes to grc_logos_cognition_v1

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

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

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

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

* docs(evals): note Hebrew holdout attachment

* docs(evals): note Koine Greek holdout attachment

* docs: add ADR 0103 placeholder

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

* docs: index ADR-0103 and refresh frontier
2026-05-22 09:43:46 -07:00
Shay
60da4f0cd0 feat(claims): auto-generate CLAIMS.md from ledger + pinned lane SHAs
CLAIMS.md is now mechanically derived from two ground-truth sources:
- core.capability.ledger_report (Tier 1: ratified domains)
- scripts/verify_lane_shas.PINNED_SHAS (Tier 2: pinned lane reports)

The generator is deterministic and gated by
tests/test_claims_md_is_current.py + the lane-shas CI workflow's new
'verify CLAIMS.md is current' step. Drift between in-tree state and
the published claims fails CI before merge.

Tier 1 (5 ratified domains) and Tier 2 (6 pinned lanes) cover every
ADR-0092..0102 invariant currently CI-pinned.
2026-05-21 21:02:36 -07:00
Shay
a8c12670ec fix(capability): correct discourse_planner flag catalog + commit-independent public_demo pin
Two pre-existing latent issues fixed:

1. discourse_planner flag catalog drift (test_flag_report failure)

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

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

2. public_demo lane SHA shifted every commit

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- smoke 67/67, cognition eval byte-identical 100/100/100/100, all
  five lanes byte-identical (reviewer_registry 681a2aab..,
  miner_loop_closure 9f071733.., domain_contract_validation f9c06cde..,
  fabrication_control summary 01e1b6b7.., demo_composition 27d83824..)
2026-05-21 19:02:29 -07:00
Shay
0390491c93 feat(packs): implement ADR-0097 — Mathematics-Logic Reasoning-Capable
First concrete domain claim under ADR-0091's Domain Pack Contract v1.
en_mathematics_logic_v1 is now formally ratified as reasoning-capable
in the capability ledger: 9/9 ADR-0091 predicates pass.

ADR-0097 §"No code changes outside pack artifacts and corpus" relaxed
to include two latent bug fixes that ADR-0093's predicate enforcement
just exposed:

1. language_packs/schema.py: LanguageRole enum widened to include
   DOMAIN_SEED. Three in-tree packs (en_mathematics_logic_v1,
   en_physics_v1, en_systems_software_v1) have declared role="domain_seed"
   since landing but the enum was never updated; load_pack() always
   raised on them. ADR-0093's P1 predicate exposed the mismatch.

2. core/capability/domain_contract_predicates.py: P2 (gloss checksum)
   was reading manifest["checksums"]["glosses_sha256"]; the canonical
   in-tree location is manifest["glosses_checksum"] (top-level). Fixed
   to prefer the canonical key and fall back to the nested form for
   forward compatibility.

ADR-0097 manifest additions to en_mathematics_logic_v1:
- domain_contract_version: 1
- domain_id: "mathematics_logic"
- axioms: null  (rules in v1 — pack proves reasoning via chain
  composition, not declarative axioms)
- rules: null
- teaching_chains: ["mathematics_logic_chains_v1"]
- eval_lanes: three lanes with dev/public/holdout (elementary_mathematics_ood,
  inference_closure, fabrication_control)
- reviewers: ["shay-j"] (resolved via ADR-0092 registry)
- known_gaps: [] (all math/logic gaps in docs/gaps.md were [x])
- provenance: "adr-0097:reviewed:2026-05-21"

Verified evidence:
- core capability domain-contract --pack-id en_mathematics_logic_v1
  → all_passed=True (P1-P9 all pass)
- core capability ledger → mathematics_logic row shows
  status=reasoning-capable, predicates.reasoning_capable=True,
  predicates.expert_demo=False, open_gaps=[],
  operator_chain_coverage all ready=True (8 chains each),
  intent_shapes_present=5
- 14 ADR-0097 invariant tests in
  test_adr_0097_mathematics_logic_ratification.py pin
  status/provenance/expert-demo-gate/contract shape

Two pre-existing tests updated for the new CLI default
(predicate-running, non-zero on missing contract):
- test_capability_domain_contract_json_absent_contract_is_noop now
  uses --structural-only to assert legacy parse-only shape
- test_cli_returns_nonzero_on_missing_contract switched its fixture
  pack from en_mathematics_logic_v1 (now has a contract) to
  en_core_cognition_v1 (no contract)

The pre-existing test_flag_report_tracks_default_off_flags failure
(discourse_planner flag default mismatch, seen since ADR-0092) is
unchanged and unrelated.

Smoke 67/67, packs 6/6, capability tests 49/50, cognition eval
byte-identical 100/100/100/100; lanes byte-identical:
reviewer_registry 6/6, miner_loop_closure 6/6,
domain_contract_validation 9/9, fabrication_control dev 12/12 +
public 9/9.
2026-05-21 18:51:58 -07:00
Shay
d7713b07b1 feat(evals): implement ADR-0096 — Fabrication-Control Eval Lane
First negative-control measure. Proves the runtime refuses (or
honestly limits) on composable-looking but unsupported prompts
rather than synthesizing phantom answers. Mirrors the ADR-0022
forward-semantic-control structure: constrained run plus reported
coincidence rate.

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

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

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

- new teaching/source.py: frozen ProposalSource dataclass with sealed
  ProposalKind Literal["operator","miner","curriculum"], runtime
  invariants (operator → empty source_id; miner/curriculum → non-empty),
  serialize() ("operator" / "miner:<id>" / "curriculum:<id>"),
  as_dict/from_dict round-trip, ProposalSource.operator() helper
- TeachingChainProposal.source field added (proposals.py)
- PackMutationProposal.source field added (store.py)
- build_proposal() accepts optional source kwarg; default uses
  _default_operator_source() pinned at cached git HEAD SHA
- ProposalLog.current_state() now strictly requires source on every
  created event; raises ProposalError with migration pointer if missing;
  validates via ProposalSource.from_dict so malformed payloads reject
- teaching/migrate_proposals_source_field.py: deterministic one-shot
  migration script using PRE_MIGRATION_SENTINEL ("pre-adr-0094-migration")
  as the emitted_at_revision so re-runs across commits produce identical
  bytes
- migration applied to live proposals.jsonl: 11 created events gained
  source field; 33 non-created events untouched; idempotent verified
- 29 unit tests in test_proposal_source.py covering construction,
  serialization, exhaustive-match pattern with assert_never,
  migration determinism (3 idempotence/cross-run tests), strict-parse
  rejection, live-log loads
- 2 test fixes in test_epistemic_invariants.py for new required source param
- smoke 67/67, teaching 17/17, cognition 120/121 (1 pre-existing skip),
  runtime 19/19; cognition eval byte-identical 100/100/100/100
2026-05-21 18:11:09 -07:00
Shay
afdd2ee413 feat(capability): implement ADR-0092 — Reviewer Registry v1
Closes the load-bearing gap blocking every reasoning-capable claim
under ADR-0091: docs/reviewers.yaml was previously `reviewers: []` and
unparsed. Now schema-validated at v1, with a bootstrap shay-j entry
self-sealed via provenance.

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

The pre-existing test_flag_report_tracks_default_off_flags failure is
unrelated (discourse_planner flag default) and not introduced here.
2026-05-21 18:01:24 -07:00
Shay
327047ce26 feat(contemplation): Phase 5 — articulation-quality miner closes the loop
Final phase of the articulation arc.  Consumes the per-turn
``PlanMetrics`` + ``ContemplationFinding`` streams produced by
Phases 3 + 4 and aggregates across many turns to emit
SPECULATIVE ``PACK_MUTATION_CANDIDATE`` findings that the operator
reviews via the existing proposal-review-ratify chain.

This is the doctrine-aligned answer to the user's question:

  "Should we... realize a way to score whether it should use what
  it produced towards memory confidence for future use?"

Yes — and it stays inside ADR-0080: read-only, SPECULATIVE-only,
deterministic, no parallel learning path, no autonomous memory
mutation.

What it adds
------------

* New module ``chat/articulation_telemetry.py``:
    - ``ArticulationObservation`` frozen dataclass — per-turn
      bundle of (turn_id, anchor_subject, prompt_hash,
      plan_substrate_hash, metrics, findings).
    - ``format_articulation_observation_jsonl(...)`` — deterministic
      sort-keys JSONL line.
    - ``load_articulation_observations(lines)`` — schema-tolerant
      loader; malformed lines drop without aborting.
    - ``ArticulationObservationSink`` protocol — structurally
      identical to ``TurnEventSink`` but distinct named type so
      consumers can subscribe to one stream without the other.

* New module ``core/contemplation/miners/articulation_quality.py``:
    - ``mine_articulation_observations(observations, paths)`` —
      pure deterministic aggregator with three v1 rules.
    - **recurring_predicate_monotony** — when the same
      (subject, predicate) pair is flagged WEAK_SURFACE in
      >= _MIN_RECURRENCE (default 3) observations, propose
      substrate diversification with non-dominant predicates.
    - **recurring_planner_gap** — when the same subject is
      flagged PLANNER_GAP >= _MIN_RECURRENCE times across modes,
      propose substrate expansion.
    - **low_average_predicate_diversity** — when mean
      ``predicate_diversity_ratio`` < 0.5 across >= _MIN_RECURRENCE
      observations on the same anchor subject, propose
      diversification.

* Runtime wiring (``chat/runtime.py``):
    - New ``ChatRuntime.attach_articulation_sink(sink)`` method.
      Mirrors ``attach_telemetry_sink`` pattern.
    - Emission point at the end of
      ``_maybe_apply_discourse_planner``: when contemplation
      enabled + sink attached + plan engaged, builds an
      ``ArticulationObservation`` and emits one JSONL line.
      Sink errors propagate (fail-fast, no swallowing).
    - Per-runtime ``_articulation_turn_counter`` increments on
      every emission; gives downstream consumers a stable
      sequence index.

Tests
-----

* ``tests/test_articulation_quality_miner.py`` (11 tests):
    - Empty / sub-threshold cases yield no findings.
    - Each of the three rules fires at threshold.
    - Recurring_predicate_monotony separates by subject (no
      cross-subject merging).
    - Recurring_planner_gap collects distinct modes into a
      sorted comma-joined string.
    - Determinism — byte-equal finding IDs across two runs.
    - SPECULATIVE doctrine pin.
    - JSONL round-trip preserves observation identity.

* ``tests/test_articulation_quality_e2e.py`` (7 tests):
    - Sink-detached + contemplation-on → no emission.
    - Sink-attached + contemplation-off → no emission.
    - Engaged turn emits exactly one observation line.
    - BRIEF prompt emits nothing (fast-path).
    - **Full loop** — run compound prompt 3x → 3 observations →
      miner emits PACK_MUTATION_CANDIDATE with subject='truth',
      predicate='recurring_predicate_monotony', object='belongs_to'.
    - Full loop is deterministic (byte-equal finding IDs across
      two complete runs).
    - Every full-loop finding is SPECULATIVE.

Doctrine pins
-------------

| Claim                                | Pinned by                                                |
|--------------------------------------|----------------------------------------------------------|
| SPECULATIVE-only                     | test_all_findings_remain_speculative                     |
| Deterministic across runs            | test_miner_is_deterministic_across_runs                  |
| Full-loop determinism (e2e)          | test_full_loop_is_deterministic_byte_equal_finding_ids   |
| No autonomous mutation               | Sink is append-only; miner outputs ContemplationFinding  |
|                                      | objects only; nothing writes to packs/vault/teaching.    |
| Append-only stream                   | Sink protocol has emit(line: str) and nothing else.      |

Live demo (3 identical compound-prompt turns)
---------------------------------------------

Runtime emits 3 observations.  Offline miner aggregates and emits:

  [pack_mutation_candidate] subject='truth'
      predicate='recurring_predicate_monotony' object='belongs_to'
      evidence_refs: 3 observations
      proposed_action: "diversify substrate for 'truth': across 3
        observations the plan repeatedly over-concentrated on
        predicate 'belongs_to'. Candidates: add teaching chains
        rooted on 'truth' with relations OTHER than 'belongs_to'
        (grounds / requires / reveals / contrasts / precedes /
        follows) so the planner's RELATION selector has more
        variety to draw from."
      epistemic_status: speculative

The system observed its own articulation patterns across many
turns, identified the corpus expansion priority, and emitted a
specific reviewable proposal — without mutating anything.  The
operator decides whether to act on it via the existing review
chain.

Verification
------------

  pytest test_articulation_quality_miner.py       11/11 pass
  pytest test_articulation_quality_e2e.py          7/7 pass
  pytest test_plan_metrics*.py                    18/18 pass (Phase 4)
  pytest test_plan_contemplation*.py              17/17 pass (Phase 3)
  pytest test_discourse_planner_*.py              99/99 pass
  pytest test_articulation_demo.py                 all claims supported
  pytest test_narrative_example_intents.py         pass
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

The articulation arc is complete.  Future work documented in
``docs/sessions/SESSION-2026-05-21-articulation-arc.md`` §8:
connective rotation, generalised pronoun selection, doctrine-gated
plan revision, Phase 2.5 mid-sentence reflection.  None blocking.
2026-05-21 10:55:39 -07:00
Shay
b07fb0413c feat(contemplation): Phase 4 — per-plan articulation telemetry metrics
Quantitative companion to Phase 3 (commit 664e081).  Where Phase 3
emits SPECULATIVE *findings* about plan quality, Phase 4 emits
typed *measurements* — pure-function projection of a
``DiscoursePlan`` into a ``PlanMetrics`` dataclass.

Why this matters
----------------

The discourse planner now produces multi-clause grounded
articulations (Phase 1), the renderer pronominalizes across
consecutive same-subject moves (Phase 2), and the contemplation
pre-flight emits qualitative concerns about plan shape (Phase 3).
What was missing was the *aggregable* layer: per-turn structured
numbers that downstream consumers can stream across many turns
to score quality patterns the per-turn observer cannot see.

Phase 4 lands that layer.  Phase 5 (offline contemplation miner)
becomes possible because there's now structured signal to mine.

What it measures
----------------

  Structure
    * move_count                      — total moves in plan
    * fact_bearing_count              — moves with fact != None
  Move-kind distribution
    * anchor_count / support_count / relation_count
      / transition_count / closure_count
  Diversity
    * unique_predicates               — distinct predicates across
                                        fact-bearing moves
    * unique_subjects                 — distinct subject lemmas
    * unique_sources                  — distinct FactSources
  Topic dynamics
    * topic_shift_count               — consecutive pairs where
                                        subject changed
    * pronominalization_opportunities — consecutive pairs where
                                        subject held (= Phase 2's
                                        anaphora trigger count)
  Derived ratios
    * predicate_diversity_ratio       — unique_predicates /
                                        fact_bearing_count
    * subject_focus_ratio             — pronominalizations /
                                        (pronominalizations +
                                         topic_shifts)

Every field is a deterministic pure function of the plan: same
plan in → byte-equal ``PlanMetrics.as_dict()`` out.  This is the
load-bearing claim that lets Phase 5 aggregate across turns
without "is this the same metric?" ambiguity.

Doctrine alignment
------------------

Per ADR-0080 contemplation discipline:
  * Read-only — metrics are pure projections of the plan; no
    mutation of plan, runtime state, or memory tiers.
  * No autonomous learning — metrics are observations, not
    learned policy.  Promotion to memory still flows through
    the existing proposal-review-ratify chain.
  * Deterministic replay — pinned by test_metrics_are_deterministic_
    and_byte_equal_as_dict plus the runtime-level
    test_metrics_byte_equal_across_runs.

Wiring
------

* New ``ChatRuntime.last_plan_metrics`` property — read-only
  ``PlanMetrics`` from the most recent turn where the planner
  engaged (and ``discourse_contemplation`` was on); ``None``
  otherwise.  Reset between turns alongside ``last_plan_findings``
  via the existing top-of-call reset block.

* Same opt-in flag as Phase 3 (``discourse_contemplation``).
  When True, the runtime computes both findings AND metrics in
  the same block; when False (default), both stay at empty/None.

Demo (config: discourse_contemplation=True)
-------------------------------------------

  "What is knowledge?"          → metrics: None  (BRIEF fast-path)
  "Tell me about memory."       → moves=3 fact_bearing=3
                                  kinds=A:1/S:1/R:1/T:0/C:0
                                  unique_predicates=3 subjects=1
                                  pronominalization_ops=2 shifts=0
                                  predicate_diversity=1.000
                                  subject_focus=1.000
  "What is truth, and why does
   it matter?"                  → moves=7 fact_bearing=6
                                  kinds=A:2/S:2/R:2/T:1/C:0
                                  unique_predicates=4 subjects=1
                                  pronominalization_ops=4 shifts=1
                                  predicate_diversity=0.667  ← Phase 3
                                                                WEAK_SURFACE
                                                                quantified
                                  subject_focus=0.800
                                  + 1 finding (weak_surface)

The compound-prompt numbers are particularly informative:
``predicate_diversity=0.667`` is the algebraic expression of the
Phase 3 ``WEAK_SURFACE`` rule — the rule fires precisely because
6 fact-bearing moves used only 4 distinct predicates.
``subject_focus=0.800`` quantifies that 80% of consecutive pairs
held the same subject — high topic stickiness that Phase 2's
reflective renderer leveraged into 4 ``it`` substitutions.

Tests
-----

* ``tests/test_plan_metrics.py`` — 10 unit tests pinning each
  field, derived ratios, bridge-move handling (``fact=None``
  resets the focus channel), and determinism via ``as_dict()``
  byte-equality.

* ``tests/test_plan_metrics_runtime.py`` — 8 end-to-end tests
  proving the runtime wiring: disabled by default, populated
  when enabled, BRIEF prompts yield None, no cross-turn leak,
  byte-equal across runs, parametrized co-population check
  alongside findings.

Verification
------------

  pytest tests/test_plan_metrics*.py              18/18 pass
  pytest tests/test_plan_contemplation*.py        17/17 pass (Phase 3)
  pytest tests/test_discourse_planner_*.py        99/99 pass
  pytest tests/test_articulation_demo.py          all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py             pass
  cognition eval OFF vs ON                        45/45 surface byte-equal
                                                  45/45 trace_hash byte-equal
                                                  4/4 aggregate metrics
                                                      identical
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

Phase 5 (logged, not built)
---------------------------

Offline contemplation miner that consumes ``last_plan_findings``
+ ``last_plan_metrics`` streams across many turns and emits
reviewable pack-mutation candidates.  Still SPECULATIVE;
review-gated; never auto-promoted to memory.  Now unblocked by
the structured metric surface Phase 4 lands.
2026-05-21 10:39:39 -07:00
Shay
664e08150c feat(contemplation): Phase 3 — live plan contemplation pre-flight
Wires deterministic, read-only contemplation OVER a completed
``DiscoursePlan`` BEFORE the renderer fires.  This is the
"reasoning at meaningful checkpoints" capability — the system
now inspects the global shape of its own articulation plan and
emits SPECULATIVE findings about quality issues the move-by-move
planner couldn't see locally.

Doctrine alignment (ADR-0080)
-----------------------------

* **Read-only** — never mutates the plan, packs, vault, teaching
  corpus, or runtime state.  Returns findings as a tuple; the
  runtime stores them on a read-only property.
* **SPECULATIVE-only** — every finding is stamped
  ``EpistemicStatus.SPECULATIVE`` by the schema's ``__post_init__``;
  the doctrine pin ``test_findings_always_speculative`` keeps that
  invariant visible.
* **Deterministic replay** — same plan → byte-identical findings
  (same ``substrate_hash``, same ``finding_id``).
* **No parallel learning path** — findings flow to a read-only
  observation surface (``runtime.last_plan_findings``).  Promotion
  to memory still goes through the existing proposal → review →
  ratify chain.  The offline contemplation miner (Phase 5 target)
  is what eventually consumes the findings and emits reviewable
  pack-mutation candidates.

v1 rules (``core/contemplation/plan_preflight.py``)
----------------------------------------------------

* ``PLANNER_GAP`` — non-BRIEF mode produced anchor-only depth.
  Signals the teaching/cross-pack substrate for that lemma is too
  thin for the planner to expand.

* ``WEAK_SURFACE`` — three or more moves share a predicate.
  Signals the rendered surface will read mechanical (e.g. three
  ``belongs_to`` clauses in a row).  Fires on today's compound
  prompt ``"What is truth, and why does it matter?"`` — the
  6-sentence plan uses ``belongs_to`` 3 times.

* ``COVERAGE_GAP`` — every move in a multi-move plan draws from
  a single ``FactSource``.  Signals one-sided substrate (e.g.
  pack-only with no teaching enrichment).

Runtime wiring
--------------

* New ``RuntimeConfig.discourse_contemplation: bool = False`` —
  opt-in for now.  Default off keeps the cognition eval byte-
  identical to Phase 2 (verified 45/45 surface + 45/45 trace_hash).
* New ``ChatRuntime.last_plan_findings`` property — read-only tuple
  of ``ContemplationFinding`` records from the most recent turn.
  Reset to ``()`` at the start of every plan-engagement call so
  findings never leak across turns.
* Contemplation runs AFTER the planner produces a multi-move plan
  and BEFORE the renderer fires; the plan itself is not modified.

Demo (config: discourse_contemplation=True)
-------------------------------------------

  "What is knowledge?"          → planner fast-path; no findings
  "Tell me about memory."       → 3 moves, distinct predicates;
                                  no findings (good!)
  "What is truth, and why does
   it matter?"                  → 6 moves, ``belongs_to`` x 3:
                                  [WEAK_SURFACE] subject='truth'
                                    predicate='predicate_repeats_in_plan'
                                    object='belongs_to'
                                  proposed action: diversify the
                                  relation inventory for 'truth'
                                  (grounds / requires / reveals /
                                  contrasts) so the planner has
                                  more variety to draw from.
  "Explain truth."              → 3 moves, distinct predicates;
                                  no findings

Tests
-----

* ``tests/test_plan_contemplation.py`` — 11 unit tests pinning
  each rule, empty/trivial plans, determinism, and the
  SPECULATIVE-only doctrine.

* ``tests/test_plan_contemplation_runtime.py`` — 6 end-to-end
  tests proving the runtime wiring: disabled by default,
  populated when enabled, reset across turns, deterministic
  across runs, all findings SPECULATIVE.

Verification
------------

  pytest tests/test_plan_contemplation*.py        17/17 pass
  pytest tests/test_discourse_planner_*.py        99/99 pass
  pytest tests/test_articulation_demo.py          all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py             pass
  cognition eval OFF vs ON                        45/45 surface byte-equal
                                                  45/45 trace_hash byte-equal
                                                  4/4 aggregate metrics
                                                      identical
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

Phases roadmap (logged in commit, not built today)
--------------------------------------------------

* Phase 4 — articulation telemetry enrichment.  Emit per-turn
  metrics (grounding_ratio, anaphora_engagement, plan_completeness,
  novelty, focus_consistency) to the existing telemetry sink so
  the offline miner has structured signal.

* Phase 5 — offline contemplation miner.  Extend
  ``core/contemplation`` with a miner that consumes
  ``last_plan_findings`` streams and emits reviewable
  pack-mutation / teaching-corpus expansion proposals.  Still
  SPECULATIVE; review-gated.
2026-05-21 10:30:22 -07:00
Shay
9dfb505f06 feat(discourse): Phase 2 — reflective rendering pronominalizes focus subject
The Phase 1 multi-clause renderer (commit 63ffd88) produces grounded
content but reads mechanically because the subject lemma repeats in
every clause:

  "Truth is what is true. Furthermore, truth belongs to cognition.truth.
   In turn, truth grounds knowledge. Truth belongs to epistemic.ground.
   Furthermore, truth belongs to logos.core. In turn, truth requires
   evidence."

This is the literal articulation gap that motivated Phase 2 —
"reasoning at meaningful checkpoints during sentence construction
in order to have a stronger idea of what has come prior and is
already done to help better inform the next move."  Between move
``i`` and move ``i+1`` the renderer now reflects on what subject
has just been established (the "focus") and renders the next clause
with a pronoun when the focus carries forward:

  "Truth is what is true. Furthermore, it belongs to cognition.truth.
   In turn, it grounds knowledge. It belongs to epistemic.ground.
   Furthermore, it belongs to logos.core. In turn, it requires
   evidence."

Rules
-----

* Track ``focus_subject`` across moves (the lemma most recently used
  as a fact subject).
* When the next move's ``fact.subject`` is byte-equal to the current
  focus → swap subject token to ``"it"``.
* When the next move's subject differs → preserve the explicit lemma
  AND update focus.  Topic shifts (TRANSITION moves; compound bridge
  TRANSITION) thus reset the pronominalization channel naturally.
* Sentence-initial position (no connective): capitalised ``"It"``.
* Mid-sentence (after connective + comma): lowercase ``"it"``.

Doctrine alignment
------------------

Pure deterministic transformation of the existing plan; no new
content introduced, no LLM, no stochastic sampling.  Same plan in →
same surface out, always.  trace_hash invariance holds because:

  * BRIEF-mode prompts short-circuit the planner before render
    (commit 63ffd88's fast path) and are unaffected.
  * Multi-move plans render to a deterministically-different string
    that compute_trace_hash already folds in via ``surface``.

Wiring
------

* New ``reflective: bool = False`` parameter on ``render_plan``
  (back-compat default — every existing call site and test pinning
  Phase 1 output continues to work).
* ``_clause_for`` gains optional ``prior_focus_subject`` arg used by
  the reflective path; unchanged default behaviour.
* Runtime hook ``chat.runtime._maybe_apply_discourse_planner``
  passes ``reflective=True`` so the default chat path benefits.

Tests
-----

New ``tests/test_discourse_planner_reflective.py``:

* ``test_reflective_replaces_repeated_subject_with_it``
* ``test_reflective_handles_three_consecutive_same_subject_moves``
* ``test_reflective_capitalises_sentence_initial_pronoun``
* ``test_reflective_resets_focus_on_topic_shift``
* ``test_reflective_off_preserves_phase1_output``
* ``test_reflective_default_is_off_for_back_compat``
* ``test_reflective_is_deterministic``
* ``test_reflective_single_move_byte_identical_to_non_reflective``
  (load-bearing — pins that the cognition eval stays byte-equal
  across the Phase 2 flip because every cognition case is single-
  move).

Verification
------------

  pytest tests/test_discourse_planner_*.py        99/99 pass
                                                  (91 existing + 8 new)
  pytest tests/test_articulation_demo.py          all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py             pass
  cognition eval OFF vs ON                        45/45 surface byte-equal
                                                  45/45 trace_hash byte-equal
                                                  4/4 aggregate metrics
                                                      identical
  core test --suite smoke                         67/67 pass
  core test --suite runtime                       19/19 pass

Live demo (default config):

  "What is knowledge?"  → unchanged (BRIEF, fast-path)
  "Tell me about
    memory."            → "Memory is what a person recalls.
                          Furthermore, it belongs to cognition.memory.
                          In turn, it requires recall."
  "What is truth, and
    why does it matter?"→ "Truth is what is true. Furthermore, it
                          belongs to cognition.truth. In turn, it
                          grounds knowledge. It belongs to
                          epistemic.ground. Furthermore, it belongs
                          to logos.core. In turn, it requires
                          evidence."
  "Explain truth."      → "Truth is what is true. Furthermore, it
                          belongs to cognition.truth. In turn, it
                          grounds knowledge."

Out of scope for this commit (future Phase 2 follow-ons):

* Connective rotation ("Furthermore" → "Also" → "In addition"
  to break the repetitive cascade).
* Cross-clause de-duplication (skip moves whose ``new`` lemmas
  were already introduced by an earlier move).
* Generalised pronoun selection beyond ``it`` (requires gender /
  number / animacy signals the pack lexicon doesn't carry today).
2026-05-21 10:16:12 -07:00
Shay
63ffd88595 feat(runtime): default discourse_planner=True + fast-path BRIEF short-circuit
Flips ``RuntimeConfig.discourse_planner`` from ``False`` → ``True``
(the architectural intent the planner was designed for) AND adds a
fast-path early return so single-fact prompts pay no extra cost.

Why the flip
------------

The discourse planner apparatus has been fully wired in the codebase
for some time (``generate.discourse_planner.plan_discourse`` /
``plan_compound_discourse`` / ``render_plan``,
``generate.grounding_accessors.grounding_bundle_for``,
``chat.runtime._maybe_apply_discourse_planner``) but gated off behind
this flag.  Investigation surfaced that:

  * **Cognition eval (45 cases) is byte-identical OFF vs ON** across
    both surface and trace_hash projections — the planner's
    downstream ``len(plan.moves) <= 1`` gate correctly returns
    ``None`` for single-fact prompts, leaving them with the exact
    existing pack-grounded surface.

  * **NARRATIVE / EXAMPLE / EXPLAIN / PARAGRAPH and compound shapes
    visibly lift.**  ``"Tell me about memory."`` goes from a one-
    fragment disclosure to a 3-sentence grounded discourse.
    ``"What is truth, and why does it matter?"`` — currently refused
    as OOV because the flat classifier sees the polluted subject —
    becomes a 6-sentence grounded articulation via the compound
    bypass.

  * **No quality regression on existing benches.**  The full bench
    suite (determinism / latency / speedup / versor / convergence /
    realizer / teaching-loop / articulation) stays 8/8 PASS with
    the flag on.

Why the fast-path
-----------------

Default-on uncovered a perf trap: the gate ran
``grounding_bundle_for(lemma)`` (pack + teaching + cross-pack queries)
AND ``plan_discourse(...)`` on EVERY turn, then discarded the
result when ``len(plan.moves) <= 1``.  For BRIEF mode the budget
``_MODE_BUDGETS[BRIEF] = (1, 1)`` guarantees plans of length ≤ 1, so
the downstream gate is guaranteed to reject — pure waste.  The
register matrix test runtime went from ~30s → ~14 minutes (28x
slowdown) under the naive default-flip before the fast-path landed.

The new short-circuit:

  if mode is BRIEF and not compound.is_compound():
      return None

skips the bundle query + plan run entirely for the common case.
Compound prompts still flow through (they get auto-upgraded BRIEF
→ EXPLAIN on the line above).  Empirical post-fast-path
measurement on a 45-case eval (workers=1):

  OFF: 23.31s  (1.93 turns/sec)
  ON : 17.74s  (2.54 turns/sec)
  slowdown : 0.76x  (flag-ON is actually 24% FASTER — the bundle
                     work the OFF path also touches downstream is
                     short-circuited cleanly when not needed)
  surface byte-equal: True
  trace_hash byte-equal: True

Test updates
------------

* ``test_discourse_planner_render.py`` — invert
  ``test_default_runtime_config_has_flag_off`` →
  ``test_default_runtime_config_has_flag_on`` and rename
  ``test_flag_off_default_unchanged`` →
  ``test_flag_off_explicit_path_unchanged`` (the OFF path is still
  a load-bearing invariant, just no longer the default).

* ``test_narrative_example_intents.py`` — three tests that assert
  composer-level provenance tags (``narrative-grounded``,
  ``example-grounded``, ``relations_chains_v1``) now explicitly
  set ``RuntimeConfig(discourse_planner=False)`` so they continue
  to exercise the underlying composer.  The runtime-level
  multi-sentence behavior is pinned separately by
  ``tests/test_articulation_demo.py``.

Verified
--------

  cognition eval (45 cases)               OFF ≡ ON byte-identical
  pytest tests/test_discourse_planner_*   132/132 pass
  pytest tests/test_articulation_demo.py  all claims supported
  pytest tests/test_narrative_example_intents.py  pass
  pytest tests/test_runtime_config.py     pass
  core test --suite smoke                 67/67 pass
  core test --suite runtime               19/19 pass
  core test --suite packs                  6/6 pass

Live demo (default config):
  "What is knowledge?"          → single sentence (BRIEF, fast-path)
  "Tell me about memory."       → 3 grounded sentences
  "What is truth, and why does
   it matter?"                  → 6 grounded sentences (was: OOV)
  "Explain truth."              → 3 grounded sentences
2026-05-21 10:06:49 -07:00
Shay
c945b9a045 fix(intent): widen CORRECTION to catch fully-spoken `that is/was ...` forms
Follow-on to the word-boundary fix (commit 0dd30b8).  After tightening
``\bno\b`` etc. with word boundaries, an audit surfaced a separate
pre-existing gap in the CORRECTION trigger: the contracted-only
``that'?s\s+(?:not|wrong)`` slot silently dropped every fully-spoken
copula form to UNKNOWN.

Concrete gap (every one previously UNKNOWN):

  "That is not right."        → UNKNOWN
  "That is wrong."            → UNKNOWN
  "That was wrong."           → UNKNOWN
  "That is incorrect."        → UNKNOWN
  "That is false."            → UNKNOWN
  "That was not right."       → UNKNOWN
  "that is mistaken."         → UNKNOWN
  "That was incorrect."       → UNKNOWN

Root cause: the slot ``that'?s\s+(?:not|wrong)`` matches only

    that's  /  thats

— ``'?s`` makes the apostrophe optional but the literal ``s`` is
mandatory.  ``that is`` (full word ``is``) and ``that was`` (full
word ``was``) had no path.  And the predicate alternation only
accepted ``not`` or ``wrong``; ``incorrect``, ``false``, and
``mistaken`` were also missing.

Fix: widen both slots in one pattern revision.

    Before:
      that'?s\s+(?:not|wrong)
    After:
      that(?:'?s|\s+(?:is|was))\s+(?:not|wrong|incorrect|false|mistaken)

The full pattern now reads:

    \b(?:no
       |that(?:'?s|\s+(?:is|was))\s+(?:not|wrong|incorrect|false|mistaken)
       |incorrect
       |actually
       |correction)\b

Boundary discipline holds: the outer ``\b...\b`` still prevents the
predicate alternation from eating into longer words.  Verified:

  "That is correct."          → UNKNOWN (right NOT in predicate set)
  "That is right."            → UNKNOWN (right NOT in predicate set)
  "That is true."             → UNKNOWN (true NOT in predicate set)
  "That works."               → UNKNOWN
  "That is interesting."      → UNKNOWN
  "That is falsifiable."      → UNKNOWN (``false`` + ``i`` is word→word
                                         so ``\b`` after ``false`` fails)
  "That was wrongly accused." → UNKNOWN (same logic for ``wrong``+``ly``)

Tests extended:
  * ``test_correction_canonical_forms_still_route`` — 8 new parametrize
    cases for the fully-spoken copula forms
  * ``test_correction_does_not_eat_no_prefixed_words`` — 9 new
    parametrize cases for the affirmative ``That is/was ...`` shape
    AND the boundary-trap cases ``falsifiable`` / ``wrongly accused``

Verified:
  pytest tests/test_intent_subject_extraction.py         33/33 pass
  full intent + register-diagnostic + proposition graph  77/77 pass
  core test --suite smoke                                67/67 pass
  core test --suite runtime                              19/19 pass
2026-05-21 08:36:33 -07:00
Shay
0dd30b86a7 fix(intent): anchor CORRECTION trigger with word boundaries
While investigating the adjacent RECALL classifier gap, a much
wider intent-classification bug surfaced: every prompt beginning
with a word that *starts with* the letters of any CORRECTION
trigger silently routed to CORRECTION with a mangled subject.

Concrete examples seen during diagnosis:

  "Now remember light."        → CORRECTION  subject="w remember light"
  "Nothing matters."           → CORRECTION  subject="thing matters"
  "Notice the truth."          → CORRECTION  subject="tice the truth"
  "Note that recall fires."    → CORRECTION  subject="te that recall fires"
  "Nominate a candidate."      → CORRECTION  subject="minate a candidate"
  "Norma is here."             → CORRECTION  subject="rma is here"
  "Notwithstanding ..."        → CORRECTION  subject="twithstanding ..."

Root cause: ``generate/intent.py`` ``_RULES`` line ~213 used the
pattern

    (?:no|that'?s\s+(?:not|wrong)|incorrect|actually|correction)

The alternation has ``no``, ``incorrect``, ``actually``, ``correction``
as bare substrings — no word boundary on either side.  Combined with
``re.match``'s start-of-string anchor, *any* prompt beginning with
``No``-, ``Incorrect``-, ``Actually``-, or ``Correction``-prefixed
text matched as CORRECTION; the regex's match span was then sliced
off the prompt to produce a subject like ``"w remember light"``
(from ``"Now remember light."``).

The same hazard threatens:

  * ``no``         → eats ``Now`` / ``Notice`` / ``Note`` / ``Nothing`` /
                     ``Nominate`` / ``Norma`` / ``Notwithstanding`` / ...
  * ``incorrect``  → would eat ``incorrectly``
  * ``actually``   → would eat ``actualization``
  * ``correction`` → would eat ``corrections``

Fix: add ``\b`` anchors on both sides of the alternation.

    \b(?:no|that'?s\s+(?:not|wrong)|incorrect|actually|correction)\b

``\b`` is zero-width, so ``re.match``'s start-of-string anchor still
holds; the left ``\b`` is a no-op at position 0.  The right ``\b``
forces the matched token to end on a word boundary — i.e., the next
character must be non-word (whitespace, punctuation, EOL) — so
``\bno\b`` matches ``"No."`` / ``"No way"`` / ``"No, ..."`` but NOT
``"Now"`` / ``"Nothing"`` / etc.

Verified 11/11 previously-misfiring prompts now correctly classify
as UNKNOWN, and 8/8 legitimate CORRECTION pragmas
(``"No."`` / ``"No way."`` / ``"Incorrect."`` / ``"Actually, ..."`` /
``"Correction: ..."`` / ``"That's wrong."`` / ``"No, that's wrong."`` /
``"no, knowledge is wrong."``) still route correctly.

Tests extended with two new parametrized blocks in
``tests/test_intent_subject_extraction.py``:

  * ``test_correction_canonical_forms_still_route`` — 8 cases pinning
    the legitimate CORRECTION patterns
  * ``test_correction_does_not_eat_no_prefixed_words`` — 10 cases
    pinning the boundary fix against regression

Verified:
  pytest tests/test_intent_subject_extraction.py        25/25 pass
  pytest tests/test_intent_proposition_graph.py        + others       60/60 pass
  core test --suite smoke                                            67/67 pass
  core test --suite runtime                                          19/19 pass

Out of scope: ``"That is not right."`` (a real CORRECTION pragma the
regex never caught because ``that'?s\s+`` requires literal ``s`` after
``that``; the colloquial ``that is`` form was always UNKNOWN). Separate
gap, unchanged here.
2026-05-21 08:29:16 -07:00
Shay
7ef4ef4546 fix(intent): widen RECALL trigger to accept `recall alongside remember`
The articulation breadth benchmark surfaced a RECALL intent gap:

  Before (bench output):
    RECALL    UNKNOWN    pack    Pack-resident tokens — pack-grounded
                                 (en_core_cognition_v1): recall ...

The probe prompt ``"Recall truth."`` classified as UNKNOWN and fell
through to the ADR-0086 pack-resident-token surface — a graceful
degradation, not a hard failure, but a real classifier gap.

Root cause: ``generate/intent.py`` ``_RULES`` line 213 only matched
the imperative ``remember``:

    (re.compile(r"remember\s+", re.IGNORECASE), IntentTag.RECALL)

The verb ``recall`` — every bit as natural an imperative — was
missing from the trigger pattern.  ``"Remember truth."`` correctly
routed to RECALL; ``"Recall truth."`` did not.

Fix: widen the alternation to ``(?:remember|recall)\s+``.  One-word
change; ``re.match`` anchoring at the start of the prompt means the
fix only catches the canonical imperative form, leaving downstream
contexts untouched:

  * ``Does memory require recall?``      → VERIFICATION (unchanged;
    earlier rule on the aux-verb pattern fires first)
  * ``What is recall?``                  → DEFINITION   (unchanged;
    ``what\s+is\s+`` fires first)
  * ``Why does recall exist?``           → CAUSE        (unchanged;
    ``why\s+`` fires first)
  * ``I recall.``                        → UNKNOWN      (unchanged;
    no trailing word after ``recall``, ``\s+`` doesn't match)
  * ``Please recall the truth.``         → UNKNOWN      (unchanged
    — symmetric with ``Please remember the truth.`` since rules use
    ``pattern.match`` not ``pattern.search``)

After (bench output):
    RECALL    RECALL    pack    Truth is what is true. pack-grounded
                                (en_core_cognition_v1).

The articulation bench probe now routes correctly and produces a
pack-grounded definition surface — the canonical RECALL output on
a pack-resident lemma.

Tests extended: ``tests/test_intent_subject_extraction.py::
test_recall_strips_articles`` is parametrized with four new
``Recall ...`` cases parallel to the existing ``Remember ...``
cases.  A regression that re-narrows the trigger pattern fails the
gate immediately.

Verified:
  * pytest tests/test_intent_subject_extraction.py            7/7 pass
  * pytest tests/test_register_firing_diagnostic.py           3/3 pass
  * core test --suite smoke                                  67/67 pass
  * core test --suite runtime                                19/19 pass
  * core bench --suite articulation  → RECALL ✓ pack-grounded
2026-05-21 08:26:08 -07:00
Shay
f6f8ee603f
feat(evals): per-intent register-firing diagnostic + CI gate + tests (#103)
Replaces the per-pack-aggregate diagnostic landed at 58ac780 with a
per-intent matrix decomposition authored by Codex on a parallel
worktree. Codex's design directly answers the original motivating
question — "which packs' marker pools don't fire on which intent
shapes" — that the aggregate version flattened.

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Codex <noreply@openai.com>
2026-05-21 07:05:23 -07:00
Shay
483c66dc5f test(register): widen invariant matrix to all 100 ratified packs
PR #102 ratified 93 drafted register packs, bringing the catalog to
100 fully-sealed packs on disk. This widens
tests/test_cognition_eval_register_matrix.py::_RATIFIED_REGISTERS
from 7 to 100 so every projection-invariant assertion (trace_hash,
intent_correct, terms_captured, surface_contains_pass,
versor_closure, versor_condition, canonical surface, and aggregate
metrics) now runs against every ratified pack.

Verification on PR #102 head: 801 cells passed in 316.76s
  = 100 registers × 8 projections + 1 meta-test
  = full ADR-0072 invariant proven across the entire register axis
    on all 45 cognition cases.

The meta-test test_register_matrix_covers_every_ratified_pack
remains the structural co-evolution guard: any future register pack
ratification must widen both REGISTER_IDS in
scripts/ratify_register_packs.py AND _RATIFIED_REGISTERS here in
the same change, or CI fails fast.
2026-05-21 06:38:22 -07:00
Shay
cad8b39928
feat(packs/register): 93-pack catalog rollout — drafted → ratified (#102)
* feat(packs/register): materialise A_depth drafted registers

Lands 3 drafted depth registers, dominated by disclosure-domain count and structural compression/expansion knobs; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path. Also aligns the smoke contract assertion with the current pack-grounded unknown evidence split.

* feat(packs/register): materialise B_tone drafted registers

Lands 15 drafted tone registers, dominated by bounded affective opening and closing marker palettes; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise C_stance drafted registers

Lands 11 drafted stance registers, dominated by epistemic posture markers plus light deterministic depth clauses; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise D_posture drafted registers

Lands 10 drafted posture registers, dominated by role-shaped marker families for peer, mentor, scholar, practitioner, and related voices; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise E_domain drafted registers

Lands 11 drafted domain registers, dominated by academic, executive, technical, legal, scientific, and philosophical marker families with bounded known-key knobs; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise F_cultural drafted registers

Lands 12 drafted cultural registers, dominated by plainspoken, diplomatic, classic, contemporary, and lyrical marker palettes; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise G_affective drafted registers

Lands 10 drafted affective registers, dominated by cheerful, somber, grave, wry, gentle, and earnest marker families; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise H_functional drafted registers

Lands 10 drafted functional registers, dominated by documentary, instructional, persuasive, clarifying, comparing, and exemplifying marker families; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.

* feat(packs/register): materialise I_composite drafted registers

Lands 11 drafted composite registers, dominated by combined knob and marker families for tutorial, interview, briefing, lecture, memo, story, elegy, epigram, and manifesto voices; the sealed reports keep grounding_source and trace_hash byte-identical to the unregistered path.
2026-05-21 06:37:38 -07:00
Shay
66db063f0b test(register): full 7-pack invariant matrix on cognition lane
Adds tests/test_cognition_eval_register_matrix.py — strict superset
of tests/test_register_invariant_grounding.py (which covered only 4
of the 7 ratified register packs).

Parametrizes over all seven ratified register packs
({default_neutral, terse, precise, convivial, pedagogical, formal,
socratic}_v1) and asserts byte-identity against the unregistered
baseline for every per-case projection the cognition eval reports:

  * trace_hash               (ADR-0072 truth-path-isolation)
  * intent_correct           (intent runs upstream of realizer)
  * terms_captured           (scored off canonical surface)
  * surface_contains_pass    (scored off canonical surface)
  * versor_closure           (truth-path field invariant)
  * versor_condition         (exact float, stronger than closure)
  * surface                  (CognitiveTurnResult.surface is the
                              pre-decoration canonical the trace
                              hash consumes; substantive transforms
                              live on turn_log[-1].surface)

Aggregate metrics on EvalReport are pinned identically: total,
intent_correct, terms_captured, terms_expected, surface_grounded,
versor_closures.

Meta-test test_register_matrix_covers_every_ratified_pack enforces
that _RATIFIED_REGISTERS in this file stays in lockstep with
scripts/ratify_register_packs.py::REGISTER_IDS — so the 93 drafted
register packs in packs/register/_catalog.json cannot ratify into
CI without each one passing the full invariant matrix.

Run: 57 cells (8 projections x 7 registers + 1 meta), 27.7s
sequential across 45 cognition cases per register.

Pre-existing smoke failure (test_chat_response_surface_uses_
articulation_plan in tests/test_runtime_config.py) is the ADR-0086
expected-string test on main; unrelated to this change.
2026-05-21 06:24:36 -07:00
Shay
79f1678923 feat: ADR-0086 + ADR-0087 + 100-register catalog — cognition lane closure
Three load-bearing pieces:

1. ADR-0086 — UNKNOWN-intent pack-resident token surface
   New deterministic composer `pack_grounded_unknown_surface` in
   chat/pack_grounding.py.  When intent classification returns UNKNOWN
   but the prompt contains pack-resident lemmas (via cross-pack
   resolver), surface those lemmas with their semantic_domains
   instead of falling to the bare _UNKNOWN_DOMAIN_SURFACE.  Wired
   into chat/runtime.py::_maybe_pack_grounded_surface as the
   last typed-intent branch before the OOV fallback.  Null-lift
   invariant pinned: fully-OOV prompts still emit the universal
   disclosure byte-identically.  Closes four cognition-eval term
   misses: unknown_logos_019 (public), unknown_evidence_042 (dev),
   unknown_spirit_041 + unknown_word_018 (holdout).  Side effect:
   evals/results/phase2_pack_measurements.json refusal_rate drops
   from 0.25 → 0.125 across all three identity packs (no longer
   refusing on these prompts).

2. ADR-0087 — PROCEDURE selector + trailing-clause subject echo
   Two coupled changes in chat/pack_grounding.py:
   (a) Numeric-determiner downrank in _extract_procedure_topic_lemma:
       tokens whose primary semantic_domain starts with
       "quantitative.numeric." are demoted; non-numeric resident
       candidates always win.  So "compare two terms" anchors on
       `compare` not `two`.
   (b) Trailing clause echoes the full normalized subject_text
       rather than just the selected lemma, so OOV head nouns like
       "terms" reach the surface even when only the procedure verb
       is pack-resident.  Closes procedure_compare_011.

3. 100-register catalog
   New packs/register/_catalog.json — canonical machine-readable
   spec for all 100 registers (7 currently-ratified + 93 drafted)
   organized into 9 voice groups (depth/tone/stance/posture/domain/
   cultural/affective/functional/composite).  Each entry is a
   complete production input — realizer_overrides, marker palettes
   (openings/transitions/closings), depth_preference, description,
   author_notes.  All realizer_overrides use only legal keys per
   scripts/ratify_register_packs.py::_KNOWN_OVERRIDE_KEYS.
   Companion packs/register/CATALOG.md documents the production
   loop: materialize → widen REGISTER_IDS → ratify → smoke.

Cognition-eval lifts (all three splits):
  public:  term_capture 91.7% → 100.0%  (+8.3pp)
  holdout: term_capture 83.3% → 100.0%  (+16.7pp)
  dev:     term_capture 78.6% → 100.0%  (+21.4pp)
  surface_groundedness: 100% preserved on all splits
  intent_accuracy / versor_closure: 100% preserved on all splits

Tests:
  tests/test_pack_grounded_unknown.py     — 14 tests (composer
    direct + runtime engagement + null-lift invariant)
  tests/test_adr_0087_procedure_selector.py — 12 tests (selector
    numeric downrank + trailing-clause echo + regression guard)
  Existing test suites unaffected — cognition lane 120 passed / 1
  skipped both before and after.  Full lane net −3 failures vs
  pristine main (39 → 36 — none introduced).
2026-05-21 00:08:12 -07:00
Shay
583aae42ef
feat(packs): ADR-0085 content style pass v2 — 3sg + plural agreement (+ closure infra) (#100)
Applies the ADR-0085 v2 brief's 16 fluency rows (Pattern A 3sg agreement on
relative-clause verbs + Pattern B plural after quantifier) plus 7 additional
"what a person {VERB}" rows surfaced in live chat probe (`Knowledge is what
a person know` → `knows`, similar for `memory`/`question`/`word`/`answer`/
`response`/`express`). 23 gloss edits total across 5 packs.

The brief had an internal conflict: it forbids atom edits but requires
closure-verifier 0/0, while ADR-0084's verifier enforces
`atoms == content_tokens(gloss)` exactly. Resolved by:

  1. Extending `scripts/verify_definitional_closure.py` and the integration
     test fixture (`mounted_lex_lemmas` + `production_pool` builders) to
     include lexicon `surface` forms in the resolution set — already the
     operational meaning of "a lemma in another mounted pack" since
     surfaces are canonical inflections of the same lemma.
  2. Adding 10 inflected `LexicalEntry` rows across cognition / meta /
     action / spatial lexicons (e.g. `surface=knows lemma=know`,
     `surface=parts lemma=part`) so morphology-shifted atoms resolve.

Live surface verification (sample 6 prompts):

  before                                          after
  "what a person know from truth and evidence" -> "...knows from..."
  "what a person recall"                       -> "...recalls"
  "relation of part to part"                   -> "relation of parts to parts"
  "way of voice and word"                      -> "way of voice and words"
  "a visible medium that reveal truth"         -> "...reveals truth"
  "what a cause make"                          -> "what a cause makes"

Verification (all gates from brief Phase 4):
  - closure verifier: 0 unresolved / 0 mismatches on all ADR-0084 packs
    (remaining domain-pack red is PR #97 follow-up — addressed by PR #99)
  - ADR-0084 integration test: 30/30
  - cognition eval: byte-identical to baseline
  - packs lane: 6/6
  - smoke lane: 67/67

Files touched: 5 gloss files (cognition / causation / meta / attitude /
spatial), 4 lexicon files (cognition / meta / action / spatial), 5 manifest
checksum refreshes (+ action), 1 verifier code change, 1 integration test
fixture extension, 1 deterministic-pack-entry-id test bump (085→091).
2026-05-20 23:12:28 -07:00
Shay
3d922a1532
Add chain-first capability ledger and domain seeds (#97) 2026-05-20 21:33:24 -07:00
Shay
2a2ef9ce49
perf(salience): vectorize curvature pairwise loop — 57× faster, 42% e2e (#96)
cProfile attribution (2026-05-21) identified
``core.physics.salience.SalienceOperator.compute`` as 64% of total
``ChatRuntime.chat()`` time.  Pre-fix it was a nested Python loop
over ``regions × regions`` with one ``np.linalg.norm`` call per
pair.  For N≈500 mounted-vocab regions per turn that meant ~250k
norm calls per turn, dominating end-to-end latency.

Fix: numpy broadcast for pairwise displacement, distance,
pressure-delta, and contribution.  Same math; same contract.
ULP-level reassociation drift is absorbed by the 12-decimal
precision ``_salience_address`` already used for content
addressing, and by the float32 conversion at the downstream
``SalienceMap.scores_arr`` site, so neither the content_address
nor the top-k ordering changes.

Measurements (region set: N=493, dim=5, seeded):

  vectorized:  11.78 ms/call
  old-loop:   672.30 ms/call
  speedup:    57.1×

End-to-end on 8 cognition-shape prompts:

  pre-fix:  ~970 ms/turn
  post-fix:  565 ms/turn   (-42%)

Validation:

  * 15 new tests in ``tests/test_salience_vectorize_parity.py``:
      - parity with a nested-loop reference to 1e-9 absolute on
        curvature_magnitude, gradient_vector, influence_radius
        across N ∈ {1, 2, 8, 32, 128, 493}
      - content_address byte-identical across N ∈ {1, 8, 32, 128}
      - top-16 ordering matches the reference at N ∈ {32, 128, 493}
      - empty regions returns empty map
      - single region has zero curvature
  * ``core eval cognition`` byte-identical: public 100/100/91.7/100.
  * ``core test --suite cognition`` 120/0/1, ``smoke`` 67/0.

The file's pre-existing docstring promised a Rust path
(``core_rs::physics::salience::compute_curvature``) that does not
yet exist — the numpy vectorization realizes the lift now while
keeping the Rust port a future optimization on stable semantics
(CLAUDE.md: "Rust backend parity only after Python semantics are
locked by tests").
2026-05-20 21:29:42 -07:00
Shay
a36b48b198
feat(runtime): opt-in unified-ingest path (ADR-0090, audit Findings 6+7) (#95)
Closes audit Findings 6 (within-turn recall not batched) and 7
(probe-ingest / commit-ingest dual field) as a single PR — the two
are architecturally entangled and resolve together.

Pre-fix flow in ``ChatRuntime.chat()``:

  1. ``probe_ingest(filtered)`` → ``probe_state.F``
  2. Gate check on ``probe_state.F``
  3. If gate fires: ``commit_ingest`` + stub response
  4. Otherwise: ``commit_ingest`` + drive bias → ``field_state.F``
  5. Walk runs on ``field_state.F``

The gate observes one manifold position; the walk navigates a
slightly different one (drive bias applied between them).  Honest
refusal decisions and walk outputs are made on different fields —
the audit's named coherence gap.

This PR ships a flag-gated unified-ingest path following the
codebase's standard substantive-change pattern (ADR-0046 /
ADR-0062 / ADR-0085 / ADR-0088 / ADR-0089):

``RuntimeConfig.unified_ingest: bool = False`` (default).

When ``True``:

  1. ``commit_ingest(filtered)`` runs first.
  2. Drive bias applied immediately.
  3. Gate observes ``committed.F``.
  4. If gate fires: stub response (turn has already committed —
     intentional semantic change documented in ADR-0090).
  5. Otherwise: walk runs on the same ``committed.F`` the gate
     decided against — no second ``commit_ingest`` call.
  6. ``probe_ingest`` is not called on this path.

When ``False`` (default): historical behavior is preserved
bit-for-bit; ``probe_ingest`` still runs first.

ADR-0090 documents:

  * Phase 1 (this PR): unified-ingest substrate.
  * Phase 2 (separate PR, after Phase 1 validates): batched recall
    — pass the gate's ``direct_hits`` into ``generate()`` as a
    ``prebuilt_first_recall`` so the walk's first step does not
    re-call ``vault.recall()`` on the same field.  Single recall
    call eliminated per turn.
  * Out of scope: ``recall_batch`` for per-step walk recalls
    (each step's query depends on the previous step's field
    state; not batchable without changing walk geometry).

Validation:

  * 5 new tests in ``tests/test_unified_ingest_null_lift.py``:
      - flag defaults to ``False`` on ``DEFAULT_CONFIG``
      - flag-off surface + trace_hash + vault_hits byte-identical
      - flag-on does not call ``probe_ingest`` (verified via spy)
      - flag-on produces well-formed surface + trace_hash
      - flag-off still calls ``probe_ingest`` (historical guard)
  * ``core eval cognition`` byte-identical across all three splits:
    public 100/100/91.7/100, dev 100/100/78.6/100, holdout
    100/100/83.3/100.
  * ``core test --suite cognition`` 120/0/1, ``smoke`` 67/0,
    ``runtime`` 19/0.

Comb-pass status after this PR:

  * Item 4 (graph topo) ✓ #92
  * Item 5 (realizer node_map) ✓ #91
  * Item 6 (batch recall) ✓ ADR-0090 substrate (this PR); Phase 2
    optimization is queued
  * Item 7 (probe/commit dual ingest) ✓ ADR-0090 (this PR)
  * Item 8 (dead defensiveness sweep) ✓ #91
  * Item 9 (local imports) ✓ #91
  * Item 11 (dead ``_fold_compose_into_surface``) ✓ #91
  * Item 13 (``_serialize_*`` fold) ✓ #91
  * Item 15 (GenerationResult tuple/list) ⊘ false positive
  * Item 16 (subject normalization consistency) ✓ #93
  * Item 17 (redundant ``^`` anchors) ✓ #94
  * Tier 5 minor (``_BE_FORMS`` hoist, walrus, reverse-iter) ✓ #94
2026-05-20 21:00:27 -07:00
Shay
ef7d59287b
rigor(intent): consistent subject normalization across all classifier paths (#93)
Comb pass 2026-05-21 (item 16).

Pre-fix ``classify_intent`` applied ``_normalize_subject`` only to
DEFINITION / CAUSE / VERIFICATION paths.  COMPARISON, FRAME_TRANSFER,
TRANSITIVE_QUERY (non-"means" branch), and BELONG_QUERY returned
bare ``.strip()`` subjects.  A probe like *"Compare the parent and
a child"* would carry the articles ("the parent", "a child") into
the subject slot, breaking downstream pack-resolver lookups that
key on bare lemmas.

Fix: apply ``_normalize_subject(..., IntentTag.DEFINITION)`` at every
classifier return site that was previously bare ``.strip()``.
DEFINITION mode preserves multi-word noun phrases (only strips
leading articles + trailing punctuation + infinitive markers); the
aux-verb stripping that's only meaningful for CAUSE/VERIFICATION
stays scoped to those paths.

Sites fixed (5):

  * COMPARISON subject + secondary_subject
  * FRAME_TRANSFER subject + frame
  * TRANSITIVE_QUERY subject (both the regular and "means" → DEFINITION
    redirect branches now share one normalized binding)
  * BELONG_QUERY subject

Behavior:

  * Eval cases without articles (the entirety of cognition v1) are
    byte-identical: ``"memory"`` and ``"recall"`` survive
    ``_normalize_subject`` unchanged.
  * Multi-word noun phrases survive intact: ``"artificial
    intelligence"`` is preserved (no aux-verb-strip wrongly trimming
    to head-noun).
  * Article-prefixed subjects ("the parent") now strip consistently
    with the DEFINITION path that's done so since ADR-0049.

Validation:

  * 7 new tests in
    ``tests/test_intent_subject_normalization_consistency.py``
    pin the consistency contract across COMPARISON, FRAME_TRANSFER,
    TRANSITIVE_QUERY, BELONG_QUERY, DEFINITION (regression guard
    on the pre-existing path), and CAUSE (regression guard on the
    aux-verb-strip behavior).
  * ``core eval cognition`` byte-identical across all three splits:
    public 100/100/91.7/100, dev 100/100/78.6/100, holdout
    100/100/83.3/100.
  * ``core test --suite cognition`` 120/0/1, ``smoke`` 67/0.
  * ``pytest -k intent`` 229/0.
2026-05-20 20:44:19 -07:00
Shay
548282fadc
perf(graph): PropositionGraph.topo_order — Kahn's O(N+E) instead of O(N×E) (#92)
Comb pass 2026-05-21 (item 4).

Pre-fix the topological-sort implementation in
``PropositionGraph.topo_order`` had two compounding inefficiencies:

  * ``queue.pop(0)`` on a list is O(N) per pop → O(N²) total
  * The inner ``for e in self.edges`` rescanned all edges on every
    iteration → O(N × E) overall

This is invisible on today's 1–2 node production graphs but would
become a real regression the moment compound-intent multi-node
dispatch (ADR-0089 Phase C2) or the grounded realizer's multi-clause
output (ADR-0088 Phase B follow-up) lands.

Fix: standard Kahn's with a precomputed out-edge adjacency map and
a ``deque`` for the work queue.  O(N + E) overall.  Deterministic
output preserved — the queue is seeded with sorted zero-in-degree
nodes (identical to the pre-fix list sort), and direct-successor
order matches edge-iteration order (identical when edges retain
insertion order).

Pinned by 6 new tests in ``tests/test_graph_topo_order_perf.py``:

  * single-node graph (today's production shape) byte-identical to
    pre-fix output
  * empty graph returns empty tuple
  * chain (A→B→C→D) orders root → leaf
  * diamond (A→B, A→C, B→D, C→D) keeps A first, D last, B/C between
  * three disjoint roots emit in sorted order
  * 100-node chain returns correct full order (would have been
    visibly slow under the O(N²) pre-fix algorithm)

Validation:

  * ``core eval cognition`` byte-identical (public 100/100/91.7/100)
  * ``core test --suite cognition`` 120/0/1
  * ``core test --suite smoke`` 67/0

Comb-pass note: item 15 (GenerationResult.tokens typed tuple but
assigned list) was investigated and turned out to be a Pyright
false positive — ``GenerationResult.__post_init__`` already coerces
to tuple via ``object.__setattr__``.  Contract is enforced at
runtime; only Pyright's static analyser misses the coercion site.
No fix needed.
2026-05-20 20:37:21 -07:00
Shay
fd48931838
perf(cognition): hot-path comb pass — 5 mechanical-sympathy fixes (#91)
Bundle of 5 hot-path optimizations + 1 dead-code removal + 1 import
sweep + 1 helper fold, surfaced by a comb pass through the cognitive
spine starting from ``CognitiveTurnPipeline.run()`` and walking
outward through ChatRuntime, intent classification, the graph
planner, the realizer, and the vault.  All eval lanes byte-identical
to MEMORY baseline; null-lift confirmed by ``core eval cognition``
across public / dev / holdout splits.

Hot-path fixes:

  1. ``ChatRuntime._apply_oov_policy`` no longer rescans every
     manifest per OOV token.  Two precomputed booleans on
     ``self`` capture the FAIL_CLOSED-all and PROPOSE_VOCAB-any
     aggregates at construction time.  Manifests are immutable
     post-construction so the cache is safe.  Turns the path from
     O(packs × OOV) to O(OOV).

  2. ``CognitiveTurnPipeline.run`` calls ``classify_compound_intent``
     once and takes its dominant ``compound.primary`` as the seeded
     intent.  Pre-fix the pipeline called both ``classify_intent``
     and ``classify_compound_intent`` on every turn — and
     ``classify_compound_intent`` internally invokes
     ``classify_intent`` on the dominant fragment, so every non-
     compound prompt walked the 15-regex cascade twice.

  3. ``TeachingStore.triples()`` materializes once per turn.
     Pre-fix ``_maybe_transitive_walk`` and ``_maybe_compose_relations``
     each called ``self.teaching_store.triples()`` independently,
     doubling the per-turn O(N) filter+tuple-build cost.  Both
     helpers now accept an optional ``triples`` arg; the pipeline
     computes once and passes through.

  5. ``realize_semantic`` and ``realize_target`` build a
     ``node_id → obj`` map once and look up each step in O(1)
     instead of an O(N) linear scan of ``graph.nodes`` per step.
     The cost was invisible on today's 1-2 node graphs but would
     have become an O(N²) regression on the multi-node graphs
     ADR-0089 Phase C2 plans to introduce.

Dead-code / cleanup:

  - Removed dead ``CognitiveTurnPipeline._fold_compose_into_surface``
    (no callers since PR #76 routed all surface composition
    through ``resolve_surface``).
  - Folded ``_serialize_walk`` + ``_serialize_compose`` (identical
    bodies) into one ``_serialize_operator`` helper.
  - Hoisted ``import json`` and ``RatifiedIntent`` from inside hot
    method bodies to module top (same pattern PR #76 applied to
    ``_is_useful_surface``).
  - Dead-defensiveness sweep on ``ChatResponse`` field reads in
    ``pipeline.run()``: ``getattr(response, "<field>", default)``
    where the field always exists on the dataclass with a default
    is replaced by direct attribute access (6 sites:
    ``realizer_grounded_authority``, ``recalled_words``,
    ``grounding_source``, ``register_canonical_surface``,
    ``pre_decoration_surface``, ``admissibility_trace``,
    ``region_was_unconstrained``).  ``refusal_reason`` retains the
    guarded read because ADR-0024 Phase 2 leaves its
    materialisation site dormant.

Benchmark profiler:

  - ``benchmarks/pipeline_profiler.py`` rebound from
    ``classify_intent`` to ``classify_compound_intent`` (the new
    single-classification site).  All other timing hooks unchanged.

Tests:

  - 4 new tests in ``tests/test_comb_pass_hot_path.py`` pin: OOV
    aggregates exist as bools; compound classifier runs exactly
    once per turn; ``triples()`` materializes exactly once per
    turn; realizer correctly resolves obj slots across an 8-node
    graph.
  - All existing tests pass.  ``core eval cognition`` byte-identical:
    public 100/100/91.7/100, dev 100/100/78.6/100, holdout
    100/100/83.3/100.
  - ``core test --suite cognition`` 120/0/1, ``smoke`` 67/0,
    ``runtime`` 19/0.
2026-05-20 20:31:56 -07:00
Shay
de3f40b549
feat(cognition): opt-in grounded-realizer authority flag (ADR-0088 Phase B) (#88)
Closes audit Finding 2 (2026-05-20) — Phase B substrate.

Pre-fix ``CognitiveTurnPipeline.run()`` invoked ``realize_semantic``
on the ungrounded ``PropositionGraph``.  Every non-COMPARISON /
non-CORRECTION node was born with ``obj = "<pending>"`` and the
realizer emitted surfaces like ``"X is defined as ..."`` that
``_is_useful_surface`` correctly rejected.  The realizer therefore
never won the surface resolver introduced by PR #76 — it was
structurally present but semantically inert in the hot pipeline
path.

This PR follows the codebase's standard substantive-change pattern
(ADR-0046 ``forward_graph_constraint``, ADR-0062 ``composed_surface``,
ADR-0083 ``transitive_surface``, ADR-0085 ``gloss_aware_cause``):
ship the wiring behind a flag, default ``False``, with a CI-pinned
null-lift invariant.

Changes:

  * ``RuntimeConfig.realizer_grounded_authority: bool = False`` —
    operator-level opt-in.
  * ``ChatResponse.recalled_words: tuple[str, ...] = ()`` —
    alphabetic-filtered walk tokens from the recall step, populated
    on the main path of ``ChatRuntime._chat``.  ``walk_tokens`` is
    now computed unconditionally so non-English packs also surface
    them (English keeps using them for
    ``articulate_with_intent`` as before).
  * ``CognitiveTurnPipeline.run()`` — when the flag is set and the
    response carries any recalled words, calls
    ``ground_graph(graph, response.recalled_words)`` and re-invokes
    ``realize_semantic`` on the grounded graph.  The surface
    resolver (PR #76) then picks the realizer's grounded output
    when it clears ``_is_useful_surface`` and the unknown-domain
    gate did not fire.

Phase A (realizer fluency parity — gloss-aware templates, 3sg verb
agreement, pack-provenance tag) is documented in ADR-0088 §Phase A
and is the prerequisite for enabling this flag in production.  The
known fluency gap (e.g. ``"Light is a visible medium that reveal
truth"`` — subject-verb disagreement leaking from realizer
templates) is the reason the flag ships default-off: operators get
the wiring stable now, the realizer becomes a real authority once
Phase A's fluency upgrade lands.

Verification:

  * 4 new tests in ``tests/test_realizer_grounded_authority_flag.py``:
      - flag defaults to ``False`` on ``DEFAULT_CONFIG``
      - flag-off produces byte-identical surface + trace_hash
        (null-lift invariant)
      - ``recalled_words`` is populated on the main path
      - flag-on runs end-to-end without crashing (surface is
        well-formed regardless of which authority won the resolver)
  * ``core eval cognition`` — public 100/100/91.7/100,
    byte-identical to the MEMORY baseline (default-off).
  * ``core test --suite cognition`` — 120/0/1.
  * ``core test --suite smoke`` — 67/0.
  * ``core test --suite runtime`` — 19/0.
2026-05-20 20:00:58 -07:00
Shay
133a1a3e1c
feat(cognition): compound-intent observability substrate (ADR-0089 Phase C1) (#89)
Closes audit Finding 4 (2026-05-20) — Phase C1.

Pre-fix ``CognitiveTurnPipeline.run()`` called only the single-intent
``classify_intent`` and silently dropped every secondary clause of a
compound prompt like *"What is X and how does it relate to Y?"*.
The graph never saw the second subject, the resolver never saw the
second clause, and the trace recorded only the dominant clause —
with no operator-visible evidence that anything was dropped.

Phase C1 is the **observability substrate** for ADR-0089: the
pipeline now also runs ``classify_compound_intent`` at step 1b and
records every dropped secondary clause on
``CognitiveTurnResult.dropped_compound_clauses``.  The dominant
clause continues to route through the existing single-intent path
exactly as before — surfaces, trace_hashes, and every existing test
remain byte-identical.

Changes:

  * ``CognitiveTurnPipeline.run()`` calls ``classify_compound_intent``
    alongside the existing ``classify_intent`` and computes
    ``dropped_compound_clauses = compound.parts[1:]`` when the
    compound is multi-part.
  * ``CognitiveTurnResult.dropped_compound_clauses:
    tuple[DialogueIntent, ...] = ()`` — empty tuple == single-clause
    turn; len > 0 == operator-visible evidence of dropped secondary
    clauses.

Out of scope (per ADR-0089):

  * Phase C2 (opt-in multi-node graph dispatch + widened trace_hash
    + multi-clause surface) is deliberately scoped to a separate
    PR because it widens ``compute_trace_hash``, the surface
    resolver contract, and ``plan_articulation``.
  * The dominant-clause routing path is unchanged: the audit's
    broken-subject case ("truth, and why does it matter") is *not*
    fixed here — that improvement is Phase C2 scope.

Verification:

  * 4 new tests in ``tests/test_compound_intent_substrate.py``:
      - single-clause prompts record empty
        ``dropped_compound_clauses``
      - AND-joined compound surfaces the secondary clause as a
        DialogueIntent with the right tag (CAUSE for "why does ...")
      - the user-visible surface and trace_hash for a compound prompt
        are byte-identical across two independent runs (no behavior
        change at the truth-path layer)
      - prompts without a recognised connector do not invent a
        secondary clause
  * ``core eval cognition`` — public 100/100/91.7/100, byte-identical
    to the MEMORY baseline.
  * ``core test --suite cognition`` — 120/0/1.
  * ``core test --suite smoke`` — 67/0.
  * ``core test --suite runtime`` — 19/0.
2026-05-20 19:59:38 -07:00
Shay
401ae53328
chore(generate): make stop-tokens caller-overridable via RuntimeConfig (#87)
Closes audit Finding 6 (2026-05-20).

Pre-fix ``_STOP_TOKENS = frozenset({"it", "to", "word"})`` was
hardcoded inside ``generate.stream.generate()`` and inhibited those
three tokens unconditionally across every pack, every language, and
every domain.  If a pack legitimately needed one of them as a content
word — e.g. a philosophy pack where ``"word"`` maps to λόγος, or a
syntax pack where ``"to"`` is a content node — there was no override
path.  The ``_try_index`` guard handled the case where the token was
absent from the pack, but offered nothing for packs that contained
the token and meant it.

Changes:

  * ``generate.stream.generate`` accepts ``stop_tokens: frozenset[str]
    | None = None``.  ``None`` resolves to the historical
    ``_STOP_TOKENS`` constant, preserving byte-identity for every
    pre-Finding-6 caller.
  * ``RuntimeConfig.stop_tokens: tuple[str, ...] | None = None`` —
    operator-level override threaded through ``ChatRuntime`` into
    ``generate()``.
  * Default ``None`` preserves byte-identical behavior for every
    existing pack and every existing test.

Scope notes:

  * This PR delivers the *runtime override* surface.  Manifest-driven
    per-pack overrides (``generation_stop_tokens`` field in the pack
    manifest) are the natural next step but require a pack-schema
    ADR and re-ratification of every affected pack, so the wiring
    lands first and the manifest field follows on a separate ADR.
  * ``agenerate`` was identified as unreachable and is being deleted
    in a sibling PR (Finding 7); its hardcoded ``_STOP_TOKENS``
    reference disappears with it, so it is intentionally not touched
    here.

Verification:

  * 4 new tests in ``tests/test_stop_tokens_override.py``:
      - ``RuntimeConfig.stop_tokens`` defaults to ``None``
      - ``generate()`` signature exposes ``stop_tokens`` with default
        ``None``
      - the historical constant is unchanged
      - an explicit override flows through the runtime end-to-end
  * ``core eval cognition`` — public 100/100/91.7/100, byte-identical
    to the MEMORY baseline.
  * ``core test --suite cognition`` — 120/0/1.
  * ``core test --suite smoke`` — 67/0.
  * ``core test --suite runtime`` — 19/0.
2026-05-20 19:59:33 -07:00
Shay
e41a14f76c
chore(ratifier): calibrate default ratification threshold 0.0 → 0.5 (#86)
Closes audit Finding 3 (2026-05-20).

Pre-fix ``ratify_intent`` defaulted to ``threshold=0.0``, which admits
anything with non-negative ``cga_inner(prompt, anchor)`` — the field
gate (ADR-0022 §TBD-1) was structurally live but semantically
transparent.  RATIFIED was logged on essentially every turn because
the CGA inner product over conformal space is not sign-symmetric.

Measurement (``scripts/calibrate_ratification_threshold.py``):

  * Runs every cognition eval prompt (45 cases = 13 public + 13 dev +
    19 holdout) through a primed ``CognitiveTurnPipeline``.
  * Captures the actual ``cga_inner(prompt, anchor)`` score from the
    pipeline's own ``_ratify_intent`` via a temporary spy on the
    imported ``ratify_intent`` binding.

Observed distribution:

  * 34 RATIFIED:  min=+1.1039  p10=+1.1039  median=+2.6820  max=+5.7508
  * 11 PASSTHROUGH (no vocab-grounded anchor available; score=0.0)
  *  0 DEMOTED at any threshold ≤ 1.10

Threshold = 0.5 chosen as the calibrated default:

  * Well below the empirical floor of 1.10 — every currently-passing
    case stays RATIFIED, byte-identically.
  * Clearly non-trivially positive — random Cl(4,1) inner products
    fluctuate around zero, so 0.5 demands genuine correlation with
    the anchor rather than passive non-negativity.
  * Leaves headroom for the gate to actually demote weakly-aligned
    off-corpus / adversarial prompts to UNKNOWN and route them
    through the honest-refusal surface.

Verification:

  * ``core eval cognition`` — public 100/100/91.7/100, holdout
    100/100/83.3/100, dev 100/100/78.6/100 — byte-identical to
    MEMORY baselines.
  * ``core test --suite cognition`` — 120/0/1
  * ``core test --suite smoke`` — 67/0
  * ``core test --suite runtime`` — 19/0
  * 2 new tests in ``tests/test_ratification_threshold_default.py``
    pin both the constant and the signature default so a future
    change cannot silently regress to ``0.0``.
2026-05-20 19:59:25 -07:00
Shay
4f9e00a6a5
fix(cognition): bound speculative-subject cache + evict on COHERENT promotion (#85)
Closes audit Finding 5 (2026-05-20).

Pre-fix ``CognitiveTurnPipeline._speculative_subjects`` was a bare
``set[str]`` that only grew over a session.  Two correctness gaps:

  * A subject promoted to ``EpistemicStatus.COHERENT`` via the teaching
    review loop kept appearing with the "(speculative, not yet
    reviewed)" marker forever, contaminating reviewed material on
    later probes.
  * Long teaching sessions widened the per-turn substring scan in
    ``_should_mark_speculative`` without bound.

Fix:

  * Back the cache with ``OrderedDict[str, None]`` (LRU) capped at
    ``_MAX_SPECULATIVE_SUBJECTS = 64``.
  * Introduce ``_remember_speculative_subject`` (insert / refresh) and
    ``_forget_speculative_subject`` (evict) helpers; route all
    SPECULATIVE inserts through them.
  * When a proposal lands as ``EpistemicStatus.COHERENT``, evict the
    subject and every long-enough non-stopword token derived from it,
    so the marker stops appearing on reviewed material.

Iteration order in ``_should_mark_speculative`` is unchanged (keys
view); lookups remain O(1).  No surface change for any case the prior
behavior didn't already mishandle, so byte-identical eval surfaces
stay stable (verified locally against ``core eval cognition`` public /
holdout / dev splits — all unchanged from MEMORY baseline).

Tests (7 new, ``tests/test_speculative_subject_lifecycle.py``):

  * storage is an OrderedDict and the cap is 64
  * remember normalizes (lower+strip) and drops empty input
  * remember refreshes LRU position on re-insert
  * cache caps at 64 with insertion-order eviction
  * forget is case-insensitive and removes the entry
  * forget on a missing / empty subject is a no-op
  * ``_should_mark_speculative`` triggers after remember and stops
    triggering after forget

Audit findings referenced:
https://github.com/AssetOverflow/core/pull/76 (Finding 5, "Unbounded
``_speculative_subjects``")
2026-05-20 19:59:21 -07:00
Shay
ff1dcb2594
fix(cognition): declare surface authority resolution (#76)
* fix(cognition): add explicit surface resolution policy

* test(cognition): cover explicit surface resolution policy

* fix(cognition): route pipeline surfaces through resolver

* fix(cognition): address PR #76 review comments

- hoist `_is_useful_surface` import from inside `run()` to module top
- call `_render_walk_surface` / `_render_compose_surface` via the class
  name (both are @staticmethod) for consistency with the existing
  `_fold_*_into_surface` helpers
- drop redundant `realized_surface` truthiness check in
  `resolve_surface` — `realizer_useful` already excludes empty /
  placeholder surfaces via `_is_useful_surface`

Tests: tests/test_surface_resolution.py + tests/test_cognitive_turn_pipeline.py
green (16 passed); cognition suite 120/1s, smoke suite 67/0.
2026-05-20 19:42:10 -07:00
Shay
0cf54a009d
feat(adr-0087): rhetorical-style pack substrate (loader + default_unstyled_v1) (#74)
Substrate-only code-side for ADR-0087 (Rhetorical Style as Selection
Axis). No composer or realizer touches the new pack yet; consumer
integration is the follow-up ADR.

packs/rhetorical_style/ (new)
  - loader.py: RhetoricalStylePack frozen dataclass + load_rhetorical_
    style_pack() with fail-closed mastery-report self-seal verification
  - __init__.py: re-exports (RhetoricalStylePack, RhetoricalStylePack-
    Error, load_rhetorical_style_pack, DEFAULT_RHETORICAL_STYLE_PACK)
  - default_unstyled_v1.json + .mastery_report.json: ratified null-lift
    baseline pack (all three constraint lists empty,
    default_unstyled=true)

scripts/ratify_rhetorical_style_packs.py (new)
  - Mirror of scripts/ratify_anchor_lens_packs.py for the rhetorical-
    style pack family. Computes pack_source_sha256 with mastery_report_
    sha256 blanked, builds self-sealed mastery report, writes both
    files. Idempotent. Uses formation.hashing for canonical JSON +
    self_seal.

Schema gate (ADR-0087 §Verification)
  - Required keys allow-list: pack_id, schema_version, version,
    issued_at, default_unstyled, permitted_frames,
    required_moves_per_claim, forbidden_moves, provenance,
    mastery_report_sha256
  - Unknown keys rejected (strict gate)
  - permitted_frames: allow-list {warrant, concession, hedge,
    definitional_move}
  - required_moves_per_claim / forbidden_moves: allow-list {claim,
    evidence, warrant, concession, hedge, bare_assertion, definitional}
  - default_unstyled=true ⟺ all three lists empty
  - non-default pack must declare at least one constraint (distinguishes
    from null-lift)
  - Duplicates within a list rejected

Ratification gate
  - require_ratified=True by default
  - CORE_ALLOW_UNRATIFIED_RHETORICAL_STYLE=1 env-var bypass for dev
  - Companion mastery report SHA must match pack's declared sha
  - verify_seal(report) must pass (self-seal integrity)
  - Sister to packs.safety.SafetyPackError pattern

core/config.py
  - Added RuntimeConfig.rhetorical_style_id: str | None = None
  - No runtime code reads it yet — that's the consumer ADR's job
  - Field declared so the interface is stable when consumer lands

Tests (tests/test_adr_0087_rhetorical_style_substrate.py — 20)
  - Default pack loads, is_null_lift, mastery-report self-seal verified,
    discovery lists it as ratified
  - Schema gate: missing key, unknown key, unknown frame, unknown move,
    duplicate frame, default_unstyled-with-constraints,
    non-default-with-zero-constraints, pack_id mismatch, path traversal
  - Ratification gate: unratified pack rejected by default, env-var
    bypass, companion report missing, companion sha mismatch
  - RuntimeConfig field: default None, accepts string, independent of
    other axes

Lanes
  smoke 67/0, cognition 120/0/1, packs 6/0. core eval cognition
  byte-identical 100/91.7/100/100.

Null-lift consumer test deferred
  ADR-0087 §Required tests lists rhetorical_style_null_lift as a
  required invariant. Today it would be trivially true because no
  composer reads the field. The invariant becomes meaningful when
  the consumer ADR wires the field through the dispatch — at that
  point the null-lift test goes into the consumer PR alongside the
  three-axis orthogonality test.

Scope per ADR-0087 §Scope limits
  - No consumer code (composer/realizer changes deferred)
  - No genre packs (en_academic_v1, etc. are content efforts after
    consumer lands)
  - No prompt-routing (operator-set only)
2026-05-20 16:19:36 -07:00
Shay
4b9404a88e
feat(adr-0085): gloss-aware CAUSE composer — explanation frame from glosses (#70)
The original "Why does light exist?" complaint that motivated ADR-0084
was specifically about CAUSE-intent surfaces. ADR-0084 (substrate) +
PR #65 (content) already moved DEFINITION/RECALL to gloss-grounded
surfaces ("Light is visible medium that reveal truth."). But CAUSE
still dispatched through the chain-walk path:

  Before: light — teaching-grounded (cognition_chains_v1):
            cognition.illumination; logos.core.
            light reveals truth (cognition.truth).
            No session evidence yet.

  After:  Light exists as visible medium that reveal truth.
          pack-grounded (en_core_cognition_v1).

The chain-walk is structurally correct but the wrong SHAPE for a why-
question — it's a graph traversal, not an explanation. ADR-0085 fixes
the shape using the same gloss material that DEFINITION/RECALL already
consume, with no new content authoring.

Additive composer
  chat/pack_grounding.py:gloss_aware_cause_surface()
  - Resolves gloss via lexicon-residency-checked resolve_gloss().
  - Frames POS-aware:
      NOUN -> "{Lemma} exists as {gloss}."
      VERB -> "To {lemma} is to {gloss}."
      ADJ  -> "To be {lemma} is to {gloss}."
      *    -> falls back to _frame_gloss (predicate-identity).
  - Threads anchor lens via the existing helper (ADR-0073c parity).
  - Returns None when no gloss exists — runtime falls through to the
    existing chain-walk path. Additive: no CAUSE case loses its surface.

Runtime dispatch
  chat/runtime.py — IntentTag.CAUSE tries gloss path FIRST under the
  flag; falls through to teaching_grounded_surface* on None.
  Unconditional fallback — never silent.

Opt-in flag
  core/config.py — RuntimeConfig.gloss_aware_cause: bool = False
  Default off preserves pre-ADR-0085 chain-walk surfaces byte-
  identically (null-drop invariant, CI-pinned).

Prompt-diversity classifier update
  evals/prompt_diversity/runner.py — _CAUSE_MARKERS widened with the
  explanation-frame markers ("exists as", "is to", "to be", "is for",
  "purpose of") plus bare-form predicates ("reveal" alongside
  "reveals"). Neither composer path is penalised on shape_fit just on
  inflection grounds.

v1/public lift (flag OFF vs ON, 26 cases)
  intent_accuracy        : 65.4% -> 65.4%   ( — )
  versor_closure_rate    : 100.0% -> 100.0% ( — )
  response_shape_fit     : 57.7% -> 57.7%   ( — , both frames recognized)
  audit_in_surface_rate  : 42.3% -> 42.3%   ( — , envelope ADR's job)
  gloss_quote_rate       : 11.5% -> 23.1%   (+11.5pp, structural lift)

Tests (15)
  - 5 pure composer (NOUN/VERB frame, unknown/empty None, no chain-
    walk artifacts in surface)
  - 5 runtime dispatch (flag-off chain-walk, flag-on gloss, parametrized
    across glossed subjects, VERIFICATION unchanged under flag, no-
    gloss fallback engages)
  - 5 cognition lane invariance (aggregate metrics byte-identical
    under both flag states; surfaces deliberately shift on the 2 CAUSE
    cases with glossed subjects — the structural-change-vs-metric-
    invariance both-sides invariant)

Lanes
  smoke 67/0, cognition 120/0/1 skipped, packs 6/0, teaching 17/0,
  runtime 19/0. core eval cognition byte-identical 100/91.7/100/100
  under both flag states.

Scope limits (per ADR §Scope limits)
  - CAUSE only; VERIFICATION still chain-walks (different shape).
  - English pilot only; Greek/Hebrew packs not opted into definitional
    layer yet (ADR-0084 scope limit).
  - Single-lemma subjects; compound/anaphoric fall through.
  - Opt-in until cognition holdout confirms the lift transfers off-
    fixture. Future PR flips default on.

Out of scope
  - Surface-vs-envelope cleanup ("pack-grounded (...)" still leaks).
  - Predicate licensing (ADR-0086).
  - Content style pass (bare lemma forms in glosses — separate brief).
2026-05-20 15:55:08 -07:00
Shay
6b0d723987
fix(evals): prompt_diversity gloss-quote heuristic — 4-token window → substring (#69)
The v1 gloss-quote detector used a 4-token contiguous window of
≥4-char tokens.  That heuristic was too strict for the actual ADR-0084
brief gloss style, which is deliberately short and primitive-only:

  light    "visible medium that reveal truth"   5 tokens ≥4 chars
  parent   "person with a child"                3 tokens ≥4 chars   ← can't window
  recall   "get memory from before"             3 tokens ≥4 chars   ← can't window
  wisdom   "good use of knowledge"              2 tokens ≥4 chars   ← can't window

Result: post-PR #65 baseline showed gloss_quote_rate=0.0% even though
the pack-grounded composer was visibly emitting glosses verbatim:

  surface: "Parent is person with a child. pack-grounded (en_core_relations_v1)."
  gloss:   "person with a child"
  window:  could not even form

Replace with substring match against the gloss text.  The composer
emits the gloss verbatim (no paraphrasing — that's the no-LLM
discipline), so substring is exact, high-confidence, and trivially
correct:

  gloss_quoted ⟺ gloss.lower().strip() in surface.lower()

Re-baselined v1/public (26 cases):
  gloss_quote_rate: 7.7% (false-positive 4-token window noise)
                  → 0.0% (post-#65, broken metric)
                  → 11.5% (this PR, real signal)

The other four metrics unchanged.  3/26 cases (DEFINITION on
``evidence``/``recall``/``parent``) are detected as gloss-quoted now,
which matches reality — the pack-grounded composer at
chat/pack_grounding.py:398 has been gloss-aware all along; it just
had no glosses to quote pre-#65.

Why this is just a heuristic refinement, not a contract change:

The contract.md still says v1 has NO pass thresholds beyond
versor_closure_rate==1.00.  The lane's job is to establish baseline
distribution.  The heuristic was *measuring the wrong thing* — fixing
the measurement is a contract clarification, not a contract change.

Tests added (TestGlossQuote, 4 cases):
  - short brief-style gloss detected via substring
  - chain-walk surface for same lemma NOT counted as gloss-quoted
  - unknown term returns False
  - empty terms returns False

Updated the function docstring with the post-#65 context so future
readers understand why v1's contract predicted 0% but reality is ~12%.
2026-05-20 15:43:01 -07:00
Shay
1938aaa674
test(adr-0084): integration test pins substrate gate against ratified content (#68)
After PR #64 (substrate) and PR #65 (content) both landed on main, this
test is the promised follow-up that exercises the substrate-callable
verify_definitional_closure against the real ratified content rather
than fixture packs. It pins three contracts:

  1. Substrate-vs-content handshake. The standalone
     scripts/verify_definitional_closure.py is the agent's dev-loop
     tool; this test is the gate-callable equivalent the ratification
     pipeline can invoke. Both must agree on what passes — divergence
     is a contract bug.

  2. Content drift catcher. Any future content edit that adds an
     unresolved token / non-mounted dependency / silent staging leak
     fails this test before the edit lands on main.

  3. Staging exclusion. en_minimal_v1 is staging per the ADR-0084
     pack-content brief and must not be load-bearing for the closure
     rule. Test-pinned via a production-pool subtest.

Substrate fix: allow empty definitional_atoms

The substrate's strict parser previously rejected empty
definitional_atoms. That stance was wrong: per the ADR-0084 pack-
content brief, the per-entry atom list excludes articles, prepositions,
and copulas. A gloss whose every content word is a function word
(e.g. en_core_temporal_v1/prior → "before") has zero content atoms by
construction. The closure rule passes vacuously when atoms is empty
— there is nothing to close. The gloss-vs-atoms mismatch check in
the standalone verifier is the second-layer gate that distinguishes
by-construction emptiness (legitimate) from by-omission emptiness
(laziness). Substrate parser shouldn't double-gate the same concern.

The corresponding substrate test flipped from
test_empty_definitional_atoms_rejected to
test_empty_definitional_atoms_accepted, with comment explaining the
reasoning.

Primitives expansion: can + action

Two content entries (en_core_cognition_v1/person → "who can know and
do" and en_core_meta_v1/intend → "decide before an action") leaned on
'can' and 'action' as atom references. Today those lemmas resolve
ONLY via en_minimal_v1/lexicon.jsonl — the staging pack. That's a
production-vs-staging leak: production content should not be load-
bearing on staging.

Two clean alternatives:
  (a) rewrite the two glosses to avoid 'can' and 'action'
  (b) promote 'can' and 'action' to primitives

Chose (b): both lemmas are genuinely terminal-feeling (can is a basic
capability modal; action is an irreducible "what is done"); the
content reads more naturally with them present than with paraphrased
substitutes; and the floor was always going to need both eventually.
The cost is two primitives.jsonl rows + checksum + count bump.

Verification:
  scripts/verify_definitional_closure.py            exit 0
  tests/test_adr_0084_integration_closure.py        30/30 pass
  tests/test_adr_0084_definitional_substrate.py     39/39 pass
  core test --suite smoke -q                        67/67
  core test --suite packs -q                         6/6
  core eval cognition                               byte-identical
                                                    (100/91.7/100/100)

Two-layer gate now in place:
  - standalone verifier (dev loop, gloss/atom mismatch check)
  - substrate verifier (ratification gate, parametrized over every
    opted-in pack, staging-exclusion test, primitives floor coverage)
2026-05-20 15:35:37 -07:00
Shay
48282eef8d
feat(adr-0084): definitional layer — proposal + substrate (schema/loader/closure) (#64)
* docs(adr-0084): propose definitional layer + prompt-diversity suite

Three companion artifacts proposing the next substantive design step
after ADR-0083:

1. ADR-0084 (Proposed) — Definitional Layer for Lexicon Packs
   Optional `definition` block on pack entries: gloss,
   definitional_atoms, predicates_invited, definition_version,
   provenance.  Pack-level opt-in.  Closure rule: every word in a
   gloss must resolve to a same-pack lemma, another mounted pack's
   lemma, or a primitive in a new `packs/primitives/` pack.
   NO composer change in this ADR (sequenced for ADR-0085) —
   ratify substrate before any consumer depends on it.

2. evals/prompt_diversity/ (Proposed) — companion eval lane
   ~50 cases across question-shape × sophistication × domain,
   measuring three new metrics: response_shape_fit,
   audit_in_surface_rate (quantifies the trust-boundary leak into
   user surfaces), gloss_quote_rate (zero today; rises with future
   gloss-aware composer).  No v1 pass thresholds — the lane
   establishes a baseline distribution so future work has
   something to move.  26 seed cases authored covering all 21
   categories.

3. docs/handoff/ADR-0084-pack-content-brief.md — paste-ready brief
   for a cheaper/faster dev agent to produce the pack content in
   parallel.  Self-contained, 5 sequenced phases (primitives pack
   → extend 9 existing glosses → add to relations/anchors → write
   closure verifier → run safety lanes), explicit don't-touch list
   (no composer / runtime / algebra / Greek+Hebrew packs / schema
   parser), no-LLM-glosses discipline, per-phase acceptance.

Discovery while drafting: 9 packs already carry glosses.jsonl
under language_packs/data/ with a flat schema (78 entries in
en_core_cognition_v1 alone).  The brief reflects that — most
work is extending existing entries, not authoring from scratch.

Strategic context: ADR-0083 raised the *depth* ceiling on chain
composition; ADR-0084 raises the *fidelity* ceiling.  The φ
separation probe (memory: phi-separation-falsified) established
that semantic capability lives in chain composition, not in φ
geometry, so deepening the composer's substrate is the natural
next step.  ADR-0084 → 0085 (gloss-aware composer) → 0086
(predicate licensing at ratification) is the planned sequence.

* feat(adr-0084): substrate — schema parser, primitives loader, closure verifier

Substrate-only code-side for ADR-0084 (Definitional Layer for Lexicon Packs).
No composer touches the new fields yet; consumer integration is ADR-0085.

Schema (additive, default preserves byte-identity)
  - LanguagePackManifest.definitional_layer: bool = False
  - compiler loader propagates the flag from manifest.json

language_packs/definitions.py (new)
  - GlossEntry dataclass: lemma, gloss, pos, definitional_atoms,
    predicates_invited, definition_version, provenance_ids
  - parse_gloss_entry(payload, *, strict) — strict mode enforces ADR-0084
    §Schema validation row-by-row: required keys, typed lists, no
    unknown keys, positive definition_version; lax mode preserves the
    legacy two-field shape for back-compat
  - load_pack_glosses(pack_id, *, strict) with cache + clear hook
  - verify_definitional_closure(pack_id, *, mounted_pack_lemmas,
    primitive_lemmas, strict) returning tuple[ClosureViolation, ...];
    case-insensitive resolution; cycles permitted per ADR

packs/primitives/loader.py (new)
  - Sister loader to packs/safety/ and packs/identity/
  - PrimitivesPack frozen dataclass with .lemmas frozenset
  - Gates: checksum match, kind=='primitives', definitional_layer:true,
    never_auto_mutable:true, pack_id matches dir, primitive_count
    cross-check, duplicate-lemma rejection, path-traversal rejection,
    strict per-entry schema with allow-list
  - DEFAULT_PRIMITIVES_PACK = 'en_semantic_primitives_v1'

tests/test_adr_0084_definitional_substrate.py
  - 38 tests covering strict parser (each required key rejection, unknown
    key rejection, empty predicates_invited allowed, empty
    definitional_atoms rejected, invalid definition_version), lax
    parser back-compat, load_pack_glosses (missing/strict raise/lax
    skip/malformed JSON), closure verifier (same-pack/primitive/mounted/
    unresolved/case-insensitive), primitives loader (every gate), and
    a back-compat check that every shipped pack still ratifies with
    definitional_layer=False

Lanes: smoke 67/0, cognition 120/0/1, teaching 17/0, runtime 19/0,
packs 6/0. Cognition eval byte-identical 100/91.7/100/100.

When the content PR lands (primitives.jsonl + extended glosses.jsonl
under ADR-0084-pack-content-brief.md), the gate catches any closure-rule
violation without further code change.

* feat(evals): prompt_diversity lane runner — measurement instrument for ADR-0084+

Implements the runner against the existing contract.md + 26-case v1
public split.  Lane auto-discovered by evals.framework via the standard
contract + runner convention.

Runner (evals/prompt_diversity/runner.py)
  - run_lane(cases, *, config, workers) -> LaneReport
  - 5 metrics: intent_accuracy, versor_closure_rate (carried over from
    cognition), plus the three new lane-specific metrics —
    response_shape_fit, audit_in_surface_rate, gloss_quote_rate
  - breakdown dict groups by (question_shape, sophistication, domain)
    per contract §How to read the output
  - mirrors evals.cognition.runner's parallel worker pattern

Per-shape classifier (deliberately substring/regex-simple at v1)
  - predicate_identity, explanation, sequence, two_subject_contrast,
    narrative, honest_disclosure
  - Unknown shape => neutral pass (don't penalise new categories)

Audit-leak detector
  - trust-boundary preamble markers (teaching-grounded (, pack-grounded
    (, No session evidence yet.)
  - dotted semantic-domain tag regex (cognition.illumination, etc.)

Gloss-quote detector
  - resolves expected_terms via chat.pack_resolver.resolve_gloss
  - 4-token contiguous-window match against surface (high-confidence
    "gloss actually quoted", not "shared one common word")

Tests (tests/test_prompt_diversity_runner.py — 23)
  - shape classifier parametrized over the six expected_shape values
  - audit-leak detector parametrized over preamble + tag + clean cases
  - end-to-end on v1 public:
      * versor_closure_rate == 1.0 (only v1 pass threshold per contract)
      * every metric in [0, 1]
      * breakdown groups present with the four per-cell metrics
      * diversity gate: >=5 question shapes, >=3 domains
        (defends against future regressions that collapse the suite
         back to a cognition-shaped fixture)

v1/public baseline (26 cases)
  intent_accuracy      : 65.4%   (contract predicted 70-85%)
  versor_closure_rate  : 100.0%  (only v1 pass threshold)  PASS
  response_shape_fit   : 53.8%   (contract predicted low)
  audit_in_surface_rate: 42.3%   (contract predicted ~100%)
  gloss_quote_rate     :  7.7%   (contract predicted 0%)

Three baseline surprises worth noting in the report (NOT failures —
the v1 lane is explicitly there to establish the distribution):

  - audit_in_surface_rate at 42% (not 100%) means the chain-walk leak
    fires on ~11/26; the other 15 are honest-disclosure cases that
    emit no audit envelope.  Sharpens the future surface-vs-envelope
    ADR's actual target: grounded surfaces specifically.
  - response_shape_fit at 54% (not "low") — classifier likely has
    false positives on the ", which " cause-marker.  Worth tightening
    once we have an ADR-0085 baseline to compare against.
  - intent_accuracy at 65% (below predicted 70-85%) — classifier dips
    harder on adversarial/cross-pack than expected.  Real gap.

All five smoke/cognition/teaching/runtime/packs lanes still green;
core eval cognition byte-identical 100/91.7/100/100.

* feat(packs): ADR-0084 pack content (primitives + extend glosses + closure verifier) (#65)

* feat(packs): ADR-0084 pack content

* feat(packs): repair ADR-0084 definitional content

* test(adr-0084): adjust substrate manifest tests for post-#65 content reality

PR #65 flipped definitional_layer:true on 13 English packs (9 core +
4 relations + collapse-anchors).  The substrate's previous test
test_existing_packs_unchanged asserted that en_core_cognition_v1 +
en_core_relations_v1 still had definitional_layer:False — which was
the right pre-content invariant but is wrong post-content.

Replace it with two complementary tests that hold against real content:

  - test_non_opted_packs_default_false:
      pins that packs that DIDN'T flip the flag (en_minimal_v1,
      he_core_cognition_v1, grc_logos_cognition_v1) still surface
      definitional_layer=False through the loader.  Defends against
      a future change accidentally flipping the flag on a non-opted
      pack.

  - test_opted_packs_carry_flag:
      pins that packs that DID flip the flag (en_core_cognition_v1,
      en_core_relations_v1) surface definitional_layer=True through
      the loader.  Proves the substrate's manifest-field propagation
      works against real ratified content, not just fixture packs.

Net: +1 test, same intent (substrate ratifies the manifest field
correctly), now with real-content coverage on both sides of the gate.

All 62 ADR-0084 substrate + prompt-diversity tests pass.
2026-05-20 15:25:25 -07:00
Shay
4d26e1503b
fix(tests): make frontier_compare viewer test resilient to copy refreshes (#67)
test_frontier_compare_report_viewer_exists was failing on main against
the current report_viewer.html because two verbatim substring checks
no longer matched the viewer's UI copy:

  - "Drop report JSON"  →  viewer now says "Drop JSON report" (order swapped)
  - "No network calls"  →  viewer now says "no network calls" (lowercase)

Both copy refreshes were behavior-preserving — drop-zone affordance
and network-free trust boundary are both intact in the viewer. The
test was coupling to verbatim phrasing rather than to the load-bearing
affordances.

Switch to case-insensitive substring checks that pin what actually
matters:
  - "frontier compare" — viewer identity
  - "drop" AND "json" together — drop-zone affordance, order-independent
  - "no network calls" — trust boundary (case-insensitive)
  - fetch(/XMLHttpRequest still hard-banned (case-sensitive — these
    are JS API surface, not human-readable copy)

Pre-existing failure flagged in PR #66's body as out-of-scope cleanup;
this is that cleanup.
2026-05-20 15:13:38 -07:00
Copilot
dedf05565d
feat(frontier): add replay variability suite and token-cost telemetry (#66)
Agent-Logs-Url: https://github.com/AssetOverflow/core/sessions/f88b48fa-0c2a-4f9d-a42b-d275596e43b8

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AssetOverflow <109810776+AssetOverflow@users.noreply.github.com>
2026-05-20 15:04:34 -07:00
Shay
9e6fa4be75
feat(adr-0083): transitive (multi-hop) teaching-grounded surface (#63)
Strict superset of ADR-0062's depth-1 composer.  `max_depth` is the
number of follow-up hops appended beyond the initial chain:

  max_depth=0  → byte-identical to single-chain surface
  max_depth=1  → byte-identical to ADR-0062 composed
  max_depth=2  → byte-identical to ADR-0062 when no second hop
                 survives, strict superset when one does

The composer surfaces content the realizer was silently dropping
from chains already ratified in `cognition_chains_v1`.  Example
live lift on `"Why does light exist?"`:

  composed: "light reveals truth, which grounds knowledge."
  transitive(2): "...which grounds knowledge, which requires evidence."

Cycle-safe at every depth via a single visited-set; single-corpus
traversal in v1 (cross-corpus transitive deferred to a follow-up
ADR alongside ADR-0064's cross-pack model).

Both flags default False — every existing surface is preserved
byte-identically.  When both `composed_surface` and
`transitive_surface` are True, transitive wins.

Implementation:
- `core/config.py`: `transitive_surface: bool = False`,
  `transitive_max_depth: int = 2`.
- `chat/teaching_grounding.py`: `_resolve_followup` shared helper
  refactored out of the depth-1 composer (no behavioural change),
  plus new `teaching_grounded_surface_transitive(subject,
  intent_tag, *, max_depth)`.
- `chat/runtime.py`: dispatch order — transitive > composed > single.

Verification:
- tests/test_transitive_surface.py: 16 new tests covering pure-fn
  contract, visited-set cycle guard at every depth, runtime
  integration, and the cognition-lane null-drop invariant at
  `max_depth=2` (public + holdout splits).
- tests/test_composed_surface.py: 11/11 pass after the helper
  refactor (ADR-0062 behaviour preserved).
- `core test --suite smoke`: 67 pass.
- `core test --suite cognition`: 120 pass, 1 skipped.
- `core test --suite teaching`: 17 pass.
- `core eval cognition`: 100 / 91.7 / 100 / 100 (byte-identical).
2026-05-20 14:11:40 -07:00
Shay
9459f815b0
feat(evals): wire ADR-0082 providers into frontier_compare runner (#61)
#58 shipped providers.py + model_registry.py for cross-provider
benchmarking but never connected them to runner.py — the adapters
sat unused.  This PR wires them through with a clear lane split.

Why a new suite instead of refactoring existing ones
-----------------------------------------------------
The three existing suites (determinism / truth_lock / axis_orthogonality)
pull CORE-only telemetry: trace_hash, versor_condition, register_id,
register_variant_id, anchor_lens_id, register_canonical_surface.
None of those fields can come from OpenAI / Anthropic / Ollama.

Forcing those suites cross-provider would silently produce reports
where the cross-provider rows have empty telemetry — a worse failure
mode than not running them at all.  So the routing is explicit:

  CORE-only suites          → --provider must be 'core'
  Cross-provider suites     → any provider; CORE is one adapter among many

Operator asks for the wrong combo → loud error with the right alternative.

New module: evals/frontier_compare/cross_provider.py
-----------------------------------------------------
- ProviderObservation dataclass — provider-agnostic observation shape
  (prompt, surface, provider, model, elapsed_ms, error fields).  No
  CORE-internal telemetry expected.
- run_prompt_battery(adapter, *, cfg) → SuiteReport reusing existing
  CaseResult / SuiteReport shapes so the report viewer renders both
  lanes without schema branching.
- _PROMPT_BATTERY: 7 fixed cases spanning definition / cause /
  verification / comparison / procedure / unknown intent shapes.
  Stable case_ids so future re-runs against the same provider produce
  diffable JSON.
- Per-case 'passed' is loose by design (non-empty surface, no
  exception).  Cross-provider quality is for human review — not for
  the runner to silently score.

Updated CLI: evals/frontier_compare/__main__.py
-----------------------------------------------
- --provider {core, openai, anthropic, ollama}    (default: core)
- --model <id>                                     (validated via require_model_card)
- --env-file <path>                                (default: ./.env)
- Auto-persist non-CORE runs to
  evals/frontier_compare/results/<provider>_<model>_<utc>.json
  even when --report is omitted.  API calls are rate-limited / paid;
  losing the artifact is costly.
- Existing CORE-native behavior unchanged when --provider not set.

Results directory: evals/frontier_compare/results/
--------------------------------------------------
Created with .gitkeep — matches the convention used by other lanes
(evals/long_context_cost/results/, evals/koine_greek_fluency/results/,
etc.).  Distinct from reports/ which .gitignore excludes for
transient debug output.

Tests: tests/test_frontier_compare_cross_provider.py (9 cases)
--------------------------------------------------------------
- prompt_battery runs with CORE adapter (no API needed)
- adapter exceptions recorded as failed observations, never propagated
- empty surfaces flagged distinctly from adapter errors
- CLI default runs CORE-native (no breaking change)
- CLI prompt_battery with --provider core routes through cross-provider path
- CLI rejects CORE-only suite + non-CORE provider with operator-helpful error
- --help surfaces both suite families
- unregistered model is rejected before any benchmark cycles burn
- ProviderObservation.succeeded handles error / empty / whitespace cases

Live evidence
-------------
$ core test --suite smoke -q
67 passed in 26.55s   (no regression)

$ python -m evals.frontier_compare --provider core --suite prompt_battery --json
model=core-native mode=core suite=prompt_battery passed=True score=1.000
  [definition_truth              ] PASS  Truth is a claim or state grounded by evidence...
  [definition_knowledge          ] PASS  Knowledge is justified understanding grounded...
  [cause_understanding           ] PASS  understanding — teaching-grounded (cognition_chains_v1)...
  [verification_evidence         ] PASS  evidence — teaching-grounded (cognition_chains_v1)...
  [comparison_knowledge_wisdom   ] PASS  knowledge contrasts with wisdom...
  [procedure_recall              ] PASS  To recall means to retrieve a stored state from memory...
  [unknown_term                  ] PASS  I haven't learned 'xylomorphic' yet...

$ python -m evals.frontier_compare --provider openai --suite determinism
error: suite 'determinism' is CORE-only; pass --suite prompt_battery
(the cross-provider suite) when --provider='openai'.

.gitignore: adds frontier_wave1.json (stray report file repeatedly
written by ad-hoc test invocations).
2026-05-20 13:22:37 -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