* 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.
109 lines
3.5 KiB
Python
109 lines
3.5 KiB
Python
"""Publish combined Phase-2 pack-measurement report (ADR-0043).
|
||
|
||
Runs both pack-driven runners (identity-divergence + refusal-calibration)
|
||
and writes a unified report to
|
||
`evals/results/phase2_pack_measurements.json` plus a per-runner copy
|
||
under each lane's own `results/packs_v1/` directory.
|
||
|
||
Usage:
|
||
PYTHONPATH=. python3 scripts/publish_pack_measurements.py [--json]
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import json
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from evals.identity_divergence.pack_runner import run_pack_divergence_eval
|
||
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": 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()),
|
||
}
|
||
|
||
|
||
def write_report(report: dict[str, Any], out_path: Path) -> Path:
|
||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||
with out_path.open("w") as fh:
|
||
json.dump(report, fh, indent=2, sort_keys=True)
|
||
fh.write("\n")
|
||
return out_path
|
||
|
||
|
||
def _print_human(report: dict[str, Any]) -> None:
|
||
identity = report["identity_divergence"]
|
||
refusal = report["refusal_calibration"]
|
||
claims = report["claims_supported"]
|
||
|
||
print("Phase-2 pack measurements (ADR-0043)")
|
||
print("=" * 72)
|
||
print(
|
||
f"identity-divergence: {identity['case_count']} cases × "
|
||
f"{len(identity['alignment_bands'])} alignment bands"
|
||
)
|
||
for entry in identity["packs"]:
|
||
print(
|
||
f" {entry['pack_id']:<24} bare={entry['bare_rate']:.2f} "
|
||
f"hedge={entry['hedge_rate']:.2f} qualifier={entry['qualifier_rate']:.2f}"
|
||
)
|
||
print()
|
||
for pair in identity["pairwise_divergence"]:
|
||
print(
|
||
f" {pair['pack_a']} ⇆ {pair['pack_b']:<22} "
|
||
f"distinct_rate={pair['distinct_rate']:.2f}"
|
||
)
|
||
print()
|
||
print(
|
||
f"refusal-calibration: {refusal['case_count']} cases "
|
||
f"(out_of_grounding={refusal['out_of_grounding_count']})"
|
||
)
|
||
for entry in refusal["packs"]:
|
||
print(
|
||
f" {entry['pack_id']:<24} refusal_rate={entry['refusal_rate']:.2f} "
|
||
f"fabrication_rate={entry['fabrication_rate']:.2f}"
|
||
)
|
||
print()
|
||
print("Claims:")
|
||
for k, v in claims.items():
|
||
print(f" {k:<40} {v}")
|
||
print("=" * 72)
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
parser.add_argument("--json", action="store_true", help="emit only JSON to stdout")
|
||
args = parser.parse_args()
|
||
|
||
report = build_combined_report()
|
||
out_path = Path("evals/results/phase2_pack_measurements.json")
|
||
write_report(report, out_path)
|
||
|
||
if args.json:
|
||
print(json.dumps(report, indent=2, sort_keys=True))
|
||
else:
|
||
_print_human(report)
|
||
print(f"wrote → {out_path}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|