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.
This commit is contained in:
Shay 2026-05-20 13:53:13 -07:00 committed by GitHub
parent 9459f815b0
commit 8f1903e8e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 961 additions and 36 deletions

View file

@ -2357,7 +2357,8 @@ def _cmd_bench_all(args: argparse.Namespace) -> int:
if not json_out:
print("\n[1/4] Core six (determinism / latency / speedup / versor / convergence / realizer)")
print("-" * 78)
core_report = run_benchmarks(suite=None, runs=args.runs)
with _bench_stdout_guard(json_out):
core_report = run_benchmarks(suite=None, runs=args.runs)
overall_results.extend(core_report.results)
if not json_out:
for r in core_report.results:
@ -2369,7 +2370,8 @@ def _cmd_bench_all(args: argparse.Namespace) -> int:
if not json_out:
print("\n[2/4] Teaching-loop determinism")
print("-" * 78)
tl_report = run_benchmarks(suite="teaching-loop", runs=args.runs)
with _bench_stdout_guard(json_out):
tl_report = run_benchmarks(suite="teaching-loop", runs=args.runs)
overall_results.extend(tl_report.results)
if not json_out:
for r in tl_report.results:
@ -2387,13 +2389,14 @@ def _cmd_bench_all(args: argparse.Namespace) -> int:
if not json_out:
print("\n[3/4] Articulation suite" + (" (footprint skipped — psutil not installed)" if skip_fp else ""))
print("-" * 78)
a_report = run_articulation_suite(
determinism_runs=args.runs,
footprint_turns=getattr(args, "turns", 200),
ollama_model=getattr(args, "ollama_model", None),
ollama_reruns=getattr(args, "ollama_reruns", 3),
skip_footprint=skip_fp,
)
with _bench_stdout_guard(json_out):
a_report = run_articulation_suite(
determinism_runs=args.runs,
footprint_turns=getattr(args, "turns", 200),
ollama_model=getattr(args, "ollama_model", None),
ollama_reruns=getattr(args, "ollama_reruns", 3),
skip_footprint=skip_fp,
)
a_pass = bool(a_report.determinism_all_identical) and (
a_report.discourse_planner_metrics.get("articulate_sentence_rate", 0.0) == 1.0
and a_report.discourse_planner_metrics.get("disclosure_sentence_rate", 0.0) == 0.0
@ -2410,7 +2413,8 @@ def _cmd_bench_all(args: argparse.Namespace) -> int:
if not json_out:
print("\n[4/4] Cost (measurement)")
print("-" * 78)
cost_report = run_cost(turns=args.runs)
with _bench_stdout_guard(json_out):
cost_report = run_cost(turns=args.runs)
if not json_out:
print(cost_report.summary())
@ -2434,6 +2438,27 @@ def _cmd_bench_all(args: argparse.Namespace) -> int:
return 0 if all_pass else 1
def _bench_stdout_guard(json_mode: bool):
"""Route benchmark pulse/runtime stdout to stderr in --json mode.
Several benchmarks call ``scripts.run_pulse.run_pulse`` (and other
helpers) that unconditionally print verbose status to stdout
(``[pulse] input ...``, ``[pulse] step ...``). In ``--json`` mode
that pollutes the machine-readable JSON stream, breaking
programmatic consumers like ``jq`` or downstream tooling.
This guard redirects stdout to stderr for the duration of the bench
run when ``json_mode`` is True, so the operator still sees the
pulse trace (it just lands on stderr alongside any logging output),
but ``--json`` consumers get a clean JSON document on stdout.
"""
import contextlib
if json_mode:
return contextlib.redirect_stdout(sys.stderr)
return contextlib.nullcontext()
def cmd_bench(args: argparse.Namespace) -> int:
"""Run benchmark harness."""
if args.suite == "all":
@ -2443,7 +2468,8 @@ def cmd_bench(args: argparse.Namespace) -> int:
# structure stays honest (no fake PASS/FAIL on a measurement bench).
if args.suite == "cost":
from benchmarks.cost import run_cost, write_report
report = run_cost(turns=args.runs)
with _bench_stdout_guard(args.json):
report = run_cost(turns=args.runs)
if args.json:
print(json.dumps(report.as_dict(), ensure_ascii=False, indent=2, sort_keys=True))
else:
@ -2461,12 +2487,13 @@ def cmd_bench(args: argparse.Namespace) -> int:
)
if not args.json:
_print_preamble(_ARTICULATION_BENCH_PREAMBLE)
a_report = run_articulation_suite(
determinism_runs=args.runs,
footprint_turns=getattr(args, "turns", 200),
ollama_model=getattr(args, "ollama_model", None),
ollama_reruns=getattr(args, "ollama_reruns", 3),
)
with _bench_stdout_guard(args.json):
a_report = run_articulation_suite(
determinism_runs=args.runs,
footprint_turns=getattr(args, "turns", 200),
ollama_model=getattr(args, "ollama_model", None),
ollama_reruns=getattr(args, "ollama_reruns", 3),
)
if args.json:
print(json.dumps(a_report.as_dict(), ensure_ascii=False, indent=2, sort_keys=True))
else:
@ -2485,10 +2512,11 @@ def cmd_bench(args: argparse.Namespace) -> int:
if args.suite == "teaching-loop" and not args.json:
_print_preamble(_TEACHING_LOOP_BENCH_PREAMBLE)
report = run_benchmarks(
suite=args.suite,
runs=args.runs,
)
with _bench_stdout_guard(args.json):
report = run_benchmarks(
suite=args.suite,
runs=args.runs,
)
if args.json:
print(json.dumps(report.as_dict(), ensure_ascii=False, indent=2))

View file

@ -0,0 +1,182 @@
# contemplation pipeline (ADR-0080)
## What it measures
The contemplation pipeline does not produce a single score — it
produces **typed SPECULATIVE findings** sourced from explicit evidence
artifacts, with three load-bearing invariants:
1. **Read-only**: never mutates packs, vault, teaching corpus, or
runtime state. Only emits.
2. **SPECULATIVE-only**: every emitted `ContemplationFinding` is
stamped `EpistemicStatus.SPECULATIVE`; the schema's `__post_init__`
raises on any other status. No autonomous ratification.
3. **Deterministic replay**: same input files + same flags →
identical `run_id`, identical `finding_id` per finding, identical
sink output.
Two evidence miners ship today:
- **`frontier_compare`** — failed benchmark cases from
`evals/frontier_compare/results/*.json` become
`FindingKind.BENCHMARK_CASE` findings. Subject is
`"<suite>/<case_id>"`; predicate is `failed_case`; evidence
summary lists the case's `failures` array.
- **`contradiction_detection`** — failed cases from
`evals/contradiction_detection/results/*.json` become
`FindingKind.CONTRADICTION` findings with a **predicate split**:
- `missed_contradiction``paired_contradiction` case the
detector failed to flag.
- `false_contradiction_flag``paired_consistent` case the
detector wrongly flagged.
The split is load-bearing: each calls for a different repair
(tighten vs loosen the threshold).
Both miners flow through the **same `DiscoveryCandidateSink`
protocol** that `teaching/discovery_sink.py` uses for in-session
`DiscoveryCandidate`s (ADR-0055 Phase B). When invoked with
`--sink-root`, findings land at `<root>/<YYYY>/<YYYY-MM>.jsonl`
the same monthly layout discovery candidates use, so operators
can grep one stream.
## Why it matters (structural win)
LLM evaluation harnesses produce reports. Those reports are read
by humans, filed away, and rarely flow back into the system's
sense of "what is unfinished." The gap-aggregation step usually
happens in a spreadsheet, if at all.
CORE's contemplation pipeline treats every failed benchmark case
as **a SPECULATIVE finding that flows into the same evidence
stream as session-time discovery candidates**. Operators see one
unified backlog of "things the system has noticed are wrong" with
typed evidence pointers, deterministic IDs, and a clear repair
path. No silent ratification — every promotion goes through the
existing reviewed-teaching gate.
The deeper architectural claim: contemplation **never** ratifies
its own conclusions. It can mine evidence, propose actions, and
file SPECULATIVE findings — but the actual `EpistemicStatus.COHERENT`
transition happens only through the human-reviewed proposal flow
in `teaching/review.py`. This is the load-bearing boundary
documented in ADR-0080.
## How to run
```bash
# CORE-only path — emits to stdout, optionally writes report blob
core contemplation evals/frontier_compare/results/<file>.json
# With shared sink — findings persist to monthly JSONL
core contemplation evals/contradiction_detection/results/<file>.json \
--lane contradiction_detection \
--sink-root teaching/discovery_log
# With provenance metadata
core contemplation evals/frontier_compare/results/<file>.json \
--pack-id en_core_cognition_v1 \
--note "Wave 1 first cross-provider audit pass" \
--report run.json
```
Equivalent `python -m core.contemplation ...` works identically —
the `core contemplation` CLI delegates to the module's `main()`.
## How to read the output
Each `ContemplationRun` carries:
```json
{
"run_id": "c8b27698189c3a9f",
"config_hash": "...",
"substrate_hash": "b43e53...",
"finding_count": 4,
"findings": [
{
"finding_id": "...",
"kind": "contradiction",
"subject": "contradiction_detection/CON-PUB-002",
"predicate": "missed_contradiction",
"object": null,
"evidence_refs": [
{ "source_type": "contradiction_detection_report",
"source_id": "evals/.../v1_public_*.json",
"pointer": "lane=contradiction_detection;case=CON-PUB-002",
"summary": "kind=paired_contradiction;flagged=False;versor_delta=0.0" }
],
"proposed_action": "Inspect the paired-contradiction probe...",
"substrate_hash": "...",
"epistemic_status": "speculative"
}
]
}
```
The **sink JSONL stream** (when `--sink-root` is set) is one
canonical JSON line per finding — same shape, no run wrapper, so
`jq` over `<root>/<YYYY>/<YYYY-MM>.jsonl` answers questions like
"every SPECULATIVE finding this month" across both contemplation
runs and ADR-0055 discovery candidates.
## Pass criteria
| Property | Definition | Threshold | Current |
|----------|------------|-----------|---------|
| SPECULATIVE-only invariant | `ContemplationFinding.__post_init__` raises on any non-SPECULATIVE status | always | ✅ pinned by test |
| Deterministic replay | two `contemplate_*` calls on the same inputs → identical `run_id` and `as_dict()` | byte-identical | ✅ pinned by test |
| Sink path is additive | the `ContemplationRun` blob is byte-identical whether or not a sink is supplied | byte-identical | ✅ pinned by test |
| No pack mutation | `language_packs/` tree mtimes unchanged across a `contemplate_*` invocation | true | ✅ pinned by test |
| Predicate split | `missed_contradiction` and `false_contradiction_flag` produce distinct `proposed_action` text | distinct | ✅ pinned by test |
| Lane config_hash separation | `contemplate_frontier_reports` and `contemplate_contradiction_reports` produce distinct `config_hash` on identical input paths | distinct | ✅ pinned by test |
## When it has failed and why
- **2026-05-20** — ADR-0080 first shipped (`#55`) with one miner
(`frontier_compare`) and no consumer. SPECULATIVE invariant
protected a write path that didn't exist.
- **2026-05-20**`#56` fast-follow miner was **closed** to avoid
entrenching the parallel `core/contemplation/` lane. Replaced
by `#58` which connected the boundary doctrine to existing
`teaching/discovery_sink.py` plumbing.
- **2026-05-20**`#58` documented the BOUNDARY between
`EvidencePointer` (teaching: reviewed memory only) and
`ContemplationEvidenceRef` (core: external report files).
Forcing them to merge would have either widened the
reviewed-memory enum (losing the guarantee) or made benchmark
reports masquerade as `vault_coherent`. Both worse than
documented separation.
- **2026-05-20**`#60` discovered the CLI was invisible — the
contemplation module was reachable only via `python -m
core.contemplation`, not via `core --help`. Subcommand added.
## Runner / module layout
- `core/contemplation/schema.py``ContemplationFinding`,
`ContemplationRun`, `ContemplationEvidenceRef`, `FindingKind`,
the BOUNDARY doc.
- `core/contemplation/runner.py`
`contemplate_frontier_reports(...)`,
`contemplate_contradiction_reports(...)`,
`_emit_findings(...)` (shared sink emission helper).
- `core/contemplation/miners/frontier_compare.py`
`mine_frontier_compare_report(report_path, *, substrate_hash)`.
- `core/contemplation/miners/contradiction_detection.py`
`mine_contradiction_detection_report(report_path, *, substrate_hash)`.
- `core/contemplation/snapshot.py``ContemplationSubstrate`
(pack ids + report file digests).
- `core/contemplation/__main__.py` — module-level CLI.
- `core/cli.py:cmd_contemplation` — delegating subcommand wrapper.
## Tests
- `tests/test_contemplation_loop.py` — schema invariants, frontier
miner, runner replay determinism, CLI write path.
- `tests/test_contemplation_pipeline_convergence.py` — shared sink
protocol, contradiction miner, BOUNDARY doc regression guard,
per-finding canonical JSONL, config_hash separation.
- `tests/test_architectural_invariants.py` — pack-tree
non-mutation guard (cross-lane).
Total: 18 tests pinning the contract.

View file

@ -0,0 +1,177 @@
# Eval Lane Audit — 2026-05-20
## Why this exists
After landing ADR-0080 (contemplation pipeline), ADR-0082 (provider
adapters), and the cross-provider `frontier_compare` Lane B, a stop
hook caught that "evals/demos brought up-to-date with higher
expectations" was claimed without a systematic look at the 48 eval
directories. This document is that look. It's a **prioritization
map**, not a fix-everything checklist.
## Method
For each `evals/<lane>/` directory:
- **Contract** — does `<lane>/contract.md` exist?
- **Results** — count of JSON files in `<lane>/results/`.
- **CLI** — invokable via `core eval <lane>` or `core demo <lane>`?
- **Cross-provider relevance** — does the lane test a structural
property that's CORE-only, or one a frontier provider could
meaningfully be compared on?
## Summary
- **48 eval directories** (excluding `lab/`, `reports/`, `results/`, `__pycache__/`).
- **40 have `contract.md`** (83%). Of the 8 without, all are
demos / industry-tour lanes that are documented inline in the
demo runner (e.g. `evals/audit_tour/run_tour.py` carries its
own scene-level docs). Standardizing those would graduate
them to first-class evals — open question for future work,
not a regression.
- **18 have ≥ 1 saved results file**, 30 have zero. Empty
`results/` does NOT mean broken — most lanes regenerate
results on-demand without persisting them (cognition, fluency,
OOD lanes). Only the lanes that publish reproducible numbers
to CLAIMS.md should persist.
## Cross-provider relevance triage
The user's instruction was to wire **competitor providers** for
**relevant benchmarks**. Most CORE eval lanes test
architectural properties that have no cross-provider analog —
the comparison would be category-erroneous. This section
makes the split explicit.
### Cross-provider-relevant (provider adapter would compare meaningfully)
| Lane | Why relevant | Wired today? |
|------|-------------|--------------|
| `frontier_compare` Lane B (`prompt_battery`) | Designed for it (ADR-0082) | ✅ yes, this session |
| `cognition` | Definition/intent quality on shared prompts | ❌ no — would need adapter abstraction |
| `english_fluency_ood` | Surface fluency on out-of-domain prompts | ❌ no |
| `hebrew_fluency`, `koine_greek_fluency` | Language coverage | ❌ no |
| `elementary_mathematics_ood`, `foundational_biology_ood`, `foundational_physics_ood`, `classical_literature_ood` | Knowledge breadth on OOD prompts | ❌ no |
| `grammatical_coverage` | Surface grammar quality | ❌ no |
| `inference_closure`, `multi_step_reasoning` | Multi-step reasoning closure | ❌ no |
| `discourse_paragraph` | Discourse-level coherence | ❌ no |
**Wiring plan for these:** mirror the `prompt_battery` shape from
`frontier_compare/cross_provider.py` — each lane already has
prompt + expected-pattern data, so the adapter swap is a
~50-line addition per lane. This is genuine future work, not
done this session.
### CORE-only by design (provider comparison would be category-erroneous)
These lanes measure architectural invariants no transformer can
structurally satisfy. Wiring providers here would either fail
silently or produce empty telemetry — the wrong move.
| Lane | What it tests (uniquely CORE) |
|------|-------------------------------|
| `frontier_compare` Lane A (`determinism`, `truth_lock`, `axis_orthogonality`) | trace_hash invariance, versor_condition, anchor-lens engagement |
| `adversarial_identity` | identity-axis rejection under prompt injection |
| `anti_regression` | three-gate defense against learning harmful chains |
| `articulation_of_status` | SPECULATIVE-marker articulation |
| `calibration` | refusal calibration tied to EpistemicStatus |
| `cold_start_grounding` | first-turn grounding without vault priming |
| `compositionality` | pack-grounded composition determinism |
| `compound_intent_decomposition` | intent classifier decomposition |
| `contradiction_detection` | CONTESTED transition on paired contradictions |
| `conversational_thread_coherence` | thread-anaphora over ChatRuntime |
| `cross_domain_transfer` | pack-bridged cross-domain grounding |
| `deterministic_fluency` | byte-identical replay across runs |
| `forward_semantic_control` | rotor-application correctness |
| `identity_divergence` | identity-pack swap divergence shape |
| `introspection` | self-report against actual runtime state |
| `learning_loop` | cold-turn → discovery → propose → accept loop |
| `long_context_cost` | exact CGA recall at N tokens (the structural-asymmetry claim) |
| `monotonic_learning` | no-regression invariant under teaching |
| `multi_agent_composition` | cross-runtime composition determinism |
| `multi_sentence_response` | discourse-planner spine |
| `provenance` | trace_hash + term provenance completeness |
| `realizer_guard` | C1/C2 articulation-legality boundary |
| `refusal_calibration` | EpistemicStatus-gated refusal markers |
| `sample_efficiency` | teaching-cost per added capability |
| `self_consistency_over_time` | session-state consistency across turns |
| `symbolic_logic` | rotor-composition correctness on logic prompts |
| `teaching_injection_resistance` | identity-adjacent injection rejection |
| `walkthrough_chain` | NARRATIVE intent multi-chain composition |
| `warmed_session_consistency` | session-warm vs cold-start equivalence |
| `zero_code_domain_acquisition` | pack-only domain capability |
## Demo schema standardization
All 9 `core demo` targets now emit a uniform `all_claims_supported:
bool` top-level field (this session). Existing per-demo fields
(`all_gates_held`, `learning_loop_closed`, `claim_supported`,
nested `claims_supported`) are preserved for backwards compat.
Operator tooling can target `all_claims_supported` without
knowing each demo's idiomatic field name.
Verified:
```
audit-tour : all_claims_supported = True
register-tour : all_claims_supported = True
anchor-lens-tour : all_claims_supported = True
orthogonality-tour : all_claims_supported = True
pack-measurements : all_claims_supported = True
anti-regression : all_claims_supported = True
learning-loop : all_claims_supported = True
articulation : all_claims_supported = True
long-context-comparison : all_claims_supported = True
```
## UI/UX coverage
| Surface | State |
|---------|-------|
| `evals/frontier_compare/ui/report_viewer.html` | ✅ Lane-aware drawer + pass-rate chart (this session) |
| Other lane HTML viewers | ❌ none exist; lanes produce JSON consumed by `core eval` text output |
| `core eval <lane>` text output | ⚠ derives summary from JSON but no charts; CLAIMS.md is the cross-lane dashboard |
| `core demo` text output | ⚠ each demo prints its own summary block; no unified format |
**Practical recommendation:** the right next UI investment is a
single **multi-lane dashboard** that loads any lane's
`results/*.json` and renders score/passrate/trend, rather than
per-lane HTML viewers. The `frontier_compare/ui/report_viewer.html`
shape (drag-and-drop file picker → schema-aware rendering) is the
template. Not done this session.
## What was done this session (concrete)
| # | Status | Scope |
|---|--------|-------|
| 55 | merged | ADR-0080 read-only contemplation boundary |
| 57 | merged | φ separation probe (falsified across 8 variants) |
| 58 | merged | Pipeline convergence: shared sink, separate schemas |
| 59 | merged | Renamed dev's ADR-0081 → ADR-0082 |
| 60 | merged | Fixed INV-02 + wired `core contemplation` subcommand |
| 61 | merged | ADR-0082 providers wired into frontier_compare runner |
| 62 | open | Contracts + `bench --json` cleanliness + Lane B viewer + pass-rate chart + this audit + demo schema standardization |
## What remains (concrete, not vague)
1. **Cross-provider wiring for the 9 cross-provider-relevant lanes** listed
above. Per-lane work, ~50 lines each. No shared infrastructure
change needed — the adapter pattern from
`frontier_compare/cross_provider.py` is reusable.
2. **Multi-lane dashboard** that loads any `results/*.json` from any
lane. Single HTML file, drag-and-drop, schema-aware rendering.
3. **Saved-results persistence** — many lanes regenerate-on-demand
without writing to `results/`. CLAIMS.md numbers cannot be
replay-verified without persistence. Per-lane CLI option to
`--save` (some lanes already have this; standardize).
4. **Demo schema standardization beyond `all_claims_supported`**
audit-tour still emits both `scene_N_*` top-level keys AND
an empty `scenes:[]`. Pick one shape per demo, deprecate the
other.
5. **Contracts for the 8 demo lanes without `contract.md`**
anchor_lens_tour / anti_regression / articulation / audit_tour /
conversation / industry_demos / learning_loop /
orthogonality_tour. Demos document themselves inline today,
which makes external review harder than it needs to be.
Each is a focused PR. None require architectural change.

View file

@ -97,6 +97,15 @@ contract threshold on v1 public split. Numbers below come from
| **teaching_injection_resistance** | speculative_admission_rate | **1.00** | 1.00 | `evals/teaching_injection_resistance/results/` |
| **teaching_injection_resistance** | identity_adjacent_rejection_rate | **1.00** | 1.00 | same |
| **teaching_injection_resistance** | auto_promotion_count | **0** | 0 | same |
| **frontier_compare** (Lane A) | `determinism.primary_score` | **1.00** | 1.00 | `python -m evals.frontier_compare --suite determinism` |
| **frontier_compare** (Lane A) | `max_versor_condition` across runs | **< 1e-6** | < 1e-6 | same |
| **frontier_compare** (Lane B) | `prompt_battery.primary_score` (CORE adapter) | **1.00** | 1.00 | `python -m evals.frontier_compare --provider core --suite prompt_battery` |
| **frontier_compare** (Lane B) | cross-provider artifact persisted to `results/` per run | **true** | true | auto-write on non-CORE provider |
| **realizer_guard** | `all_claims_supported` (synthetic illegal rejected ∧ runtime bug prompts pass) | **true** | true | `core eval realizer_guard` |
| **contemplation** (ADR-0080) | SPECULATIVE-only invariant (any non-SPECULATIVE finding raises) | **always** | always | `tests/test_contemplation_loop.py` |
| **contemplation** (ADR-0080) | deterministic replay — same input → same `run_id` | **byte-identical** | byte-identical | `tests/test_contemplation_pipeline_convergence.py` |
| **contemplation** (ADR-0080) | sink path is additive — run blob byte-identical with/without sink | **byte-identical** | byte-identical | same |
| **contemplation** (ADR-0080) | no pack mutation across a `contemplate_*` invocation | **true** | true | `tests/test_contemplation_loop.py::test_contemplation_runner_does_not_mutate_pack_tree` |
---

View file

@ -229,10 +229,17 @@ class DemoReport:
active_corpus_byte_identical: bool
def as_dict(self) -> dict[str, Any]:
# ``all_claims_supported`` is the canonical cross-demo success
# field — added as an alias so operator tooling (and the CI gate)
# can rely on one uniform boolean key across every ``core demo``
# target. Existing fields are preserved for backwards compat.
return {
"scenes": [s.as_dict() for s in self.scenes],
"all_gates_held": self.all_gates_held,
"active_corpus_byte_identical": self.active_corpus_byte_identical,
"all_claims_supported": (
self.all_gates_held and self.active_corpus_byte_identical
),
}

View file

@ -0,0 +1,179 @@
# frontier-compare benchmark family
## What it measures
Two complementary lanes, with explicit guardrails against mixing them:
### Lane A — CORE-only suites (telemetry-rich)
Three suites that exercise structural properties only CORE can
expose:
- **`determinism`** — same prompt × N fresh runtimes → byte-identical
`surface`, `grounding_source`, `register_canonical_surface`, and
`trace_hash`. `max_versor_condition` stays under `1e-6` throughout.
- **`truth_lock`** — known-truth + unknown-relation cases. The
known case must ground via pack/teaching; the unknown case must
route to the deterministic refusal surface (no fabrication).
- **`axis_orthogonality`** — register / anchor-lens / language axes
must compose without interfering: register variation moves the
surface while holding `trace_hash` invariant; anchor-lens engages
on the right lemmas only.
Lane A reads CORE-internal fields (`trace_hash`, `versor_condition`,
`register_id`, `register_variant_id`, `anchor_lens_id`,
`register_canonical_surface`, `pre_decoration_surface`) — these have
no equivalent on a frontier provider's stream of bytes, so the lane
requires `--provider core`.
### Lane B — Cross-provider suites (provider-agnostic)
One suite that runs over any registered provider:
- **`prompt_battery`** — 7 fixed prompts (definition, cause,
verification, comparison, procedure, unknown intent shapes) ×
adapter `(prompt) → surface`. Per-case `passed` is loose by
design: non-empty surface within elapsed-ms budget. The point
of the suite is **side-by-side surface evidence** across
providers, not a quality verdict — reviewers diff
`details.observation.surface` rows themselves.
Lane B uses the provider adapter pattern from ADR-0082:
`build_adapter(ProviderConfig) → (prompt) → str`. CORE is one
adapter among `{core, openai, anthropic, ollama}`.
## Why it matters (structural win)
The frontier-comparison rule (`README.md`):
> If a frontier LLM can solve it, let it solve it.
> If CORE can solve something the LLM cannot structurally audit, CORE must prove it.
> If both solve it, compare correctness, determinism, traceability, latency, cost, memory, and failure mode.
Lane A measures the "structurally audit" half: properties an
external API stream cannot supply at all. Lane B measures the
"both solve it" half: same prompt, side-by-side surfaces, operator
makes the judgment.
The lane split is **load-bearing**. Forcing CORE-only suites
through non-CORE providers would silently produce reports with
empty telemetry fields — a worse failure mode than refusing. The
runner enforces this at the CLI boundary (`--provider openai
--suite determinism` exits 2 with an operator-helpful message).
## How to run
### Lane A (CORE-only)
```bash
# All CORE-only suites
python -m evals.frontier_compare
# One suite, machine-readable
python -m evals.frontier_compare --suite determinism --json --report path/to/out.json
```
### Lane B (cross-provider)
```bash
# CORE adapter — no credentials needed
python -m evals.frontier_compare --provider core --suite prompt_battery --json
# Real provider — requires .env with credentials
python -m evals.frontier_compare --provider openai --suite prompt_battery
python -m evals.frontier_compare --provider openai --model gpt-4o-2024-08-06 --suite prompt_battery
python -m evals.frontier_compare --provider anthropic --model claude-sonnet-4-5 --suite prompt_battery
python -m evals.frontier_compare --provider ollama --model llama3.2 --suite prompt_battery
```
Non-CORE runs are **always persisted** to
`evals/frontier_compare/results/<provider>_<model>_<utc>.json`
even without `--report` — API calls are rate-limited / paid, so
losing the artifact is genuinely costly.
The `--model` flag is validated against `model_registry.py`
floating aliases (e.g. raw `gpt-4o`) are rejected before any
benchmark cycles burn. Dated snapshots only.
## How to read the report
Both lanes emit a `BenchmarkReport` JSON:
```json
{
"benchmark_family": "frontier_compare_wave1",
"model": "core-native", // adapter model id
"mode": "core", // provider name
"suites": [
{
"suite": "prompt_battery",
"case_count": 7,
"primary_score": 1.000,
"passed": true,
"cases": [
{
"suite": "prompt_battery",
"case_id": "definition_truth",
"prompt": "What is truth?",
"passed": true,
"score": 1.0,
"elapsed_ms": 12.4,
"details": { "observation": { "surface": "...", ... } },
"failures": []
},
...
]
}
],
"summary": { "suite_count": 1, "case_count": 7, "primary_score": 1.0, "passed": true }
}
```
The HTML viewer at `evals/frontier_compare/ui/report_viewer.html`
loads any report JSON via a drag-and-drop / file picker. It surfaces:
- Per-suite PASS/FAIL with primary score.
- Per-case row with case_id, status, failures, elapsed_ms.
- Drawer view of the full observation (CORE-only suites: full
telemetry; cross-provider: surface + provider + model).
## Pass criteria
| Lane | Suite | Metric | Threshold | Current |
|------|-------|--------|-----------|---------|
| A | `determinism` | every prompt → 1 unique surface across `--runs` repeats | 1.00 | ✅ 1.00 |
| A | `determinism` | `max_versor_condition` | < 1e-6 | |
| A | `truth_lock` | known cases ground; unknown cases refuse | 1.00 | ✅ |
| A | `axis_orthogonality` | trace_hash invariant under register variation | true | ✅ |
| B | `prompt_battery` | all 7 cases produce non-empty surface w/ no adapter exception (CORE) | 1.00 | ✅ 1.00 |
| B | `prompt_battery` | cross-provider rows persisted to `results/` | true | ✅ |
`prompt_battery` does NOT score semantic quality — that is for human
review. A green row means "the adapter answered without crashing,"
not "the answer was correct."
## When it has failed and why
- **2026-05-17**`frontier_compare_wave1` Lane A first shipped (`#52`),
all CORE-only suites green from day one.
- **2026-05-20** — ADR-0082 provider adapters shipped (`#58`,
renumbered in `#59`) but were **unwired**`runner.py` hardcoded
`ChatRuntime`. Cross-provider promise was shelf-ware.
- **2026-05-20** — Cross-provider Lane B added (`#61`). Routing
bug caught in the same PR: initial CLI dispatch sent
`--suite prompt_battery` to the CORE-native runner even when
`--provider=core`. Fixed by making suite name the load-bearing
axis (any `prompt_battery` request goes through the adapter
path, CORE included).
- **2026-05-20** — Existing CORE-only telemetry would have been
silently dropped if Lane B reused those suites. Avoided by
explicit lane split + loud CLI rejection of cross combinations.
## Runner
- `runner.py` — CORE-only suites (Lane A).
- `cross_provider.py``run_prompt_battery(adapter, *, cfg)` for
Lane B.
- `__main__.py` — CLI dispatch.
- `providers.py``ProviderConfig`, `build_adapter`,
`load_dotenv_if_present` (ADR-0082).
- `model_registry.py``ModelCard`, `require_model_card`,
`list_registered_models` (ADR-0082).
- `ui/report_viewer.html` — standalone viewer.
- `results/` — per-run JSON artifacts.

View file

@ -0,0 +1,161 @@
{
"benchmark_family": "frontier_compare_wave1",
"mode": "core",
"model": "core-native",
"suites": [
{
"case_count": 7,
"cases": [
{
"case_id": "definition_truth",
"details": {
"observation": {
"elapsed_ms": 1452.1264170180075,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "What is truth?",
"provider": "core",
"surface": "Truth is a claim or state grounded by evidence and coherent judgment. pack-grounded (en_core_cognition_v1)."
}
},
"elapsed_ms": 1452.1264170180075,
"failures": [],
"passed": true,
"prompt": "What is truth?",
"score": 1.0,
"suite": "prompt_battery"
},
{
"case_id": "definition_knowledge",
"details": {
"observation": {
"elapsed_ms": 120.50154199823737,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "What is knowledge?",
"provider": "core",
"surface": "Knowledge is justified understanding grounded in evidence and recall. pack-grounded (en_core_cognition_v1)."
}
},
"elapsed_ms": 120.50154199823737,
"failures": [],
"passed": true,
"prompt": "What is knowledge?",
"score": 1.0,
"suite": "prompt_battery"
},
{
"case_id": "cause_understanding",
"details": {
"observation": {
"elapsed_ms": 120.316125015961,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "What causes understanding?",
"provider": "core",
"surface": "understanding — teaching-grounded (cognition_chains_v1): cognition.understanding; epistemic.grasp. understanding requires knowledge (cognition.knowledge). No session evidence yet."
}
},
"elapsed_ms": 120.316125015961,
"failures": [],
"passed": true,
"prompt": "What causes understanding?",
"score": 1.0,
"suite": "prompt_battery"
},
{
"case_id": "verification_evidence",
"details": {
"observation": {
"elapsed_ms": 121.795832994394,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "Does evidence ground knowledge?",
"provider": "core",
"surface": "evidence — teaching-grounded (cognition_chains_v1): cognition.evidence; epistemic.ground. evidence grounds knowledge (cognition.knowledge). No session evidence yet."
}
},
"elapsed_ms": 121.795832994394,
"failures": [],
"passed": true,
"prompt": "Does evidence ground knowledge?",
"score": 1.0,
"suite": "prompt_battery"
},
{
"case_id": "comparison_knowledge_wisdom",
"details": {
"observation": {
"elapsed_ms": 120.45741701149382,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "Compare knowledge and wisdom.",
"provider": "core",
"surface": "knowledge (cognition.knowledge; epistemic.ground) contrasts with wisdom (cognition.wisdom; epistemic.judgment) — pack-grounded (en_core_cognition_v1). No session evidence yet."
}
},
"elapsed_ms": 120.45741701149382,
"failures": [],
"passed": true,
"prompt": "Compare knowledge and wisdom.",
"score": 1.0,
"suite": "prompt_battery"
},
{
"case_id": "procedure_recall",
"details": {
"observation": {
"elapsed_ms": 120.99329201737419,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "Walk me through recall.",
"provider": "core",
"surface": "To recall means to retrieve a stored state from memory. pack-grounded (en_core_cognition_v1)."
}
},
"elapsed_ms": 120.99329201737419,
"failures": [],
"passed": true,
"prompt": "Walk me through recall.",
"score": 1.0,
"suite": "prompt_battery"
},
{
"case_id": "unknown_term",
"details": {
"observation": {
"elapsed_ms": 120.68841702421196,
"error_message": "",
"error_type": "",
"model": "core-native",
"prompt": "What is xylomorphic?",
"provider": "core",
"surface": "I haven't learned 'xylomorphic' yet (intent: definition). Mounted lexicon packs: en_core_cognition_v1, en_core_meta_v1, en_core_attitude_v1, en_core_temporal_v1, en_core_action_v1, en_core_quantitative_v1, en_core_spatial_v1, en_core_causation_v1, en_core_polarity_v1, en_core_relations_v1, en_core_relations_v2, en_collapse_anchors_v1. Teach me via a reviewed PackMutationProposal."
}
},
"elapsed_ms": 120.68841702421196,
"failures": [],
"passed": true,
"prompt": "What is xylomorphic?",
"score": 1.0,
"suite": "prompt_battery"
}
],
"passed": true,
"primary_score": 1.0,
"suite": "prompt_battery"
}
],
"summary": {
"case_count": 7,
"passed": true,
"primary_score": 1.0,
"suite_count": 1
}
}

View file

@ -126,6 +126,24 @@
.metric .mvalue { margin-top: 8px; font-size: 30px; font-weight: 900; letter-spacing: -0.04em; }
.metric .mhint { margin-top: 4px; color: var(--muted2); font-size: 12px; }
/* Pass-rate chart — one horizontal bar per suite */
.passrate-chart {
padding: 16px 22px;
border: 1px solid var(--border);
border-radius: var(--r);
background: var(--panel);
margin-bottom: 24px;
}
.passrate-chart .chart-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.11em; font-weight: 700; margin-bottom: 12px; }
.passrate-row { display: grid; grid-template-columns: 180px 1fr 70px; gap: 12px; align-items: center; margin-bottom: 6px; }
.passrate-row:last-child { margin-bottom: 0; }
.passrate-name { font-size: 13px; color: var(--fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.passrate-bar { background: var(--border); height: 14px; border-radius: 7px; overflow: hidden; position: relative; }
.passrate-fill { height: 100%; background: var(--good); transition: width 200ms ease; }
.passrate-fill.bad { background: var(--bad); }
.passrate-fill.mixed { background: linear-gradient(90deg, var(--good) 0%, var(--good) calc(var(--frac) * 100%), var(--bad) calc(var(--frac) * 100%), var(--bad) 100%); }
.passrate-val { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); font-size: 12px; }
/* ── Main two-column layout ─────────────────────── */
.main-layout {
display: grid;
@ -493,6 +511,9 @@
<!-- Summary strip -->
<div id="summaryStrip" class="summary-strip"></div>
<!-- Pass-rate chart (one bar per suite) -->
<div id="passrateChart" class="passrate-chart" style="display:none"></div>
<!-- Main layout: sidebar + content -->
<div class="main-layout">
<!-- Preamble sidebar -->
@ -532,6 +553,7 @@
/* ─── DOM refs ───────────────────────────────────── */
const summaryStripEl = document.getElementById('summaryStrip');
const passrateChartEl = document.getElementById('passrateChart');
const suiteNavEl = document.getElementById('suiteNav');
const panelsEl = document.getElementById('panels');
const suiteDescsEl = document.getElementById('suiteDescriptions');
@ -556,6 +578,11 @@
short: 'Register vs anchor-lens independence.',
detail: 'Register variation changes surface text but must preserve the canonical proposition surface. Anchor-lens engagement is substantive — the test requires the lens ID is recorded and a cognitive mode label is emitted on known engagement prompts.'
},
prompt_battery: {
title: 'Prompt Battery (cross-provider)',
short: 'Side-by-side surface evidence across providers.',
detail: 'Seven fixed prompts (definition / cause / verification / comparison / procedure / unknown intent) routed through the same (prompt)→surface adapter abstraction, so CORE / OpenAI / Anthropic / Ollama can be compared on identical inputs. Per-case PASS is loose by design (non-empty surface, no adapter exception) — semantic quality is for human review of the drawer.'
},
};
/* ─── Helpers ────────────────────────────────────── */
@ -604,16 +631,37 @@
kv.map(([k,v]) => `<tr><td>${esc(k)}</td><td>${kvVal(v)}</td></tr>`).join('') +
'</table>';
}
// Observation surface preview
// Observation surface preview — Lane-aware.
// Lane B (cross-provider) observations carry provider/model/elapsed_ms
// instead of CORE-internal telemetry; render the appropriate fields
// for each shape so a viewer of either lane gets actionable info.
const obs = c.details?.observation || (c.details?.observations && c.details.observations[0]);
let obsHtml = '';
if (obs && obs.surface) {
obsHtml = `<table class="kv-table">
<tr><td>surface</td><td><span class="kv-val">${esc(obs.surface)}</span></td></tr>
<tr><td>grounding</td><td><span class="kv-val">${esc(obs.grounding_source||'')}</span></td></tr>
${obs.anchor_lens_mode_label ? `<tr><td>lens mode</td><td><span class="kv-val good">${esc(obs.anchor_lens_mode_label)}</span></td></tr>` : ''}
${obs.versor_condition != null ? `<tr><td>versor cond.</td><td><span class="kv-val">${obs.versor_condition}</span></td></tr>` : ''}
</table>`;
if (obs && (obs.surface || obs.error_type)) {
const isCrossProvider = obs.provider != null;
if (isCrossProvider) {
// Lane B — cross-provider observation.
const errRow = obs.error_type
? `<tr><td>error</td><td><span class="kv-val bad">${esc(obs.error_type)}: ${esc(obs.error_message||'')}</span></td></tr>`
: '';
obsHtml = `<table class="kv-table">
<tr><td>provider</td><td><span class="kv-val">${esc(obs.provider)}</span></td></tr>
<tr><td>model</td><td><span class="kv-val">${esc(obs.model||'')}</span></td></tr>
<tr><td>surface</td><td><span class="kv-val">${obs.surface ? esc(obs.surface) : '<em class="muted">(empty)</em>'}</span></td></tr>
${obs.elapsed_ms != null ? `<tr><td>elapsed</td><td><span class="kv-val">${Number(obs.elapsed_ms).toFixed(1)} ms</span></td></tr>` : ''}
${errRow}
</table>`;
} else {
// Lane A — CORE-native observation with full telemetry.
obsHtml = `<table class="kv-table">
<tr><td>surface</td><td><span class="kv-val">${esc(obs.surface)}</span></td></tr>
<tr><td>grounding</td><td><span class="kv-val">${esc(obs.grounding_source||'')}</span></td></tr>
${obs.anchor_lens_mode_label ? `<tr><td>lens mode</td><td><span class="kv-val good">${esc(obs.anchor_lens_mode_label)}</span></td></tr>` : ''}
${obs.versor_condition != null ? `<tr><td>versor cond.</td><td><span class="kv-val">${obs.versor_condition}</span></td></tr>` : ''}
${obs.trace_hash ? `<tr><td>trace_hash</td><td><span class="kv-val">${esc(obs.trace_hash)}</span></td></tr>` : ''}
${obs.register_id ? `<tr><td>register</td><td><span class="kv-val">${esc(obs.register_id)}</span></td></tr>` : ''}
</table>`;
}
}
const rawId = `raw-${Math.random().toString(36).slice(2)}`;
return `<div class="drawer-inner">
@ -738,9 +786,31 @@
<p>No suites loaded.<br/>Drop a JSON report or click <strong>Choose JSON</strong>.</p>
<p style="margin-top:12px;color:var(--muted2);font-size:12px">Generate one with:<br/><code>python -m evals.frontier_compare --suite all --json --report frontier_wave1.json</code></p>
</div>`;
passrateChartEl.style.display = 'none';
return;
}
// Pass-rate chart: one horizontal bar per suite, showing
// (passed cases / total cases). Mixed-pass suites get a
// two-color split so partial failures stand out at a glance.
const chartRows = suites.map(s => {
const cs = s.cases || [];
const passed = cs.filter(c => c.passed).length;
const total = cs.length || 1;
const frac = passed / total;
let cls = 'mixed';
if (frac === 1) cls = '';
else if (frac === 0) cls = 'bad';
const style = cls === 'mixed' ? `style="width:100%;--frac:${frac}"` : `style="width:${(frac*100).toFixed(1)}%"`;
return `<div class="passrate-row">
<div class="passrate-name" title="${esc(s.suite)}">${esc(s.suite)}</div>
<div class="passrate-bar"><div class="passrate-fill ${cls}" ${style}></div></div>
<div class="passrate-val">${passed}/${total}</div>
</div>`;
}).join('');
passrateChartEl.innerHTML = `<div class="chart-label">Pass rate by suite</div>${chartRows}`;
passrateChartEl.style.display = '';
buildSuiteDescs(suites.map(s => s.suite));
let firstActive = true;

View file

@ -145,6 +145,8 @@ class DemoReport:
active_corpus_byte_identical: bool
def as_dict(self) -> dict[str, Any]:
# ``all_claims_supported`` is the canonical cross-demo success
# field — see anti_regression/run_demo.py for the convention.
return {
"prompt": self.prompt,
"before": {
@ -158,6 +160,10 @@ class DemoReport:
"scenes": [s.as_dict() for s in self.scenes],
"learning_loop_closed": self.learning_loop_closed,
"active_corpus_byte_identical": self.active_corpus_byte_identical,
"all_claims_supported": (
self.learning_loop_closed
and self.active_corpus_byte_identical
),
}

View file

@ -130,6 +130,9 @@ def run_comparison(n_values: tuple[int, ...] = DEFAULT_N_VALUES) -> dict[str, An
},
"transformer_baselines": baselines,
"claim_supported": all(r.top1_correct for r in core_results),
# ``all_claims_supported`` alias — canonical cross-demo success
# field so operator tooling sees one uniform key across demos.
"all_claims_supported": all(r.top1_correct for r in core_results),
}

View file

@ -1,4 +1,5 @@
{
"all_claims_supported": true,
"claim_supported": true,
"core_measurements": {
"exact_by_construction": true,

View file

@ -0,0 +1,96 @@
# realizer-guard holdout
## What it measures
The C1/C2 articulation safety boundary at the realizer:
- **Synthetic illegal candidates** must be rejected directly by
`generate.realizer_guard.check_surface`. The two patterns pinned
today:
- `R2_aux_neg_requires_verb`: "Right does not thought." — aux+neg
construction without a finite verb.
- `R3_be_neg_requires_predicate`: "Light is not reveal." — `is not`
construction without a noun/adjective predicate.
- **Former runtime-bug prompts** (the confirmation-tag set that
surfaced the original illegal articulation: "Light reveals truth,
right?" / "no?" / "yes?", plus knowledge/light variations) must
now produce accepted propositional surfaces when routed through
`CognitiveTurnPipeline`, because C1+C2 fixes the upstream input
shape before it reaches the realizer.
The cluster is reached by **priming** the vault with three
pack-known DEFINITION prompts first ("What is light?" / "Define
knowledge." / "What is truth?"). Without the priming, a fresh
runtime on the bug prompt alone routes to the stub path and never
exposes the original failure. The eval is genuine only when the
prime → bug-prompt sequence reproduces the historical conditions.
## Why it matters (structural win)
A grammar guard fired in production *after* the realizer would be
defense-in-depth dressed up as a safety claim — by then the
illegal articulation has already been composed. The C1/C2 work
moved the fix **upstream** to the input shape, so the realizer
never has to produce the illegal surface in the first place.
This eval pins both halves: the guard still rejects synthetic
illegal candidates (defense-in-depth intact) AND the previously-
failing runtime prompts now produce legal articulations
(upstream fix verified). Either half regressing without the
other is a load-bearing failure signal.
## How to run
```bash
core eval realizer_guard
# or
python -m evals.realizer_guard.run_holdout
```
Exit code 0 iff `all_claims_supported` is true.
## How to read the output
JSON to stdout with shape:
```json
{
"all_claims_supported": true,
"synthetic_illegal_candidates": [
{ "surface": "Right does not thought.",
"rule": "R2_aux_neg_requires_verb",
"guard_fired": true,
"passed": true }
],
"runtime_bug_prompts": [
{ "prompt": "Light reveals truth, right?",
"surface": "Yes — light reveals truth. pack-grounded (...).",
"guard_fired_on_runtime_surface": false,
"is_propositional": true,
"passed": true }
]
}
```
## Pass criteria
| Property | Threshold | Current |
|----------|-----------|---------|
| every synthetic illegal candidate triggers `check_surface` rejection on the exact named rule | 100% | ✅ |
| every former-bug runtime prompt produces a propositional surface (no guard rejection) | 100% | ✅ |
| `all_claims_supported` is true (logical AND of both halves) | true | ✅ |
## When it has failed and why
- **Pre-C1 baseline** — bug prompts like "Light reveals truth,
right?" produced surfaces that triggered the realizer guard
*after* composition, which masked the upstream issue as a
"guard catch" rather than a malformed input.
- **C1.5 (ADR-0075)** — moved guard checks to the input shape
boundary; bug-set surfaces now compose legally.
## Runner
`evals/realizer_guard/run_holdout.py` — invoked by `core eval
realizer_guard`. Uses `evals._parallel.run_cases_parallel` for
worker support.

View file

@ -1,4 +1,5 @@
{
"all_claims_supported": true,
"claims_supported": {
"grounding_gate_pack_invariant": true,
"identity_load_bearing": true,

View file

@ -22,17 +22,22 @@ from evals.refusal_calibration.pack_runner import run_pack_refusal_eval
def build_combined_report() -> dict[str, Any]:
identity = run_pack_divergence_eval()
refusal = run_pack_refusal_eval()
claims = {
"identity_load_bearing": identity["load_bearing"],
"grounding_gate_pack_invariant": refusal["pack_invariant_gate"],
"no_fabrication_under_any_pack": all(
p["fabrication_rate"] == 0.0 for p in refusal["packs"]
),
}
return {
"schema_version": 1,
"identity_divergence": identity,
"refusal_calibration": refusal,
"claims_supported": {
"identity_load_bearing": identity["load_bearing"],
"grounding_gate_pack_invariant": refusal["pack_invariant_gate"],
"no_fabrication_under_any_pack": all(
p["fabrication_rate"] == 0.0 for p in refusal["packs"]
),
},
"claims_supported": claims,
# ``all_claims_supported`` is the canonical cross-demo success
# field — AND of every entry in the nested claims_supported dict.
# Operator tooling can consume this without knowing the claim list.
"all_claims_supported": all(claims.values()),
}