core/tests/test_cli_demo.py
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

222 lines
8.4 KiB
Python

"""CLI tests for the ADR-0024 chain demo subcommand."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from core import cli
class TestADR0024SuiteAliases:
"""Layer 1: pin the new suite aliases so they don't drift."""
@pytest.mark.parametrize(
"suite,must_contain",
[
("refusal", "tests/test_refusal_contract.py"),
("margin", "tests/test_margin_admissibility.py"),
("rotor", "tests/test_rotor_admissibility.py"),
("inner-loop", "tests/test_inner_loop_admissibility.py"),
("phase5", "tests/test_phase5_corpus.py"),
("phase6", "tests/test_phase6_demo.py"),
],
)
def test_suite_alias_resolves(
self, monkeypatch, suite: str, must_contain: str
) -> None:
calls: list[tuple[str, ...]] = []
def fake_run(*args: str, check: bool = False, cwd=None) -> int:
calls.append(args)
return 0
monkeypatch.setattr(cli, "_run", fake_run)
rc = cli.main(["test", "--suite", suite, "-q"])
assert rc == 0
assert calls, f"no _run invocation for suite {suite!r}"
assert must_contain in calls[0]
def test_adr_0024_alias_runs_full_chain(self, monkeypatch) -> None:
calls: list[tuple[str, ...]] = []
monkeypatch.setattr(
cli, "_run", lambda *a, **kw: (calls.append(a) or 0)
)
rc = cli.main(["test", "--suite", "adr-0024", "-q"])
assert rc == 0
command = calls[0]
# All six Phase 2-6 contract files must be present.
for path in (
"tests/test_refusal_contract.py",
"tests/test_margin_admissibility.py",
"tests/test_rotor_admissibility.py",
"tests/test_phase5_corpus.py",
"tests/test_phase6_demo.py",
):
assert path in command, f"missing {path} in adr-0024 expansion"
def test_list_suites_includes_new_aliases(self, capsys) -> None:
rc = cli.main(["test", "--list-suites"])
captured = capsys.readouterr()
assert rc == 0
out = captured.out.splitlines()
for alias in (
"adr-0024",
"refusal",
"margin",
"rotor",
"inner-loop",
"phase5",
"phase6",
):
assert alias in out, f"alias {alias!r} missing from --list-suites"
class TestDemoSubcommand:
"""Layer 2: pin the `core demo` subcommand surface."""
def test_demo_help_lists_targets(self, capsys) -> None:
with pytest.raises(SystemExit) as exc:
cli.main(["demo", "--help"])
assert exc.value.code == 0
captured = capsys.readouterr()
for target in ("phase5", "phase6", "all", "list-results"):
assert target in captured.out
def test_demo_phase6_runs_and_writes_report(self, capsys) -> None:
rc = cli.main(["demo", "phase6"])
assert rc == 0
captured = capsys.readouterr()
# Headline text on stdout.
assert "ALL THREE CONDITIONS" in captured.out
assert "PASS" in captured.out
# Report file present and well-formed.
report = Path("evals/forward_semantic_control/results/phase6_demo_report.json")
assert report.exists()
data = json.loads(report.read_text())
assert data["metrics"]["all_three_conditions_pass"] is True
def test_demo_phase6_json_emits_machine_readable(self, capsys) -> None:
rc = cli.main(["demo", "phase6", "--json"])
assert rc == 0
captured = capsys.readouterr()
# First non-blank chunk of stdout must be a parseable JSON
# object containing the headline keys.
payload = json.loads(captured.out.split("\n\n")[0])
assert "metrics" in payload
assert "all_three_conditions_pass" in payload["metrics"]
def test_demo_list_results_indexes_reports(self, capsys) -> None:
rc = cli.main(["demo", "list-results"])
assert rc == 0
captured = capsys.readouterr()
assert "results directory" in captured.out
assert "phase6_demo_report.json" in captured.out
def test_demo_list_results_json_well_formed(self, capsys) -> None:
rc = cli.main(["demo", "list-results", "--json"])
assert rc == 0
captured = capsys.readouterr()
data = json.loads(captured.out)
assert "results_dir" in data
assert isinstance(data["reports"], list)
names = [e["file"] for e in data["reports"]]
assert "phase6_demo_report.json" in names
def test_demo_index_file_refreshed_after_run(self) -> None:
cli.main(["demo", "phase6"])
index_path = Path("evals/forward_semantic_control/results/index.json")
assert index_path.exists()
data = json.loads(index_path.read_text())
names = [e["file"] for e in data["reports"]]
assert "phase6_demo_report.json" in names
class TestDemoPreambles:
"""Pin the preamble explanations so they don't drift silently."""
def test_phase6_preamble_explains_three_conditions(self, capsys) -> None:
cli.main(["demo", "phase6"])
out = capsys.readouterr().out
assert "WHAT THIS DEMO TESTS" in out
assert "C1 Replay determinism" in out
assert "C2 Traced rejection" in out
assert "C3 Coherent refusal" in out
assert "WHAT TO EXPECT" in out
assert "WHEN TO TWEAK" in out
def test_phase6_preamble_states_in_system_baseline(self, capsys) -> None:
cli.main(["demo", "phase6"])
out = capsys.readouterr().out
# The "why not a transformer LLM" explanation must be present.
assert "ADR-0023 ablation" in out
assert "non-deterministic" in out or "Non-deterministic" in out
def test_phase5_preamble_explains_five_families(self, capsys) -> None:
cli.main(["demo", "phase5"])
out = capsys.readouterr().out
assert "WHAT THIS DEMO TESTS" in out
for family in (
"near_forbidden_correct_endpoint",
"near_equal_admissible",
"no_admissible_path",
"multi_step_admissibility",
"heterogeneous_relation",
):
assert family in out
assert "WHAT TO LOOK FOR" in out
def test_phase5_preamble_states_delta_falsifiable(self, capsys) -> None:
cli.main(["demo", "phase5"])
out = capsys.readouterr().out
assert "FALSIFIABLE" in out or "falsifiable" in out
def test_preamble_suppressed_under_json(self, capsys) -> None:
cli.main(["demo", "phase6", "--json"])
out = capsys.readouterr().out
# No preamble text should leak into --json mode.
assert "WHAT THIS DEMO TESTS" not in out
# Output must be parseable JSON from the first character.
payload = json.loads(out.split("\n\n")[0])
assert "metrics" in payload
def test_all_preamble_explains_combined_run(self, capsys) -> None:
cli.main(["demo", "all"])
out = capsys.readouterr().out
assert "Combined Demo" in out
# Both phase preambles fire for `demo all`.
assert "Phase 5 Demo" in out
assert "Phase 6 Demo" in out
# Combined summary at the end.
assert "Combined demo summary" in out
assert "load-bearing claim of the ADR-0024 chain" in out
class TestResultsReadme:
"""The results/ directory ships with an explanatory README so cold readers
can interpret each report without spelunking the runner source."""
def test_results_readme_exists(self) -> None:
readme = Path("evals/forward_semantic_control/results/README.md")
assert readme.exists()
text = readme.read_text()
# The README must explicitly call out each phase's report file.
for fname in (
"phase5_report.json",
"phase6_demo_report.json",
"phase5_benign_inner_loop_report.json",
"phase4_characterization",
"phase3_v2_report.json",
"phase2_inner_loop_report.json",
):
assert fname in text, f"{fname} missing from results/README.md"
def test_corpus_readmes_exist(self) -> None:
for path in (
"evals/forward_semantic_control/public/v2_phase5/README.md",
"evals/forward_semantic_control/public/v2_phase6_demo/README.md",
"evals/forward_semantic_control/public/inner_loop_benign/README.md",
):
assert Path(path).exists(), f"{path} missing"