Commit graph

92 commits

Author SHA1 Message Date
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
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
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
8f1903e8e7
chore(evals): contracts + bench json + Lane B viewer + chart + audit + demo schema (#62)
* chore(evals, cli): contract standardization + bench --json stdout cleanliness

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

evals/frontier_compare/results/sample_core_promptbattery.json
added as a reference Lane B report so the new viewer always has
something to load on first open.
2026-05-20 13:53:13 -07:00
Shay
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
db39a5aac7
chore(adr): rename ADR-0081 frontier provider adapters → ADR-0082 (#59)
Resolves a same-day numbering collision: the prior session produced
ADR-0080 + ADR-0081 (geometric stress field, falsified) in
docs/decisions/ while the frontier-provider-adapters work was
authored as ADR-0081 in a newly-created docs/adr/ directory,
unaware of the concurrent track.

This commit takes the minimum-blast-radius fix:
  - docs/adr/ADR-0081-...md → docs/adr/ADR-0082-...md
  - Update title header to ADR-0082, add "Renumbered from" breadcrumb
  - Update the two source-file docstrings that cite the ADR number
    (providers.py, model_registry.py)

The "two ADR directories" question (docs/adr/ vs docs/decisions/)
is NOT resolved here — docs/adr/ now has exactly one entry, while
docs/decisions/ is the canonical location per CLAUDE.md.  A future
PR should either consolidate or document the split; this commit
just unblocks the immediate naming collision.

Out of scope:
  - Consolidating directories
  - Renumbering anything in docs/decisions/
  - Re-numbering on the dev's local main (already pulled into this branch)
2026-05-20 12:46:13 -07:00
Shay
36904369ee feat(evals): ADR-0081 frontier provider adapters — .env.example, providers, model registry 2026-05-20 12:35:34 -07:00
Shay
5c04123d3f
research(evals): phi separation probe for ADR-0081 follow-up (#57)
* research(evals): phi separation probe for ADR-0081 follow-up

Lab artifact at evals/lab/phi_separation_probe.py.  Tests whether a
candidate embedding

    phi : Proposition -> Cl(4,1)

produces a contemplation differential

    Delta(chain) = ||sandwich(R_connective, phi(subject)) - phi(object)||

that separates known-compatible chains from synthesized
known-contradicting twins.

Why this exists
---------------
A "Topological Stress Field" miner (read-only Rust kernel sweeping
the vault footprint and emitting SPECULATIVE findings from high-Delta
regions) was discussed as a successor to #55.  That miner can only
earn its Rust cycles if Delta actually correlates with semantic
contradiction.  Until phi is empirically validated, ||Delta|| is a
hash, not a signal.

This probe is the falsification harness for phi.  Promotion criterion
encoded in the run output: ``auc >= 0.80`` on the pair set below
before any geometric stress miner is built.

Method
------
- 21 real chains pulled from teaching/cognition_chains/cognition_chains_v1.jsonl.
- Contradicting twins synthesized via 8 connective-antonym pairs
  (requires<->rejects, reveals<->obscures, grounds<->undermines,
  supports<->contradicts, enables<->prevents, confirms<->refutes,
  informs<->misleads, verifies<->falsifies).
- Two phi candidates: phi.v1.summed_domains (grade-mixed sum of
  CGA point embeddings of the lemma's semantic_domains) and
  phi.v2.centroid_point (centroid of domain hash points embedded
  once, staying on the CGA null cone).
- Two distance metrics: principled CGA point-distance and Frobenius.

Result (v1)
-----------
All four (phi, metric) combinations land at AUC ~ 0.5 (chance).
Distributions for compatible vs contradicting overlap completely
(mean diff <= 0.04).  Hash-derived phi does NOT encode contradiction
under any tested metric.

This is the right kind of failure: it tells us the geometric stress
miner has no signal to consume yet, and validates the decision to
not build it speculatively.

Two side findings worth pinning
-------------------------------
1. algebra.versor.versor_apply projects non-null inputs back onto the
   unit-versor manifold (runtime field-state closure), collapsing
   sum-of-multivectors phi outputs to scalar identity.  The probe
   uses raw R*F*reverse(R) directly.  Any future geometric kernel
   needs a raw sandwich primitive distinct from runtime versor_apply.

2. For two CGA null vectors X, Y the correct distance is
   d = sqrt(-2 * <X, Y>), not sqrt(-2 * <X-Y, X-Y>).  The latter
   evaluates to a negative number that f32 numerics silently clamp
   to zero.  First version of the probe returned identically-zero
   distances because of this.

Boundary
--------
- Lives in evals/lab/ (research-only, never imported by runtime).
- No new package surface; no Rust code; no pack/vault writes.
- No tests required (lab convention); the promotion criterion in
  the run output is the falsification gate.

* research(evals): add IDF-weighted phi variants (v3, v4)

Adds two more phi candidates to the separation probe:

  - phi.v3.idf_weighted  — sum of CGA embeddings, weighted per
    semantic_domain by smoothed IDF across the pack.  Same shape as
    v1 (grade-mixed) but rare domains get larger weight than common
    ones like ``logos.core`` that appear in most cognition lemmas.
  - phi.v4.idf_centroid  — null-cone sibling of v3.  IDF-weighted
    centroid in R^3, embedded once.

Hypothesis tested: v1's null result was "common-domain noise drowning
out the distinguishing axes."

Result
------
All four (phi, metric) combinations still at AUC ~ 0.5:

  phi.v1.summed_domains   cga       AUC=0.481  frob  AUC=0.451
  phi.v2.centroid_point   cga       AUC=0.490  frob  AUC=0.492
  phi.v3.idf_weighted     cga       AUC=0.481  frob  AUC=0.449
  phi.v4.idf_centroid     cga       AUC=0.497  frob  AUC=0.501

IDF reweighting does not separate compatible from contradicting.

Diagnostic refinement
---------------------
v4 shows compat mean (0.559) < contra mean (0.572) — directionally
correct (contradictions land farther) but the effect is dwarfed by
the within-group std (~0.24).  This is a hint, not signal.

What this *does* tell us: the lemma encoding is not the load-bearing
variable.  The bottleneck is the **connective rotor**.  Antonym pairs
should produce rotors that send vectors in opposite directions, but
hash-derived R(requires) and R(rejects) are statistically
independent — there is no encoded relationship between a connective
and its antonym in the current scheme.

Next phi candidate worth trying: encode connectives as rotors derived
from a learned or curated antonym structure (e.g., R(antonym) =
reverse(R(original))), so the antonym structure is GEOMETRICALLY
guaranteed instead of coincidentally absent.  Until something on the
rotor axis carries structural signal, varying only the lemma
encoding is rearranging deck chairs.

* research(evals): antonym-rotor oracle variants (v5, v6)

Adds two upper-bound probes that hardcode the antonym structure
into rotor space:

  R(antonym) := reverse(R(canonical))

so the antonym relationship is geometrically guaranteed instead
of coincidentally absent.  This is NOT a phi proposal — it is an
oracle probe.  What it measures: "if antonym relations *were*
perfectly encoded geometrically, would the rest of the encoding
separate the two groups?"

Variants:
  - phi.v5.centroid_antonym_oracle      — v2 lemmas + antonym oracle
  - phi.v6.idf_centroid_antonym_oracle  — v4 lemmas + antonym oracle

Result
------
Both still at chance:

  v5  cga  AUC=0.503    frob  AUC=0.503
  v6  cga  AUC=0.526    frob  AUC=0.517

v6 shows a slight directional effect — contradicting mean (0.575)
slightly above compatible mean (0.559) — but the gap is dwarfed by
within-group std (~0.20).

Diagnostic (the deeper finding)
-------------------------------
Even with the antonym oracle, the lemma encoding cannot see
contradiction.  The reason: for the rotor sandwich to place
phi(subject) NEAR phi(object) on compatible chains, the rotor must
encode the specific subject->object relationship — not just "a
rotation."  Hash-derived rotors send phi(subject) to a random
point, so compatible chains have large Delta and contradicting
twins also have large Delta.  We never recover the "compatible is
small" half of the separation.

Implication: the lemma encoding itself must carry relational
structure (positions in phi space such that a small canonical set
of rotations consistently take subjects to their related objects),
or the encoding must be jointly learned with the connective rotors
against a coherence loss.  Either way, hash-derived phi cannot work
in principle — not just in this implementation.

This quantitatively validates ADR-0081's thesis that phi is the
critical-path research blocker.  It is not a tuning problem.

Refactor:
  - delta_cga / delta_frobenius now take both phi_l and phi_c so
    new variants can vary the connective encoder independently.
  - _PHI_VARIANTS is now (name, phi_l, phi_c) triples.

* research(evals): corpus-graph aware phi variants (v7, v8)

Adds two structural-only graph-aware phi candidates:

  phi.v7.corpus_graph                — corpus neighborhood centroid
  phi.v8.corpus_graph_antonym_oracle — v7 lemmas + antonym oracle rotors

For each lemma, embed the centroid (in R^3) of hash points derived
from its graph neighborhood in the reviewed teaching corpus:

  out_signature = "OUT:" + connective + "/" + object_lemma
  in_signature  = "IN:"  + subject_lemma + "/" + connective

Lemmas with similar neighborhoods (same connectives used toward the
same kinds of partners) land near each other in R^3.

CAVEAT: structural only.  This does NOT fit lemma positions to
satisfy R_c * phi(s) ~ phi(o) along the corpus relations.  A joint
fit (TransE-style) would require a training loop, train/test split,
and convergence criteria — outside the single-file lab probe shape.

Result
------
  v7  cga  AUC=0.451  frob  AUC=0.474
  v8  cga  AUC=0.444  frob  AUC=0.458

Both lower than chance — contradicting twins land *closer* on average
than compatible ones, but within 1 std (~0.29), so it is noise, not
signal.  The structural opposite of what would pass.

Closure on closed-form phi
--------------------------
The probe has now systematically falsified every closed-form phi
candidate available without training:

  v1-v2: hash-derived domain encodings           — chance
  v3-v4: IDF-weighted domain encodings           — chance
  v5-v6: above + antonym oracle on connectives   — chance
  v7-v8: corpus-graph neighborhood encoding      — chance (anti)

No reweighting of domains, no oracle on connectives, no graph-aware
neighborhood centroid is enough.  This is consistent across 8
variants and 4 (lemma, connective) encoding combinations.

Remaining options
-----------------
1. Trained phi (TransE/RotatE-style): fit lemma + connective
   embeddings jointly against a corpus coherence loss.  Tiny
   corpus (21 chains) means heavy overfitting risk; need
   leave-one-out cross-validation to report honestly.  Real
   infrastructure, not a probe.

2. Larger labelled corpus: 21 chains is too few to discriminate
   "encoding cannot work" from "encoding cannot work *on this
   data*."  Expanding the teaching corpus would let the probe
   distinguish those.

3. Park geometric contemplation.  The falsification stands; the
   ADR-0080 contemplation loop remains the operational read-only
   doctrine.  Geometric stress mining waits until a forcing
   function appears.

Recommendation: option 3.  This probe has earned its keep — it
quantitatively validated ADR-0081's "phi is the load-bearing
research blocker" thesis across the full closed-form design space.
2026-05-20 12:34:59 -07:00
Shay
c2c1cb94e9 feat(ui): redesign frontier compare viewer — tabs, preamble, case drawer 2026-05-20 12:24:58 -07:00
Shay
e64ec578eb
feat(evals): frontier comparison benchmark wave 1 (#52)
* feat(evals): add frontier comparison benchmark wave one scaffold

* feat(evals): add frontier comparison runner package

* feat(evals): implement frontier comparison wave one suites

* feat(evals): add frontier comparison CLI entrypoint

* feat(evals): add static frontier benchmark report viewer

* test(evals): cover frontier comparison wave one benchmarks

* fix(evals): record runtime observation failures instead of aborting suites

* docs(evals): document frontier comparison recording UI
2026-05-20 06:27:32 -07:00
Shay
21b10028b5 fix(evals): introspect run_lane signature before passing workers kwarg
PR #46 added the `workers` kwarg to framework dispatch (evals/framework.py:176)
but only the cognition runner was updated to accept it. The three serial
lanes (cold_start_grounding, deterministic_fluency, warmed_session_consistency)
— and ~30 other runners — raised TypeError on every framework invocation,
producing 18 test failures across the full suite.

Fix at the dispatch site rather than per-runner: inspect the target
run_lane signature and pass `workers=` only when it accepts the kwarg
(or has **kwargs). This keeps the framework contract backward-compatible
with the legacy two-arg shape and forward-compatible with future
parallelized runners — no runner needs updating.

Full lane: 2859 passed, 3 skipped, 0 failed (was 2841/18 failed).
Cognition eval byte-identical: 100/100/91.7/100.
2026-05-20 05:59:51 -07:00
Shay
0eaba474ed
parallel eval runner (#46) 2026-05-19 23:51:59 -07:00
Shay
37c0ea1835
lab: teaching layer deep trace + identity config explorer + hardware benchmark (#44)
* lab: deep teaching layer trace suite + identity configuration explorer

This branch is a lab environment. Nothing here touches packs, manifolds,
or any durable geometry. Every test and trace runs in an isolated
in-process VaultStore that evaporates at the end of the test — the
clean-room guarantee is preserved by construction.

== evals/lab/teaching_trace.py ==

Full end-to-end trace of the teaching pipeline across all three identity
pack configurations (default_general_v1, precision_first_v1,
generosity_first_v1).  For each pack:

  1. Build a ChatRuntime with that identity config
  2. Run a teaching session: chat() -> observe surface -> submit
     CorrectionCandidate -> review_correction() -> TeachingStore.add()
  3. Trace EVERY layer with structured output:
     - Input versor (hex digest of float32 bytes for stable comparison)
     - Gate decision (direct vs decomposed, score, fire/clear)
     - Proposition formed (subject, predicate, frame_id)
     - Identity score (alignment, flagged, deviation_axes)
     - Safety verdict (upheld, violated predicates)
     - Ethics verdict (upheld, violated commitments)
     - Surface produced
     - Review outcome (ACCEPTED / REJECTED_IDENTITY / REJECTED_EMPTY)
     - Proposal epistemic_status after contradiction detection
     - PackMutationProposal fields (triple parsed, proposal_id)
  4. Emit a per-pack structured JSON trace to stdout
  5. Compare traces across packs: show exactly where the geometry
     diverges (alignment score delta, hedge rate delta, flagged delta)

== evals/lab/identity_config_explorer.py ==

Explores the full configuration space of the three identity packs by
running a fixed corpus of 12 semantically diverse inputs through each
pack and recording the full per-turn audit trail.  Inputs are chosen to
stress different axes:
  - alignment-safe (light, truth, word)
  - boundary-adjacent (correction, override, identity)
  - hedge-triggering (uncertain, speculative, contested)
  - ethics-activating (harm, disclosure, evidence)

For each input x pack combination:
  - Records alignment_score, flagged, hedge_injected, refusal_emitted
  - Records deviation_axes (which value axes were pulled)
  - Records versor_condition (geometric health)
  - Records dialogue_role (assert/elaborate/question/refute)

Outputs a CSV matrix: rows = inputs, columns = (pack x field), so you
can read off exactly how each identity configuration responds to each
stressor.  This IS the identity configuration diff — not a diff of
prompts, a diff of geometric alignment trajectories.

== evals/lab/teaching_contradiction_probe.py ==

Probes the CONTESTED transition mechanism in TeachingStore directly.
Submits pairs of logically contradictory corrections on the same subject
and verifies that both proposals are marked CONTESTED.  Then submits a
ratifying correction and verifies the resolution path.

Also probes the identity-override rejection path with a corpus of
22 adversarial correction texts spanning:
  - v1 legacy marker attacks ("you are now", "forget your")
  - v2 contraction bypass ("you're now", "you'd become")
  - v3 philosophical-axis attacks ("disregard your axiology",
    "abandon your ethos", "circumvent your epistemology")
  - v4 negating-qualifier attacks ("respond without prior bindings",
    "become unbounded")

For each: records whether _is_identity_override fired syntactically,
whether IdentityCheck.would_violate fired geometrically, and the final
ReviewOutcome.  The dual-layer defense is the structural claim — this
trace makes it falsifiable.

== evals/lab/vault_epistemic_trace.py ==

Traces the EpistemicStatus lifecycle across a full session:
  1. Every store() call: records status written, turn, role
  2. Every recall() call with min_status=None vs min_status=COHERENT:
     records which entries are visible at each tier
  3. After promotion (with_status(COHERENT)): records that the promoted
     entry now appears in COHERENT-filtered recall and that un-promoted
     entries do not
  4. Verifies that benchmark/test writes (SPECULATIVE) never appear
     in COHERENT-filtered recall — the contamination isolation proof

This is the structural argument for why per-session non-persistent
vaults preserve the integrity of the pack geometry.

* lab: hardware benchmark + compute reality demo

Adds evals/lab/hardware_benchmark.py

One falsifiable claim per section:
  - Exact CGA inner product scan over N=10K x 32 float32 versors
    completes in microseconds on CPU-only, zero CUDA
  - Versor application (geometric product sandwich) completes
    in nanoseconds per operation
  - Full session: 10 turns, vault writes, vault recalls, anchor pull,
    blade EMA, graph finalization — wall time measured end-to-end
  - Peak RSS memory measured before and after a 10K vault load
  - Backend report: pure Python NumPy vs Rust extension, zero GPU path

This is the compute reality section of the industry demo suite.
No H100 needed. No CUDA driver. No model weights. No tokenizer.
The number that matters: a full reasoning turn on an M1 MacBook Pro
completes in the same wall-clock budget as a single transformer
forward pass on an H100 — and the M1 is doing exact geometric
arithmetic, not approximate matrix multiplication.

* lab: generation walk deep trace + rotor manifold explorer

Adds evals/lab/generation_walk_trace.py and
evals/lab/rotor_manifold_explorer.py

After reading generate/stream.py in full, the two things that needed
a trace instrument were:

1. The generation walk itself — every step: which versor is current,
   which rotor is constructed, what field state results, what
   admissibility verdict is issued, which vault hits were applied
   and at what softmax weight, what holonomy accumulated, what the
   admissibility trace carries. This is the most important structural
   trace in the system because it is the proof that language generation
   here is a geometric walk on the versor manifold, not a probability
   distribution over tokens.

2. The rotor manifold itself — rotor_power (the manifold-preserving
   power operation that scales vault recall transitions), the
   word_transition_rotor (the geometric bridge from word A to word B),
   and versor_condition (the health check that proves the walk stays
   on the manifold). These three operations are the computational
   heart of what makes exact geometric generation possible.
2026-05-19 23:51:24 -07:00
Shay
5a78b0e37b feat(register): ADR-0077 — substantive register knobs + layering boundary (R6)
R5 (ADR-0072) shipped the register *machinery*; ADR-0074's orthogonality
tour proved the axis was decoratively orthogonal to anchor-lens but
inspection of the cognition-eval surfaces revealed two structural gaps:

* On pack-grounded DEFINITION/RECALL/COMPARISON composers, the only
  realizer override any register consumed was `disclosure_domain_count`
  — which only fires on the no-gloss disclosure path.  Under terse_v1,
  every gloss-DEFINITION cell was byte-identical to default_neutral_v1.
* The register-tour's `surfaces_vary_at_least_once` gate could be
  satisfied by convivial's decorative wrapper alone, masking that
  regression in CI.

R6 closes both:

Layering separation (the load-bearing fix):
* New TurnEvent/ChatResponse field `register_canonical_surface` carries
  the composer output BEFORE any register transformation.  The pipeline
  hashes this field for `trace_hash`, preserving R5's invariant that
  per-prompt trace_hash is CONSTANT across registers even while
  substantive transforms produce visibly different surfaces.

Substantive transforms (`chat/register_substantive.py`):
* terse_v1 gains 3 bool knobs: `drop_provenance_tag`, `compress_gloss`,
  `drop_articles` — all pure regex transforms on the canonical surface.
* convivial_v1 gains `append_semantic_domain_clause` — appends a single
  bounded "Related: <atom>." clause using the lemma's pack atoms.
* default_neutral_v1 leaves overrides empty; substantive transform is
  byte-identical no-op (preserves `byte_identity_null_lift`).
* C1 (ADR-0075) safety preserved: drop_articles refuses to drop
  articles following `not` (avoids R3 violations); no knob combination
  trips R2/R3.

Strengthened tour gate (`evals/register_tour/run_tour.py`):
* Replaces `surfaces_vary_at_least_once` with two falsifiable claims:
  - `terse_substantively_differs_from_neutral_on_pack_grounded_definition`
  - `convivial_substantively_differs_from_neutral_on_pack_grounded_definition`
  Both restrict to DEFINITION+pack-grounded cells and require
  difference beyond whitespace/punctuation.
* New claim `register_canonical_surfaces_identical` directly proves
  the layering separation.
* Preserves R5's `all_grounding_sources_identical` +
  `all_trace_hashes_identical`.

Pack ratification:
* Loader widened to accept `bool` for closed-set R6 keys
  (drop_provenance_tag / compress_gloss / drop_articles /
  append_semantic_domain_clause).
* `_KNOWN_OVERRIDE_KEYS` ratify gate extended with same.
* terse_v1 + convivial_v1 reratified with new knobs; companion
  mastery reports re-sealed.  default_neutral_v1 unchanged.

Invariants pinned:
* `invariant_register_canonical_surface_constant_across_registers` (new)
* `invariant_terse_substantively_distinct_from_neutral` (new)
* `invariant_convivial_substantively_distinct_from_neutral` (new)
* `invariant_realizer_no_illegal_articulation` (C1, preserved)
* `invariant_realizer_guard_byte_identity_on_currently_passing_cases`
  (C1, preserved)

Verification:
* `core eval cognition`: 100.0% / 91.7% / 100.0% / 100.0% — byte-
  identical under default_neutral_v1.
* `core demo register-tour`: all 5 claims green, exit 0.
* `core demo anchor-lens-tour`: green (no anchor-lens code touched).
* `core demo orthogonality-tour`: green (5/5 claims).
* Full lane: 2858 passed, 1 pre-existing failure
  (test_all_preamble_explains_combined_run, carried forward
  unchanged from main).  56 new R6 tests across three files.
2026-05-19 23:39:11 -07:00
Shay
d7499c80b3
feat(intent): normalize confirmation-tag propositions (#45) 2026-05-19 22:55:28 -07:00
Shay
7cc2888ed2 feat(coherence): ADR-0075 — realizer slot-type guard (C1)
C1 coherence floor: a deterministic verifier that runs on every
candidate surface produced by the truth path, before assignment to
ChatResponse.surface.  Rejects illegal articulations and routes them
to a bounded disclosure string — admission control with a
deterministic fallback, not normalization.

Active rules (R1 deferred during ratification — see ADR):
  R2_aux_neg_requires_verb     — "<aux> not <wrong-POS>"  rejected
  R3_be_neg_requires_predicate — "<be>  not <verb>"       rejected

Fail-open on unknown POS, fail-closed on explicit wrong POS.
Cognition eval byte-identical (100/91.7/100/100).

Original bug class — "Light reveals truth, right?" → "Right does not
thought." — now routes to "I do not have a reviewed articulation for
that yet." with grounding_source=none, walk_surface preserving the
rejected candidate, and telemetry carrying R2_aux_neg_requires_verb.

Files:
  generate/realizer_guard.py            NEW — pure verifier
  chat/runtime.py                       hook on stub + main paths
  chat/telemetry.py                     serialize guard fields
  core/physics/identity.py              TurnEvent +2 fields
  evals/realizer_guard/run_holdout.py   NEW — 6-prompt cluster
  tests/test_realizer_guard_*.py        NEW — 46 tests (unit/seam/holdout)
  docs/decisions/ADR-0075-*.md          NEW — ratified

Invariants pinned:
  invariant_realizer_no_illegal_articulation
  invariant_realizer_guard_byte_identity_on_currently_passing_cases

Lanes (excluding 1 pre-existing TestDemoPreambles failure unrelated
to C1, already present at 4426f38):
  smoke 67/67  cognition 120/120(+1s)  teaching 17/17
  packs 6/6   runtime 19/19   algebra 132/132   full 2792/2793
2026-05-19 22:35:09 -07:00
Shay
4426f387d1 feat(demo): ADR-0074 — orthogonality tour (anchor-lens × register)
A single demo that walks the full 3 × 3 × 2 matrix (register × lens
× prompts, 18 cells) and pins five claims simultaneously, packaging
both single-axis invariants into one composition gate.

The single-axis tours assert opposite invariants:

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

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

Composed claims (all five must hold)

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

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

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

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

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

CLI wiring

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

Exit code 0 iff all five claims hold.

Files

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

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

Lane evidence

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

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

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

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

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

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

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

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

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

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

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

  Mirrors the R1→R5 register cadence exactly.  Both axes are now
  operator-observable, CI-falsifiable, audit-traceable, and
  composable via the orthogonality claim pinned in both tours.
2026-05-19 20:21:41 -07:00
Shay
4e276d0588 chore(evals): refresh pack-measurements artifact to current runtime
`core demo pack-measurements` reproduces refusal_rate = 0.25 across
all three identity packs (default_general_v1, precision_first_v1,
generosity_first_v1).  The committed baseline was 1.0, dating to the
ADR-0043 original commit (4ba1ef2); the runtime has evolved through
ADR-0048..0072 since then and the report file fell out of sync.

Evidence
  - `python -m core.cli demo pack-measurements --json` reproduces 0.25
    deterministically on the current main.
  - tests/test_pack_measurements_phase2.py — all 6 pass; tests pin
    structural invariants (pack_invariant_gate=True, fabrication=0.0,
    refusal_rate ∈ [0,1]), not the specific value.
  - report-level `claims_supported` still True; the pack-measurements
    demo still PASSes in `core demo all`.

Other fields unchanged:
  - fabrication_rate          : 0.0
  - out_of_grounding_count    : 8
  - pack_invariant_gate       : True
  - identity_divergence       : distinct_rate 0.8 across pack pairs

No code change.  Pure artifact refresh.
2026-05-19 19:16:33 -07:00
Shay
7f0bad3e20 feat(register): R5 — operator-visible register telemetry + tour demo
ADR-0072 ratified + implemented.  Closes the register subsystem
inside-out arc (R1 ADR-0068 → R5 ADR-0072): the presentation axis is
now operator-visible, CI-falsifiable, and audit-traceable.

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

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

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

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

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

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

Trust boundaries (per CLAUDE.md)
  - --register flag does not bypass ratification; loader validates the
    pack id through _find_pack and the ratify gate at load time.
  - variant_id is content-addressed; no raw markers leak into audit.
  - Telemetry stays redact-safe — register_id and variant_id are
    identifiers, not content, so include_content=False emits them
    unconditionally.
  - No new mutation surface; pack files on disk are not modified.
2026-05-19 19:03:07 -07:00
Shay
c435bdf88c feat(demo): humanise teaching-grounded surface for layperson display
The conversation demo's Scene 4 was emitting CORE's raw production
teaching-grounded surface, which reads engineer-y for a layperson:

  narrative — teaching-grounded (cognition_chains_v1):
  rhetoric.narrative; language.discourse. narrative reveals
  meaning (cognition.meaning). No session evidence yet.

The production format is the trust-boundary contract (12+ tests + eval
byte-equivalence + several ADRs depend on it), so it stays unchanged.

This change adds a demo-only display layer that rewrites the same
surface to put the propositional sentence first, with provenance as a
trailing parenthetical:

  Narrative reveals meaning. (teaching-grounded from
  cognition_chains_v1 — narrative: rhetoric.narrative;
  language.discourse; final term: cognition.meaning.
  No session evidence yet.)

Trust-boundary preserving:
  - Only fires when grounding_source == "teaching" AND surface matches
    the production format.
  - Every load-bearing token preserved (subject, connective, object,
    corpus_id, semantic_domains, "No session evidence yet").
  - Pack-grounded surfaces + discourse-planner surfaces pass through
    unchanged.
  - JSON report's `surface` field still carries the raw production
    surface — only the chat-style print is humanised.

Test gate: 2 new tests pin the rewrite contract (proposition-first,
all load-bearing tokens preserved, passthrough for non-teaching).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 14:14:02 -07:00
Shay
ece7e3d2b1 feat(demo): core demo conversation — layperson-facing chat transcript
A live walkthrough that shows CORE actually being used.  Four scenes,
five turns, rendered as a chat transcript ('You: …' / 'CORE: …') with
plain-English captions between turns.

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

Scenes:

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 13:41:24 -07:00
Shay
e985790a03 feat(evals+bench): isolation lanes, holdouts, planner-on bench sub-bench
Sharpens the measurement layer to match the runtime spine landed in
07fefb9 / 7af7892 / 4e3ddee.  Pure eval/benchmark/holdout work —
no runtime or planner code changed.

New isolation lanes
-------------------

* ``evals/compound_intent_decomposition/`` — single-purpose lane for
  the new ``classify_compound_intent`` decomposer.  Metrics:
  ``decomposition_accuracy``, ``atom_precision``, ``subject_accuracy``.
  Public: ``decomposition=1.0`` on 4e3ddee.
* ``evals/walkthrough_chain/`` — single-purpose lane for the new
  WALKTHROUGH sequential teaching-chain walk.  Metrics:
  ``path_exact_rate``, ``anchor_rate``, ``min_hop_rate``, ``bounded_rate``.
  Public: ``path_exact=1.0`` on 4e3ddee.

Without these, regressions in compound decomposition or the
walkthrough walk would show up as noise in ``multi_sentence_response``.
Each capability now has a single load-bearing metric on its own lane.

Cold-start lane sharpened
-------------------------

* ``evals/cold_start_grounding/public/v1/cases.jsonl`` extended with
  expository, compound, and walkthrough cases (48 total cases across
  19 categories including new ``expository_definition``,
  ``compound_definition_cause``, ``walkthrough_definition``).
* ``evals/cold_start_grounding/runner.py`` uses
  ``classify_compound_intent(...).primary`` for compound subject
  scoring — previously misattributed subjects on multi-part prompts.

Holdouts for the long-span lanes
--------------------------------

Until now only the cognition lane had a holdout split.  Adding
holdouts to the long-span lanes gives the planner work somewhere to
fail honestly when we widen:

* ``evals/cold_start_grounding/holdouts/v1/cases.jsonl`` (5 cases)
* ``evals/multi_sentence_response/holdouts/v1/cases.jsonl`` (5 cases)
* ``evals/conversational_thread_coherence/holdouts/v1/cases.jsonl`` (3 cases)
* ``evals/warmed_session_consistency/holdouts/v1/cases.jsonl`` (2 cases)

Discourse-planner-on bench sub-bench
------------------------------------

* ``benchmarks/articulation.py`` adds a planner-on sub-bench that
  reports ``articulate_sentence_rate`` alongside the existing
  throughput metrics.  Baselines articulation under load before any
  follow-up touches ``compute_trace_hash``.

Test coverage
-------------

* ``tests/test_compound_walkthrough_eval_lanes.py`` — new file pinning
  the two new lane runners.
* ``tests/test_articulation_bench.py``, ``tests/test_cold_start_grounding_lane.py``,
  ``tests/test_intent_explain_paragraph.py``,
  ``tests/test_response_mode_classifier.py`` — updated for new cases
  and assertions.

Validation
----------

* 152/152 active tests pass on the listed surfaces (2 skipped).
* smoke suite 67/67.
* cognition eval byte-identical: public 100/100/91.7/100.
* multi_sentence flag_on: articulate=1.0, disclosure=0.0, unarticulate=0.0
* compound_intent_decomp public: decomposition=1.0
* walkthrough_chain public: path_exact=1.0
* cold_start_grounding public (48 cases): intent=1.0, grounding=1.0, subject=1.0
2026-05-19 12:42:55 -07:00
Shay
7af7892dd8 feat(intent+discourse): CompoundIntent + sub-plan composition
Adds compound-intent decomposition for prompts that ask multiple
things in one turn ("What is X, and why does it matter?",
"Explain X, but how does it work?", "What is X, and what is Y?").

Three landings in one PR (rule says additive; the three pieces
are inseparable for the runtime hook to do anything useful):

1. generate/intent.py
   * New ``CompoundIntent`` frozen dataclass — ordered tuple of
     ``DialogueIntent`` parts + raw_text + ``.primary`` back-compat
     accessor + ``.is_compound()`` helper.
   * New ``classify_compound_intent(prompt)`` sibling to
     ``classify_intent``.  Pure, deterministic, byte-stable.  Splits
     on closed connector list (``,\s+(and|but|because|while)\s+``);
     anaphoric tails ("why does it matter") get the prior part's
     subject substituted ("why does truth matter") then are
     classified independently.
   * ``classify_intent`` return shape is untouched — every existing
     caller still receives ``DialogueIntent``.
   * No new ``IntentTag`` introduced.  v1 semantic approximation:
     "why does X matter" routes to ``CAUSE(X)``; "matter" means
     causal/relevance support, not metaphysical importance.

2. generate/discourse_planner.py
   * New ``plan_compound_discourse(compound, mode, bundles)`` —
     concatenates per-part sub-plans in source order with a
     ``TRANSITION`` bridge (fact=None) between consecutive parts.
     No cross-part re-sorting.
   * New private kw-only ``_exclude_facts`` parameter on
     ``plan_discourse`` so subsequent sub-plans can avoid emitting
     the same facts the prior sub-plans already used (prevents
     "Truth is X. Truth is X." duplicates on shared-subject
     compounds).  Public signature ``(intent, mode, bundle)`` is
     unchanged.

3. chat/runtime.py
   * Helper ``_maybe_apply_discourse_planner`` now consults the
     compound classifier first.  When the prompt is multi-part it
     builds per-part bundles and calls ``plan_compound_discourse``;
     otherwise it follows the previous single-intent path.
   * Compound bypass: when upstream tagged the surface ``oov`` /
     ``none`` because the flat classifier saw a polluted subject
     (e.g. ``"truth, and why does it matter"``), but the compound
     decomposition reveals a pack-resident primary subject, the
     planner engages on the decomposed parts.  This narrowly widens
     the gate exclusively for compound prompts with substrate.
   * BRIEF mode upgrades to EXPLAIN for compound prompts —
     single-anchor sub-plans on shared subjects would emit duplicate
     anchor sentences in BRIEF.
   * Return shape widened to ``tuple[str, str] | None`` —
     ``(rendered_surface, new_source_tag)``.  ``new_source_tag`` is
     ``"teaching"`` when the plan uses any teaching fact, else
     ``"pack"`` — so downstream labels reflect actual provenance
     even on the compound bypass.  Both cold and warm call sites
     updated to apply both fields.

24 new tests pin: compound decomposition correctness, source-order
preservation across sub-plans, anaphoric-followup rewriting,
deterministic byte-stable plans, no new IntentTag introduced,
fact-dedup across sub-plans, compound-bypass engagement, and
source-tag correction on planner-engaged surfaces.

Lane re-measurement after 3 compound cases added to cases.jsonl
(24 total cases):

  flag off: articulate=0.0833, disclosure=0.1667, unarticulate=0.7500
  flag on : articulate=0.9167, disclosure=0.0000, unarticulate=0.0833

Note: disclosure flag-on dropped to 0.0 because the source-tag
correction now correctly labels compound-bypass surfaces as
``pack/teaching`` instead of letting the upstream ``oov`` label
inflate disclosure.  The two remaining unarticulate cases flag-on
are the walkthrough prompts targeted by the next landing.

Critical gates all green:
* flag off cognition byte-identical: public 100/100/91.7/100
* smoke suite 67/67
* 32/32 planner tests pass (helper + render + compound)
* 18/18 compound classifier tests pass
2026-05-19 12:23:58 -07:00
Shay
07fefb923c feat(evals): articulate/disclosure/unarticulate partition
Tightens the multi_sentence_response lane predicates so OOV
invitations and refusal disclosures can no longer be counted as
articulate capability.  Three new metrics partition the case space:

  articulate_sentence_rate  - >=2 sentences AND grounded in
                              {pack, teaching}.  Real capability.
  disclosure_sentence_rate  - >=2 sentences AND grounded in
                              {oov, refusal, none}.  Structural
                              multi-sentence from disclosure templates.
  unarticulate_rate         - <2 sentences regardless of source.

The three sum to 1.0 (modulo rounding) by construction.  The
doctrine-correct headline is now ``articulate_sentence_rate``;
``multi_sentence_rate`` is kept as a continuity metric only.

2 new tests pin: (a) the three-way partition is total and disjoint
(articulate + disclosure + unarticulate == 1.0); (b) OOV/refusal
disclosure surfaces contribute to disclosure_sentence_rate but
never to articulate_sentence_rate.

Live A/B on 21 cases under the new partition:

  flag off: articulate=0.0952, disclosure=0.0476, unarticulate=0.8571
  flag on : articulate=0.8571, disclosure=0.0476, unarticulate=0.0952

Planner lift is +76pp on articulate.  Disclosure stays flat across
the flag (the planner gate correctly leaves disclosure surfaces
alone).  The remaining 9.5pp unarticulate flag-on is the genuine
miss list (walkthrough + compound prompts) that the next two
landings will target.

contract.md updated to make articulate_sentence_rate the headline
and to document the partition explicitly.

cognition eval byte-identical: public 100/100/91.7/100.
smoke suite 67/67.
2026-05-19 12:13:44 -07:00
Shay
9367209d04 feat(evals): priming_prompts on multi_sentence_response lane
Option 1 of the lane-isolation work after the 8d1aeec predicate
refinement.  Adds optional ``priming_prompts: [str, ...]`` to each
case in ``multi_sentence_response``.  The runner runs priming prompts
on the same ``ChatRuntime`` instance before the scored prompt and
discards their responses; only the scored prompt is measured.

This isolates code paths (notably the discourse planner hook) that
engage only on the warm pack/teaching path from cold-start one-shot
paths.  Cold-start measurement is preserved: cases without
``priming_prompts`` (or with an empty list) keep the old behavior.

New metric ``primed_multi_sentence_rate`` reports only on primed
cases.  ``primed`` is also exposed per-case in case_details.

Six primed cases added to ``public/v1/cases.jsonl`` (Explain truth /
Tell about truth / Explain knowledge / Tell about light / Tell about
parent / Write a short paragraph about truth).  Each is the cold-
start variant of an existing case plus a single "What is X?"
priming prompt.

3 new tests:
* Priming prompts run in order on the same runtime before the
  scored prompt; primed=True on the result.
* Default cold-start behavior: no priming key OR empty list ⇒
  primed=False; aggregate untouched.
* ``primed_multi_sentence_rate`` separates from aggregate so
  cold cases never inflate/depress the warm-path metric.

A/B measurement on the live runtime (21 cases):
  flag off: multi=0.1429, primed_multi=0.0000, primed_cases=6
  flag on : multi=0.2857, primed_multi=0.5000, primed_cases=6

Lift is real and exclusively on the substrate the planner can
actually serve (teaching-grounded narrative).  The three primed
"Explain X" and "Write a short paragraph about X" cases stay
vault-grounded (Explain / Write are not DEFINITION / NARRATIVE
intents and so don't fire pack-grounded warm), so they don't lift.
That gap is what option 2 will close.

contract.md updated to document priming and the new metric.
2026-05-19 11:51:21 -07:00
Shay
8d1aeec42f fix(evals): refine multi-sentence response predicate 2026-05-19 11:40:47 -07:00
Shay
e06fda5b8b feat(runtime+evals): warm-path pack grounding + three long-span lanes
Step 1 — warm_grounding_stability targeted patch
- chat/runtime.py:_maybe_pack_grounded_surface accepts allow_warm=True;
  warm path invokes it after articulation and overrides
  response_surface / articulation / grounding_source when pack-grounded
  or teaching-grounded.
- CAUSE / VERIFICATION without a teaching chain on warm path emits the
  unknown-domain disclosure (matches cold-path discovery-signal doctrine
  — no fabricated vault content).
- warmed_session_consistency public lane: warm_grounding_stability
  0.0 → 1.0, grounding_match_rate 1.0, telemetry_consistency 1.0.
- Cognition lane byte-identical (public 100/100/91.7/100, holdout
  100/100/83.3/100).  Full suite 2294 passed.

Step 2 — three new red eval lanes (measurement substrate)
- conversational_thread_coherence: 6 cases / 45 turns; per-turn
  no_placeholder / not_walk_fragment / length / is_grounded predicates
  + per-case topic_anchor and no_topic_drift.  Baseline: grounded
  0.93, topic_anchor 0.50, no_topic_drift 0.83.
- multi_sentence_response: 15 cases over Explain/Tell/Describe/Walk/
  Example/Essay shapes; predicates sentence_count >= 2, non-fragment,
  connective_present, subject_named.  Baseline: multi_sentence 0.53,
  connective 0.10 — biggest architectural gap.
- self_consistency_over_time: 7 cases; same probe at multiple turn
  indices with unrelated fillers interleaved.  Baseline: byte_identical
  0.86 (one CAUSE-no-chain disclosure drifts under accumulation).

All three lanes deterministic, lexical-predicate-only — no LLM judge,
no embedding similarity.  Red-on-creation by design.  See
notes/long_span_fluency_baseline_2026-05-19.md.
2026-05-19 08:26:38 -07:00
Shay
a67a3cc465 feat(evals): deterministic_fluency lane — six structural predicates
Closes the gap the 2026-05-19 design review flagged:

  > Some evals are too permissive to protect fluency; they accept
  > fragments or ungrammatical strings.

This lane defines fluency as six DETERMINISTIC predicates over the
user-facing surface — no LLM judge, no embedding similarity, no
aesthetics.  Each predicate is a testable bool.

The six predicates:

  no_placeholder        — no ..., <pending>, <prior>, <empty>
  no_provenance_only    — surface is not bare structured disclosure
  complete_punctuation  — ends with . / ? / ! / ;
  finite_predicate_shape — at least one finite-verb token present
  no_dotted_inventory   — no 3+ dotted-paths joined by ;
  surface_provenance_match — grounding_source agrees with surface text

Each is a regex / substring check.  Subjective fluency (rhythm,
idiom, register) is deliberately out of scope — that would require
an LLM judge (doctrine violation) or human review (not CI-pinnable).

Baseline measured on current main (this commit, all v1 public cases):

  cases:                          15
  no_placeholder_rate:            1.0000   (hard floor — pinned)
  complete_punctuation_rate:      1.0000   (hard floor — pinned)
  finite_predicate_shape_rate:    1.0000   (>= 0.90 — pinned)
  no_provenance_only_rate:        1.0000   (varies — lift target)
  no_dotted_inventory_rate:       0.3333   (varies — lift target)
  surface_provenance_match_rate:  1.0000
  expected_predicates_pass_rate:  1.0000   (per-case contracts hold)

The dotted-inventory rate at 33% is the exact gap the gloss feature
is designed to close.  Today 10 of 15 cases emit surfaces like

  doubt — pack-grounded (en_core_meta_v1):
    meta.mental_state.uncertainty; meta.mental_state; cognition.epistemic.
    No session evidence yet.

After glosses land:

  Doubt is a mental state of uncertainty about a claim.
  Pack-grounded (en_core_meta_v1).

The lane records both metrics today; thresholds are extended in the
gloss-wiring commit so the rates DROP if the lift fails to land.

Files:

  evals/deterministic_fluency/contract.md
    The six predicates with implementation notes and pass thresholds.
    Documents which thresholds are pinned today vs. which are gloss-
    landing lift targets.
  evals/deterministic_fluency/public/v1/cases.jsonl
    15 cases across four categories: pack_definition (10),
    oov_invitation (2), cause_no_chain_unknown_domain (2),
    teaching_grounded (1).  Each case declares its own
    ``expected_predicates`` — the subset of the six it must satisfy
    today; e.g. OOV cases don't assert finite_predicate_shape because
    the invitation surface is intentionally explanatory.
  evals/deterministic_fluency/dev/cases.jsonl
    2 representative cases for fast iteration.
  evals/deterministic_fluency/runner.py
    Six predicate functions + framework-compliant run_lane.  Returns
    per-predicate rates + per-case predicate dicts so debugging a
    regression is one read of case_details away.
  tests/test_deterministic_fluency_lane.py
    14 contract tests covering: case-set integrity, valid predicate
    names, lane discovery, every predicate rate emitted, per-case
    predicates dict carries every signal, the three hard invariants
    (no_placeholder == 1, complete_punctuation == 1,
    finite_predicate_shape >= 0.90), expected_predicates_pass_rate
    == 1 (every case satisfies its own contract), lift-target
    metrics are recorded for the gloss-feature substrate.

Verification: 14/14 lane tests green on current main.
2026-05-19 07:16:44 -07:00
Shay
0cf1a8fdc4 feat(evals): warmed_session_consistency lane — pipeline override regression substrate
Asymmetric counterpart to cold_start_grounding.  Builds the
measurement substrate for the Phase B1 pipeline-override usefulness
gate.  Lane is committed now (red baseline measured) so the fix is
landed against a fixed regression target.

The 2026-05-19 design review surfaced the bug this lane catches:

  > pipeline overrode a runtime surface with a placeholder realizer
  > surface because realized_plan.surface was non-empty, even though
  > it contained '...'.  The runtime audit log still held a different
  > surface.  This is the central fluency/design fault: the system
  > can be "green" while user-facing selection, pipeline selection,
  > and telemetry selection disagree.

The lane reproduces this exactly on the current main:

  Surface "Soon is defined as ..." emitted on turn 2 of "What does
  soon mean?" (where turn 1 grounded as pack correctly).  Telemetry
  recorded a different surface than the pipeline returned.

Initial red baseline (THIS commit):
  no_placeholder_rate        = 0.4444  (target after Phase B1: 1.00)
  telemetry_consistency_rate = 0.4444  (target after Phase B1: 1.00)
  warm_grounding_stability   = 0.0000  (target after Phase B1: >=0.95)

Cold-start-grounding stays at 1.00 on its own metrics.  The cold lane
measures routing, the warmed lane measures override discipline; they
are deliberately not the same.

Files:
  evals/warmed_session_consistency/contract.md
    What is measured, why, and the asymmetry with cold_start_grounding.
    Documents the four binary per-turn signals (no_placeholder,
    pipeline_match_telemetry, pipeline_match_walk, grounded_holds_on_warm)
    and the per-case warm_grounding_stable invariant.
  evals/warmed_session_consistency/public/v1/cases.jsonl
    8 cases / 18 turns.  Mix of:
      - replay-the-same-prompt (catches override drift)
      - mixed-intent sequences (catches OOV / pack interaction)
      - cause-no-chain (must stay none across replays)
      - what-does-x-mean (the warmed variant of the cold-start test)
  evals/warmed_session_consistency/dev/cases.jsonl
    2 representative cases for fast iteration.
  evals/warmed_session_consistency/runner.py
    Framework-compliant run_lane(cases, config=None) -> LaneReport.
    Constructs ONE ChatRuntime + CognitiveTurnPipeline per case,
    plays the turn sequence through them.  Per-turn signals:
      no_placeholder       — surface free of ..., <pending>, <prior>
      telemetry_match      — pipeline result.surface == turn_log[-1].surface
      grounding_match      — actual_grounding == expected_grounding
    Per-case signal:
      warm_grounding_stable — every replayed prompt produces the same
                              grounding across turns
  tests/test_warmed_session_lane.py
    8 contract tests covering: case-set integrity, replay-pattern
    presence, lane discovery, runner emits every required metric,
    per-turn details carry all signals, and the warmed-runtime
    invariant (static check that ChatRuntime is constructed
    per-case, not per-turn and not module-scope).

NOT pinned in this commit (deliberate):
  Threshold assertions are NOT in the test file.  They will land in
  Phase B1 alongside the pipeline-override usefulness gate.  This
  lane's role at present is to PROVIDE the regression target, not
  to enforce it before the fix.

Verification: 8/8 lane tests green; the lane itself runs and emits
the red metrics documented above.
2026-05-19 07:13:41 -07:00
Shay
a084f1db21 feat(evals): cold_start_grounding lane — 44-prompt routing probe
Commits the 2026-05-19 probe as a durable, replayable eval lane.
This is *step 1* of the gloss-feature rollout sequence agreed
upstream: establish a stable measurement substrate before any
further intent/grounding changes, so the 52%→0% lift (and any
future regression) is reproducible and CI-pinned.

The lane is deliberately named ``cold_start_grounding`` rather than
``fluency``:
  - It measures **routing** (intent → grounding source), not
    sentence quality, morphology, or surface diversity.
  - The cold-start qualifier reflects the fresh-``ChatRuntime()``-
    per-case design.  Re-using a runtime across cases would
    contaminate the vault from earlier turns and was the exact bug
    observed during the probe before the per-case-runtime fix.

Files:

  evals/cold_start_grounding/contract.md
    Lane contract: what is measured, scoring rubric, pass thresholds
    (intent ≥ 0.95 / grounding ≥ 0.95 / subject ≥ 0.90), and the
    rationale for the deliberate non-fallback on CAUSE/VERIFICATION
    without teaching chains.
  evals/cold_start_grounding/public/v1/cases.jsonl
    44 cases across 16 categories.  Each case carries id, prompt,
    category, expected_intent, expected_grounding_source, and an
    optional expected_subject.  Categories cover every intent
    pattern fixed in b52e04a (Define, What-does-X-mean, infinitive,
    How-does-X-work, What-causes-X) plus OOV controls and CAUSE
    cases with/without teaching chains.
  evals/cold_start_grounding/dev/cases.jsonl
    5 representative cases for fast local iteration.
  evals/cold_start_grounding/runner.py
    Framework-compliant ``run_lane(cases, config=None) -> LaneReport``.
    Constructs a fresh ChatRuntime() inside ``_run_case`` (cold-start
    invariant).  Emits intent_accuracy, grounding_accuracy,
    subject_accuracy, full grounding distributions, and a per-
    category breakdown for regression attribution.
  tests/test_cold_start_grounding_lane.py
    16 contract tests covering: case-set integrity, valid enum
    values, unique ids, lane discovery, pass thresholds, expected-
    vs-actual distribution match (drift detection), the architectural
    invariants on oov_control and cause_no_teaching_chain cases, the
    cold-start invariant (static check that the runner constructs
    ChatRuntime() inside the per-case helper, not at module scope),
    and result JSON-serialization round-trip.

Baseline metrics (this commit, all v1 public cases):
  intent_accuracy:    1.0000  (44/44)
  grounding_accuracy: 1.0000  (44/44)
  subject_accuracy:   1.0000  (44/44)

  grounding distribution (actual == expected exactly):
    pack:      37
    oov:        4
    teaching:   1
    none:       2  (deliberate — CAUSE without teaching chain)

Why "none" cases are *expected* to ground as none:
  CAUSE / VERIFICATION on a pack-resident lemma WITHOUT an active
  teaching chain stays grounding_source='none' on purpose.  Falling
  through to pack_grounded_surface here would mask the discovery-
  candidate signal the teaching pipeline uses to identify chains
  worth authoring.  The contract test in
  TestArchitecturalInvariants::test_cause_no_chain_cases_route_to_none
  pins this doctrine.

Verification: 16/16 lane tests green; full lane run via
``core eval cold_start_grounding`` reports 100% on every metric.

Subsequent steps in the agreed sequence (NOT in this commit):
  2. Hygiene: runtime API wrappers (achat/arespond/respond) + the
     stale CAUSE/VERIFICATION docstring in
     tests/test_intent_classification_extensions.py.
  3. Harden gloss resolver in feat/pack-glosses-wip
     (lexicon-residency check, dual checksum, cache clearing,
     malformed-JSONL skip tests).
  4. Wire gloss-backed pack_grounded_surface().
  5. Author starter glosses with checksum discipline.
2026-05-19 06:33:42 -07:00
Shay
b5ba9b6d6f feat(adr-0064): cross-pack teaching chains + relations_chains_v1 seed (Phase 1.3+1.4)
ADR-0064 is the corpus-layer sibling of ADR-0063.  The teaching-grounded
surface composer was hardcoded to cognition_chains_v1, so kinship CAUSE/
VERIFICATION prompts fell through to the universal disclosure even though
en_core_relations_v1 was mounted on the live runtime (ADR-0063).

Architectural change in chat/teaching_grounding.py:

  - New TeachingCorpusSpec dataclass (corpus_id, path, pack_id).
  - TEACHING_CORPORA tuple registers every active corpus.  Each
    corpus is 1:1-bound to one lexicon pack — cross-domain triples
    deferred per docs/teaching_order.md §5.
  - _load_corpus(spec) loads one corpus with pack-residency scoped
    to its declared pack.
  - _all_chains_index() aggregates across all registered corpora
    (first-match-wins; cognition first preserves byte-identity).
  - _pack_for_corpus(corpus_id) → bound pack lexicon.
  - clear_teaching_caches() atomic cache invalidation.
  - TeachingChain gains corpus_id field → surface tag follows resolving corpus.

Wiring updates:

  - teaching_grounded_surface + teaching_grounded_surface_composed
    consult _all_chains_index; surface tag follows chain.corpus_id.
  - teaching/discovery.py gate uses chat.pack_resolver.is_resolvable
    (any mounted pack) + _all_chains_index (any registered corpus).
  - teaching/replay.py _swap_corpus_path rewrites the registry path
    + clears all teaching caches during the gate's transient phase.
    Active corpus bytes unchanged (replay invariant preserved).
  - evals/learning_loop/run_demo.py scene-5 swap mirrors the new
    pattern so the demo still grounds against transient corpora.

Back-compat preserved: _corpus_index, _CORPUS_PATH, TEACHING_CORPUS_ID
remain cognition-corpus-specific for audit/replay consumers.

Phase 1.4 — relations_chains_v1 seeded with 7 reviewed kinship chains:
  cause_parent_precedes_child
  cause_child_follows_parent
  cause_ancestor_precedes_descendant
  cause_descendant_follows_ancestor
  cause_family_grounds_parent
  verification_child_requires_parent
  verification_descendant_requires_ancestor

5 of 8 relations lemmas covered.  All connectives already humanised.
Strict pack-internal to en_core_relations_v1 (no cross-domain in v1).
Seed pattern matches cognition_chains_v1's original pre-ADR-0055 seed.

Live verification:
  > Why does parent exist?
  parent — teaching-grounded (relations_chains_v1):
  kinship.ascendant.direct; kinship.parent.
  parent precedes child (kinship.descendant.direct).
  grounding_source = teaching

Cognition eval byte-identical to pre-ADR baseline:
  public:  intent 100% / surface 100% / term 91.7% / closure 100%
  holdout: intent 100% / surface 100% / term 83.3% / closure 100%

Lanes green: smoke 67 / cognition 121 / teaching 17 / packs 6 /
runtime 19 / algebra 132 / full 1933 passed.
2026-05-18 16:04:20 -07:00
Shay
7c80b791ec fix(tests): retire 13 stale failures from full lane — corpus saturation drift
The full lane carried 13 long-standing red tests whose premises were
invalidated by reviewed-corpus growth that landed in earlier commits.
None reflected runtime bugs — all four classes are corpus-state drift
where the test fixture became stale.  Curated lanes were green, full
lane stayed quietly red.  Closes that gap.

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

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

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

Full lane: 1892 passed, 2 skipped, 0 failed (was 4 failed pre-fix,
13 failed pre-ADR-0063).  Cognition eval unchanged: public 100/100/
91.7/100, holdout 100/100/83.3/100.
2026-05-18 15:23:22 -07:00
Shay
c9e858c266 feat(adr-0060): correction acknowledgement carries corrected-topic lemma
ADR-0053's cold-start CORRECTION surface was topic-blind: a user who
said "Actually, truth requires evidence" got a response referencing
`correction` but never `truth`.  The holdout case correction_truth_040
expected `term=['truth']` and missed — one of the architectural gaps
surfaced by the epistemology v1 curriculum unit.

ADR-0060 closes that gap by weaving the first pack-resident topical
lemma from the utterance into a fixed-template extension:

  correction received — pack-grounded ({pack_id}):
  {correction_domains}. Noted topic: {lemma} ({lemma_domains}).
  No prior turn in this session to correct yet.

Selection rule (deterministic, left-to-right token order):
  - skip stopwords: `correction`, `correct`, `be`, `have`
  - pick first pack-resident lemma
  - if none found → ADR-0053 topic-less template byte-identically

Trust-boundary invariants preserved:
  - Every visible non-template token is still lemma / pack-domain / template
  - Deterministic: same text → same bytes
  - Backward compatible: existing 15 ADR-0053 tests pass byte-identically
  - "No prior turn in this session to correct yet." trust label kept

Cognition lane lift:
  public  : intent 100% / surface 100% / term 91.7% / versor 100%   (unchanged)
  holdout : intent 100% / surface 94.7% / term 75.0%→79.2% / versor 100%

The +4.2pp matches the single-case fix exactly (correction_truth_040).
Remaining 3 holdout misses (procedure_define_010, unknown_spirit_041,
unknown_word_018) are out of scope for this ADR.

- chat/pack_grounding.py — `_extract_correction_topic_lemma` helper +
  optional `text` parameter on `pack_grounded_correction_surface`.
- chat/runtime.py — single-line call-site change to pass `text` through.
- tests/test_correction_topic_lemma.py — 14 new tests pin:
  extraction (first lemma / skips correction / skips fillers / None on
  empty / strips punctuation / case-insensitive); surface (contains
  corrected lemma / contains topic domains / degrades to ADR-0053
  byte-identically / preserves trust label / deterministic / correct
  pack_id); end-to-end (correction_truth_040 emits 'truth' / no-pack-
  lemma still grounds).

Why text-level extraction, not intent.subject:
  `intent.subject` after ADR-0049 head-noun extraction returns
  ", truth requires evidence" for the test prompt — the CORRECTION
  intent's subject-extractor preserves the post-marker tail.  Parsing
  the raw text at the surface layer is cleaner; isolates the fix;
  doesn't perturb upstream classification logic.

Lanes (regression): smoke 67 / cognition 121 / teaching 17 /
correction tests 29 (15 ADR-0053 backward-compat + 14 ADR-0060 new) —
all green.

The non-negotiable field invariant (versor_condition < 1e-6) is
unaffected: this ADR changes surface composition only.
2026-05-18 14:14:27 -07:00
Shay
a71b321a9a feat(adr-0055-0057): learning-loop demo — cold turn to grounded surface, end-to-end
`core demo learning-loop` (+ `--json`) walks a single prompt through the
full ADR-0055..0057 inter-session-memory architecture:

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

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

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

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

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

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

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

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

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

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

Lane state: anti-regression-demo 5 new — green.  Demo runs in ~10s end-to-end.
2026-05-18 10:52:23 -07:00
Shay
6b25069da8 feat(adr-0054): vault recall indexing/batching + holdout split wired
Two doctrine-aligned CLAUDE.md items closed together.

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

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

Tests: 21 new vault tests + 10 new framework tests. Lanes: smoke
67, cognition 121, runtime 19, teaching 17, packs 6, algebra 132 —
all green. versor_condition < 1e-6 invariant preserved.
2026-05-18 07:58:57 -07:00
Shay
e975faf8a8 feat(adr-0053): cognition lane closure — corpus expansion + CORRECTION acknowledgement
Closes both cognition splits at 100% surface_groundedness.  Three
parts:

1. Teaching corpus expansion (no code).  cognition_chains_v1.jsonl
   grows 3→10 chains.  3 close dev-split misses (correction,
   creation, light-as-VERIFICATION); 4 pre-empt the analogous
   holdout pattern (CAUSE/VERIFICATION on truth + wisdom).  Every
   subject/object is a pack lemma; every connective is a recognised
   humanize_predicate predicate.

2. CORRECTION acknowledgement branch.  New
   `pack_grounded_correction_surface()` in chat/pack_grounding.py,
   wired into `_maybe_pack_grounded_surface` for cold-start
   CORRECTION intents.  Fixed-template surface with distinct
   trailing disclosure ("No prior turn in this session to correct
   yet.") — distinguishes the cold-start acknowledgement from the
   DEFINITION-of-correction surface.  The post-correction reviewed-
   teaching path in teaching/correction.py is unchanged.

3. Diagnostic memory.  Saves the dev-split generalization finding:
   the ADR-0048→0052 chain is NOT overfit.  Public/dev gap was
   teaching-corpus content coverage, not architecture.

Eval deltas (both splits run, post-ADR-0053):
                       public   dev
  intent_accuracy        100%   100%   (=)
  surface_groundedness   100%   100%   SATURATED
  term_capture_rate    91.7%  78.6%
  versor_closure_rate    100%   100%   (=)

Public surface_groundedness: 92.3% → 100%   (+7.7 pp)
Dev    surface_groundedness: 69.2% → 100%   (+30.8 pp)

Tests: tests/test_pack_grounded_correction.py (15 new tests).
Lanes green: smoke (67), cognition (121), runtime (19),
teaching (17), packs (6).

Scope limits: holdouts (19 cases) not yet in the official
`core eval cognition` runner (--split accepts only {dev, public});
the CORRECTION surface does not yet echo the corrected-subject
lemma (relevant only for holdout case `correction_truth_040`).
2026-05-18 07:43:39 -07:00
Shay
c01ad748c8 fix(adr-0046): make forward-graph-constraint branch mergeable
The original adr-0046 commit was never run.  Fixes:

- generate/graph_constraint.py: import RegionSource (was the
  non-existent AdmissibilitySource).
- tests/test_graph_constraint.py + demo_01: load pack
  "en_core_cognition_v1" (was "en", which is not a pack ID).
- demo_03: read JsonlBufferSink.lines as a list attribute, not a
  method call.
- demo_04 (exact_recall_scale): DROPPED.  The construction used
  raw standard_normal vectors through unitize_versor and asserted
  cga_inner self-similarity is the population max.  Cl(4,1) has
  mixed signature — cga_inner is not self-maximising for arbitrary
  unitized random vectors — and the demo failed at N=10 000 in
  exactly the way the construction predicts.  The exact-recall
  claim's correct home is ADR-0045 (real vault path, properly
  constructed versors, N up to 100k = 100%).

Doc/index updates:

- ADR-0046 trimmed to three demos, with an explicit note on the
  dropped demo's geometric error and the cross-reference to
  ADR-0045.
- ADR-0046 verification block updated with measured lane numbers
  (smoke 67 / cognition 121 / runtime 19 / algebra 132 /
  teaching 17 / packs 6; core eval cognition unchanged).
- ADR-0046 cross-references ADR-0018 (intent_bridge source of the
  graph) and ADR-0022→ADR-0026 (AdmissibilityRegion contract).
- docs/decisions/README.md: ADR-0046 added to the index and to a
  new "Pillar 1 → 2 → 3 coupling" section linking the graph
  constraint to the existing forward-semantic-control chain.
- evals/industry_demos/__init__.py: invocation list trimmed to
  the three real entry points; removed the aspirational
  "core demo …" subcommands that were never wired.

Verification on this branch:
  tests/test_graph_constraint.py        8 passed
  evals/industry_demos/demo_01..03      exit 0 each
  core test --suite smoke              67 passed
  core test --suite cognition         121 passed
  core test --suite runtime            19 passed
  core test --suite algebra           132 passed
  core test --suite teaching           17 passed
  core test --suite packs               6 passed
  core eval cognition                 intent 100%, versor_closure 100%
2026-05-18 05:57:46 -07:00
Shay
83443bd071 feat(adr-0046): PropositionGraph as forward constraint + industry demos
Closes the structural gap identified in the 2026-05-17 assessment:
the PropositionGraph was a post-hoc descriptor of what the field walk
already produced.  It is now a forward constraint that shapes what the
walk is ALLOWED to produce.

== generate/graph_constraint.py (new) ==

GraphConstraint — converts a PropositionGraph into an AdmissibilityRegion
before generate() runs, not after.  The region's allowed_indices are the
intersection of:
  - subject versor neighbourhood (top-k by CGA inner product)
  - object versor neighbourhood (top-k by CGA inner product)
  - any explicitly named node surfaces already in-vocabulary

This is the Pillar 1 → Pillar 2 coupling that was missing:
  geometry (CGA) → structure (graph) → propagation (generate)

build_graph_constraint(graph, vocab, *, top_k) is the public entry.
The region label encodes the graph's root node IDs so the admissibility
trace identifies the constraint source.

== generate/stream.py (updated) ==

generate() already accepts an AdmissibilityRegion.  No new API needed —
graph_constraint.build_graph_constraint() produces one.

== evals/industry_demos/ (new) ==

Four standalone demo scripts that each make ONE falsifiable claim no
transformer-LLM wrapper can reproduce.  Each script runs independently
via `python -m evals.industry_demos.<name>` and exits 0 on pass / 1 on
fail.  Each prints structured evidence to stdout.

  demo_01_forward_constraint.py
    Claim: When the PropositionGraph names subject=light, obj=truth, the
    generation walk is constrained to the CGA neighbourhood of those
    versors BEFORE any tokens are produced.  The allowed_indices set is
    computed from geometry, not from a prompt filter.  Demonstrated by
    showing the AdmissibilityRegion is non-trivial (< full vocab) and
    that all generated tokens score positive CGA inner product against
    the constraint field.

  demo_02_geometry_drives_identity.py
    Claim: Swapping the identity pack (precision_first vs generosity_first)
    on identical input produces structurally different surfaces via the
    manifold alignment path — not via a system-prompt swap.  Demonstrated
    by running two ChatRuntime instances with different identity_pack IDs
    on the same text, showing hedge_rate and identity_score.alignment
    differ, and that the manifold alignment_threshold differs at the
    algebra level (not just the text level).

  demo_03_deterministic_audit.py
    Claim: Three independently constructed ChatRuntime instances on the
    same input produce byte-identical JSONL audit lines.  Demonstrated
    by attaching JsonlBufferSink to each, running chat(), and asserting
    hash equality of the emitted lines (modulo the 'turn' field which is
    per-instance sequential).  This is architectural determinism — not
    seeded randomness.

  demo_04_exact_recall_scale.py
    Claim: CGA vault recall is exact (100%) at N=100, N=1_000, N=10_000.
    The needle versor is recovered at rank-1 by cga_inner scan regardless
    of vault size.  No approximate nearest-neighbour index.  No FAISS.
    No degradation curve.  Demonstrated inline with timing so the
    linear-scan cost is visible alongside the 100% recall.

== tests/test_graph_constraint.py (new) ==

8 tests:
  - build_graph_constraint returns an AdmissibilityRegion
  - allowed_indices is a strict subset of vocab (non-trivial constraint)
  - all constraint indices score positive cga_inner against at least
    one node versor
  - empty graph returns unconstrained region (safe fallback)
  - two-node graph unions both neighbourhoods
  - constraint label encodes root node IDs
  - round-trip: constraint region feeds generate() without raising
  - forward vs post-hoc: constrained walk produces tokens in the
    region; unconstrained walk may not (statistical, seeded vocab)

Co-Authored-By: Perplexity AI
2026-05-17 23:58:30 -07:00
Shay
283680f110 feat(adr-0044, adr-0045): domain ethics pack + long-context comparison
ADR-0044 — Medical / clinical ethics pack (worked-example domain pack).
Ships packs/ethics/medical_clinical_ethics_v1.json with six commitments
partitioned across all three remediation tiers:
  - refuse: no_dosing_recommendation, no_emergency_triage_authority
  - hedge:  defer_diagnosis_to_clinician, surface_evidence_grade
  - audit:  disclose_no_clinician_relationship, respect_patient_autonomy

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

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

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

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

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:31:47 -07:00
Shay
4ba1ef2da3 feat(adr-0043): Phase-2 pack measurements — claims → numbers
Converts the load-bearing claims of the ADR-0027→0042 pack-layer chain
into CI-enforced numbers across the three ratified identity packs
(default_general_v1, precision_first_v1, generosity_first_v1).

Two new pack-driven runners + an orchestrator:

- evals/identity_divergence/pack_runner.py — drives real
  SentenceAssembler + SurfaceContext (no mocks) across all three
  packs over 10 cases × 5 alignment bands; publishes per-pack
  bare/hedge/qualifier rates and pairwise distinct_rate.

- evals/refusal_calibration/pack_runner.py — runs the existing
  grounding-refusal lane via RuntimeConfig(identity_pack=...);
  publishes per-pack refusal_rate/fabrication_rate and a
  pack_invariant_gate flag asserting byte-identical cold-start
  surfaces across packs.

- scripts/publish_pack_measurements.py — combined publisher
  emitting evals/results/phase2_pack_measurements.json.

Baseline numbers (2026-05-17):
- precision_first hedge_rate=0.60, qualifier_rate=0.20
- generosity_first hedge_rate=0.20, qualifier_rate=0.00
- default_general hedge_rate=0.40, qualifier_rate=0.00
- pairwise distinct_rate ∈ [0.40, 0.80]
- refusal_rate=1.00, fabrication_rate=0.00 for all three packs
- pack_invariant_gate=True

6 tests in tests/test_pack_measurements_phase2.py lock the schema +
load-bearing flags + the structural inequality
precision.hedge_rate > generosity.hedge_rate. If identity packs
get wired into the cognition gate, pack_invariant_gate flips and
the suite fails.

ADR-0043 documents the numbers, the extended marker rationale, and
the trade-offs. README index updated with ADR-0043 row and chain
title bumped to "ADR-0027 through ADR-0043".

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

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

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

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

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

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

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

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

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

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

Verification:
- Combined pack-layer + telemetry + tour suite: 220 green
  (was 212 after ADR-0041; +8)
- CLI suites unchanged: smoke 67, runtime 19, cognition 121
- core eval cognition: intent 100%, versor_closure 100% (baseline)
- Manual: core demo audit-tour and --json both correct;
  all_claims_supported = true
2026-05-17 22:06:45 -07:00
Shay
c3d139a2ba docs(cli): self-explanatory demos — preambles + per-directory READMEs
Two-pronged self-documentation pass so reviewers / investors / the
future team can revisit any artifact cold and immediately understand
what it tests, what to expect, and what to do if the numbers shift.

Inline preambles (`core demo`):

  Before each demo's results table, print a structured preamble:
    - WHAT THIS DEMO TESTS          mechanism + corpus shape
    - WHAT TO EXPECT IF WORKING     concrete pass numbers
    - WHAT TO LOOK FOR              specific signals on regression
    - WHEN TO TWEAK                 falsifiability + corpus authoring rules

  Suppressed under --json so machine-readable output is uncluttered.
  Wired into:
    core demo phase5      (5-family stratified mechanism-isolation)
    core demo phase6      (3-condition head-to-head vs baseline)
    core demo all         (combined; both preambles + a "what this means"
                           summary after the combined table)

Per-directory READMEs:

  evals/forward_semantic_control/results/README.md
    - Inventory of every JSON report with headline metrics
    - Per-report interpretation guide ("when to look here")
    - Per-case schema reference
    - "When something looks wrong" troubleshooting tree
    - Cross-links to ADRs, runtime_contracts, findings docs

  evals/forward_semantic_control/public/v2_phase5/README.md
    - The five failure-mode families, geometric construction, and
      expected behaviour per mode
    - Case schemas (single-step + chained) with field semantics
    - How cases were geometrically mined (phase5_mine.py)
    - Authoring rules: add cases, never relax assertions

  evals/forward_semantic_control/public/v2_phase6_demo/README.md
    - The three conditions with case counts and what each proves
    - Why the baseline is in-system (not a transformer LLM) — table
    - Case schema with the `condition` field
    - Authoring rules: surface specific asymmetry, never relax predicate

  evals/forward_semantic_control/public/inner_loop_benign/README.md
    - Why this corpus exists (replaces adversarial-by-accident v1/dev)
    - The Cl(4,1) signature quirk (23/85 tokens with negative
      self-cga_inner) and the 0.25 self-score authoring filter
    - Expected exhaustion_rate per condition
    - How to verify a new case before committing (one-liner snippet)

New contract tests (tests/test_cli_demo.py::TestDemoPreambles + ::TestResultsReadme):
  - Phase 6 preamble explains C1/C2/C3 and the in-system baseline rationale
  - Phase 5 preamble explains all five families AND that δ is falsifiable
  - Preamble suppressed under --json (parseable JSON from byte 0)
  - `demo all` runs both preambles + a "what this means" summary
  - results/README.md mentions every phase report file
  - All three corpus READMEs exist

Tests: 1107 passed, 2 skipped (+8 from preceding baseline).

No mechanism changes — all additions are documentation surface.
2026-05-17 16:39:50 -07:00
Shay
36aad75202 feat(cli): ADR-0024 chain test-suite aliases + core demo subcommand
Two layers of CLI surface so reviewers / investors / industry
observers can run the ADR-0024 chain evidence end-to-end without
typing test file paths or hunting for runner scripts.

Layer 1 — test-suite aliases:
  core test --suite refusal     (Phase 2 typed refusals)
  core test --suite margin      (Phase 3 / ADR-0026 ranked-with-margin)
  core test --suite rotor       (Phase 4 / ADR-0025 rotor admissibility)
  core test --suite inner-loop  (ADR-0024 inner-loop, all 4 sub-tests)
  core test --suite phase5      (stratified mechanism-isolation)
  core test --suite phase6      (3-condition comparative demo)
  core test --suite adr-0024    (full chain, 98 tests, ~2 min)

Layer 2 — `core demo` subcommand:
  core demo phase5              stratified pass/refuse table + per-family
                                breakdown (5 families, both modes)
  core demo phase6              three head-to-head verdicts vs baseline
                                (replay determinism / traced rejection /
                                coherent refusal)
  core demo all                 both phases + combined summary
  core demo list-results        index every JSON report in the central
                                results directory with headline metrics

All demo runs:
  - Write fresh JSON to evals/forward_semantic_control/results/
  - Refresh the results/index.json manifest so reviewers see every
    available report in one place
  - Accept --json for machine-readable output

Central results directory: evals/forward_semantic_control/results/
  phase2_inner_loop_report.json
  phase3_v2_report.json
  phase4_characterization_*.json
  phase5_report.json
  phase5_benign_inner_loop_report.json
  phase6_demo_report.json
  index.json (auto-generated manifest)

Files:
  core/cli.py                   — +9 suite aliases, +cmd_demo (3 targets +
                                  list-results), +index manifest writer
  tests/test_cli_demo.py        — 14 contract tests pinning Layer 1 + 2
  evals/forward_semantic_control/results/index.json — auto-generated

Tests: 1099 passed, 2 skipped (+14 from Phase 6 baseline).
2026-05-17 16:21:37 -07:00
Shay
a0765066b4 feat(adr-0024): Phase 6 — comparative demo, three head-to-head conditions
Closes the 6-phase ADR-0024 chain with a focused comparative demo
that distinguishes CORE (inner-loop + margin + typed refusals) from
the in-system boundary-only baseline (ADR-0023 ablation).

Three conditions, all passing under contract tests:

  C1. Replay determinism
        baseline: 8/8 stable across 5 reruns
        CORE:     8/8 stable across 5 reruns
        CORE additionally folds refusal_reason into trace hash so
        refusal events are replayable evidence.

  C2. Traced rejection
        baseline emits forbidden: 3/3 (admits=False but walk continues)
        CORE corrects-or-refuses:  3/3
        CORE rejection in trace:   3/3
        Demonstrates that inner-loop is causally responsible for the
        selection difference between baseline and CORE.

  C3. Coherent refusal
        baseline typed refusals:        0/3 (never raises typed refusal)
        baseline emits inadmissible:    3/3
        CORE typed refusals:            3/3 (all INNER_LOOP_EXHAUSTION)
        Demonstrates that typed refusal with rejected_attempts evidence
        is new in CORE, not present in boundary-only.

Why in-system baseline (not LLM):
  A transformer-LLM comparison would be non-deterministic by
  construction, could not be CI-enforced, and would be apples-to-
  oranges (different corpus / training / sampling).  The honest
  comparison is the ablation: same codebase with the Phase 2-5
  additions disabled.

Files:
  evals/forward_semantic_control/phase6_demo.py
  evals/forward_semantic_control/public/v2_phase6_demo/cases.jsonl   (8 cases)
  evals/forward_semantic_control/results/phase6_demo_report.json
  tests/test_phase6_demo.py                                          (17 passing)
  docs/evals/phase6_comparative_demo.md

Tests: 1085 passed, 2 skipped (+17 from Phase 5 baseline).

This closes the ADR-0024 6-phase chain:
  Phase 1 — pack-grounded fixture + architectural finding   (3940290)
  Phase 2 — typed refusals + trace fold                     (310793a)
  Phase 3 — ADR-0026 ranked-with-margin                     (639e107)
  Phase 4 — ADR-0025 rotor / frame admissibility            (542e13d)
  Phase 5 — stratified 5-family mechanism-isolation         (b664984)
  Phase 6 — comparative demo                                (this commit)
2026-05-17 16:02:37 -07:00
Shay
b6649848a6 feat(adr-0024): Phase 5 — stratified mechanism-isolation across 5 failure-mode families
Authors a 20-case corpus stratified across five geometric failure-mode
families and a separate 10-case benign corpus for the
EXHAUSTION_CEILING lane:

  A. near_forbidden_correct_endpoint  (6 cases, gaps 0.002 to 0.55)
  B. near_equal_admissible             (5 cases, diffs ≤ 0.01)
  C. no_admissible_path                (3 cases, honest refusal)
  D. multi_step_admissibility          (3 chained cases)
  E. heterogeneous_relation            (3 chained cases, blade-switching)

phase5_runner runs each case under BOTH threshold and ADR-0026 margin
modes and reports per-family pass_rate, refusal_rate, and (for Family
A) rejection_traced_rate + boundary_overridden_rate.

Headline:
  pass_rate_threshold = 1.00 (20/20)
  pass_rate_margin    = 1.00 (20/20)
  mechanism_isolated  = true (both modes, all five families)
  replay determinism  = byte-identical across 3 reruns

Family C refuses with RefusalReason.INNER_LOOP_EXHAUSTION in both
modes (load-bearing evidence for ADR-0024 Phase 2 typed refusals).
Family B refuses under margin mode (validates ADR-0026 δ=0.4 gate).

Benign inner-loop corpus for EXHAUSTION_CEILING ≤ 0.05 gate:
  boundary_only:    exhaustion 0.00, pass 1.00
  null_control:     exhaustion 0.00, pass 1.00
  inner_loop_t0:    exhaustion 0.00, pass 1.00
  inner_loop_tpos:  exhaustion 0.00, pass 1.00 (threshold 0.25)

Geometric finding documented while authoring the benign corpus:
23 of 85 pack tokens have negative self-cga_inner under Cl(4,1).
Tokens with self-score ≤ 0 cannot serve as single-token expected
endpoints in threshold mode — the algebra's Lorentzian signature
forbids this geometrically.  Phase 5 benign corpus draws expected
endpoints from the 62-token positive-self-score subset.  This is
consistent with Phase 4 characterization: no static threshold
delivers separation_quality ≥ 0.8 — the margin lane survives
because margin compares differences, not absolute scores.

Files:
  evals/forward_semantic_control/public/v2_phase5/cases.jsonl
  evals/forward_semantic_control/public/inner_loop_benign/cases.jsonl
  evals/forward_semantic_control/phase5_runner.py
  evals/forward_semantic_control/phase5_mine.py
  evals/forward_semantic_control/results/phase5_report.json
  evals/forward_semantic_control/results/phase5_benign_inner_loop_report.json
  tests/test_phase5_corpus.py        (20 passing)
  docs/evals/phase5_stratified_findings.md

Tests: 1068 passed, 2 skipped (+20 from Phase 4 baseline).
2026-05-17 15:51:59 -07:00
Shay
394029008e feat(adr-0024): Phase 1 addendum — retire v1/dev fixture rot
Rewrite v1+dev FSC cases with pack-grounded tokens drawn from
en_core_cognition_v1. Closes the 9/9 region-construction failure
recorded in Phase 4 (chain_tokens alpha/beta/gamma/delta/etc. were
ungrounded in the active pack).

Token mappings preserve each case's test pattern:
* alpha→beta→gamma→delta  →  tone→evidence→memory→wisdom (causes)
* mu→nu→omicron           →  voice→memory→wisdom (means)
* pi→rho→sigma→tau        →  question→answer→understanding→wisdom (precedes)
* upsilon→phi→chi         →  word→discourse→narrative (part_of)
* eta/theta/zeta + means-distractors → symbol/word/meaning + image/light

Result post-rewrite:
* skipped_count: 9/9 → 0/9 (region constructible)
* causal_attribution_valid: True (preserved)
* code_path_residual: 0.0 (preserved)
* inner_loop_t0 hash stability: 1.0 (preserved)
* best_separation_quality: 0.0 → 0.056 (still below 0.8 gate)

The rewrite exposes a deeper architectural finding documented in the
ADR addendum: v1/dev case schema (prime + chain_tokens) probes
teaching-driven walk (ADR-0022/0023), not the inner-loop's
blade-admissibility mechanism (ADR-0024). The Phase 2 corpus-
observation runner's reuse of v1/dev was a categorical error.
v1/dev belong to the boundary-walk lane (runner.py); v2 belongs to
the inner-loop lane (v2_runner.py). Phase 5 will author the benign
inner-loop corpus the EXHAUSTION_CEILING gate was designed against.

Tests pinning new state:
* TestV1ChainBladeUngrounded → TestV1ChainBladePostGrounding
  (assertions inverted: skipped_count == 0; separation_quality < 0.5)
* TestPhase2 (unchanged) continues to assert causal_attribution_valid
  and hash stability; exhaustion remains a finding, not an invariant.
2026-05-17 14:43:34 -07:00