qec-playground / scripts /verify_gating.py
Tunti35's picture
Deploy QEC-Playground Streamlit app
6e5a865 verified
Raw
History Blame Contribute Delete
12.4 kB
"""Run plan.md verification steps 1-5 with UTF-8 evidence capture.
Writes one fresh log per step to launch_contract.resolve_scratch_dir().
Exits non-zero on the first failed assertion.
"""
from __future__ import annotations
import io
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from launch_contract import ( # noqa: E402
check_cli_output,
check_license,
check_positioning,
check_readme,
check_source_claims,
clean_stale_scratch_artifacts,
resolve_scratch_dir,
)
CYCLE_SLOW_LITERAL = "Cycle time: 2.0 µs"
MANGLED_MICRO = "┬Á"
CLI_MARKERS = (
"QEC-Playground Simulation Results",
"Total decoding time",
"Conditional wait reduction",
)
EVIDENCE_FILES = (
"core_direct.log",
"cli1.log",
"cli2.log",
"cli_verify.log",
"cli_slow.log",
"pytest.log",
"license.log",
"positioning.log",
"docs_verify.log",
"sensitivity.log",
"changed_files.log",
"pytest_full.log",
"gating_manifest.log",
)
def _write(path: Path, text: str) -> None:
path.write_text(text, encoding="utf-8")
def _fail(step: str, message: str) -> int:
print(f"GATING_FAIL step={step}: {message}", file=sys.stderr)
return 1
def step1_core_direct(scratch: Path) -> tuple[str, int] | None:
code = """
from core.simulator import run_simulation
res = run_simulation(
processor_count=4,
cycle_time_us=1.0,
speculation_accuracy=0.9,
decoder_latency_rounds=2,
ordering_strategy='shallow_first',
seed=42,
)
print('spec_wait=', res['speculative']['average_conditional_wait_time_us'])
print('nonspec_wait=', res['non_speculative']['average_conditional_wait_time_us'])
print('max_decoders=', res['speculative']['max_concurrent_decoders'])
print('metrics_keys=', list(res['speculative'].keys()))
print('completed=', res.get('completed'))
"""
proc = subprocess.run(
[sys.executable, "-c", code],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
log = proc.stdout
if proc.stderr:
log = f"{log}\n{proc.stderr}" if log else proc.stderr
_write(scratch / "core_direct.log", log)
if proc.returncode != 0:
return _fail("1", f"core_direct exit {proc.returncode}")
if "spec_wait=" not in log or "nonspec_wait=" not in log:
return _fail("1", "missing spec_wait/nonspec_wait lines")
if "completed= True" not in log and "completed=True" not in log.replace(" ", ""):
return _fail("1", "completed not True")
for key in (
"total_decoding_time_us",
"average_window_backlog",
"average_conditional_wait_time_us",
"ui_window_count",
"max_concurrent_decoders",
):
if key not in log:
return _fail("1", f"metrics_keys missing {key}")
spec_line = next((ln for ln in log.splitlines() if ln.startswith("spec_wait=")), "")
nonspec_line = next((ln for ln in log.splitlines() if ln.startswith("nonspec_wait=")), "")
max_dec_line = next((ln for ln in log.splitlines() if ln.startswith("max_decoders=")), "")
try:
spec_wait = float(spec_line.split("=", 1)[1].strip())
nonspec_wait = float(nonspec_line.split("=", 1)[1].strip())
max_dec = float(max_dec_line.split("=", 1)[1].strip())
except (IndexError, ValueError):
return _fail("1", "could not parse wait times or max_decoders")
if spec_wait <= 0 or nonspec_wait <= 0:
return _fail("1", f"non-positive conditional wait spec={spec_wait} nonspec={nonspec_wait}")
if max_dec < 0:
return _fail("1", f"invalid max_decoders {max_dec}")
try:
from core.simulator import run_simulation
res = run_simulation(seed=42, schedule_id="three_t_injection")
spec_rt = float(res["speculative"]["total_decoding_time_us"])
nonspec_rt = float(res["non_speculative"]["total_decoding_time_us"])
if spec_rt > nonspec_rt:
return _fail("1", f"spec_runtime {spec_rt} > nonspec_runtime {nonspec_rt}")
except Exception as exc:
return _fail("1", f"runtime check failed: {exc}")
return None
def step2_cli(scratch: Path) -> int | None:
def run_app(*extra: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, "app.py", *extra],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
for name in ("cli1", "cli2"):
proc = run_app()
log = proc.stdout
if proc.stderr:
log = f"{log}\n{proc.stderr}" if log else proc.stderr
_write(scratch / f"{name}.log", log)
if proc.returncode != 0:
return _fail("2", f"{name} exit {proc.returncode}")
cli1 = (scratch / "cli1.log").read_text(encoding="utf-8")
cli2 = (scratch / "cli2.log").read_text(encoding="utf-8")
if cli1 != cli2:
return _fail("2", "cli1.log != cli2.log")
_write(scratch / "cli_verify.log", "CLI_REPEAT_OK\n")
slow = run_app("--cycle-time-us", "2", "--processors", "4", "--schedule", "three_t_injection")
slow_log = slow.stdout
if slow.stderr:
slow_log = f"{slow_log}\n{slow.stderr}" if slow_log else slow.stderr
_write(scratch / "cli_slow.log", slow_log)
if slow.returncode != 0:
return _fail("2", f"cli_slow exit {slow.returncode}")
for label, text in (("cli1", cli1), ("cli2", cli2), ("cli_slow", slow_log)):
checks = check_cli_output(text)
if not all(checks.values()):
return _fail("2", f"{label} CLI markers failed: {checks}")
if not any(ch.isdigit() for ch in text):
return _fail("2", f"{label} missing numeric values")
if MANGLED_MICRO in slow_log:
return _fail("2", "cli_slow has mangled micro sign (PowerShell encoding)")
if CYCLE_SLOW_LITERAL not in slow_log:
return _fail("2", f"cli_slow missing literal {CYCLE_SLOW_LITERAL!r}")
return None
def step3_pytest(scratch: Path) -> int | None:
proc = subprocess.run(
[
sys.executable,
"-m",
"pytest",
"tests/test_swiper_sim.py",
"tests/test_decoder.py",
"tests/test_managers.py",
"-q",
"--tb=line",
],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
_write(scratch / "pytest.log", proc.stdout)
if proc.returncode != 0:
err_tail = proc.stderr.strip().splitlines()[-3:] if proc.stderr else []
return _fail("3", f"pytest exit {proc.returncode}; stderr_tail={err_tail}")
lines = [ln for ln in proc.stdout.splitlines() if ln.strip()]
if not lines or " passed" not in lines[-1]:
return _fail("3", f"pytest stdout does not end with passed summary; tail={lines[-3:]}")
return None
def step4_docs(scratch: Path) -> int | None:
license_text = (ROOT / "LICENSE").read_text(encoding="utf-8")
_write(scratch / "license.log", license_text)
readme_text = (ROOT / "README.md").read_text(encoding="utf-8")
spec_text = (ROOT / "spec.md").read_text(encoding="utf-8")
buf = io.StringIO()
print("license", check_license(), file=buf)
print("readme", check_readme(readme_text), file=buf)
print("spec_pos", check_positioning(spec_text), file=buf)
print("source", check_source_claims(), file=buf)
positioning_log = buf.getvalue()
_write(scratch / "positioning.log", positioning_log)
license_checks = check_license()
readme_checks = check_readme(readme_text)
spec_checks = check_positioning(spec_text)
source_checks = check_source_claims()
if not all(license_checks.values()):
return _fail("4", f"license checks failed: {license_checks}")
if not all(readme_checks.values()):
return _fail("4", f"readme checks failed: {readme_checks}")
if not all(spec_checks.values()):
return _fail("4", f"spec_pos checks failed: {spec_checks}")
if not all(source_checks.values()):
return _fail("4", f"source checks failed: {source_checks}")
proc = subprocess.run(
[sys.executable, "scripts/docs_verify.py"],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
docs_log = proc.stdout
if proc.stderr:
docs_log = f"{docs_log}\n{proc.stderr}" if docs_log else proc.stderr
_write(scratch / "docs_verify.log", docs_log)
if proc.returncode != 0:
return _fail("4", f"docs_verify exit {proc.returncode}")
if "DOCS_VERIFY_OK" not in docs_log:
return _fail("4", "docs_verify missing DOCS_VERIFY_OK")
return None
def step5_sensitivity(scratch: Path) -> int | None:
code = """
from core.simulator import run_simulation
f = run_simulation(cycle_time_us=1.0, seed=42)
s = run_simulation(
cycle_time_us=2.0,
processor_count=2,
schedule_id='three_t_injection',
seed=42,
)
print(
'FAST',
f['speculative']['average_conditional_wait_time_us'],
f['non_speculative']['average_conditional_wait_time_us'],
)
print(
'SLOW',
s['speculative']['average_conditional_wait_time_us'],
s['non_speculative']['average_conditional_wait_time_us'],
)
print('SENSITIVITY_OK')
"""
proc = subprocess.run(
[sys.executable, "-c", code],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
log = proc.stdout
if proc.stderr:
log = f"{log}\n{proc.stderr}" if log else proc.stderr
_write(scratch / "sensitivity.log", log)
if proc.returncode != 0:
return _fail("5", f"sensitivity exit {proc.returncode}")
if "SENSITIVITY_OK" not in log:
return _fail("5", "missing SENSITIVITY_OK")
return None
def _wipe_scratch(scratch: Path) -> None:
"""Remove every file in scratch so stale polluted logs cannot survive partial runs."""
if not scratch.exists():
return
for path in scratch.iterdir():
if path.is_file():
path.unlink()
def main() -> int:
scratch = resolve_scratch_dir()
scratch.mkdir(parents=True, exist_ok=True)
clean_stale_scratch_artifacts(scratch)
_wipe_scratch(scratch)
_write(
scratch / "gating_manifest.log",
f"producer=scripts/verify_gating.py\nscratch={scratch}\nencoding=utf-8\n",
)
for step_fn in (step1_core_direct, step2_cli, step3_pytest, step4_docs, step5_sensitivity):
err = step_fn(scratch)
if err is not None:
return err
changed_proc = subprocess.run(
[sys.executable, "scripts/changed_files_report.py"],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
_write(scratch / "changed_files.log", changed_proc.stdout)
if changed_proc.returncode != 0:
return _fail("changed_files", changed_proc.stderr or "changed_files_report failed")
changed_section = changed_proc.stdout.split("DEVIATIONS:")[0]
for line in changed_section.splitlines():
parts = line.split()
if len(parts) >= 2 and parts[-1] == "cli.py":
return _fail("changed_files", "cli.py must not appear in CHANGED_FILES section")
if ".gating-evidence" in changed_proc.stdout:
return _fail("changed_files", ".gating-evidence must not appear in CHANGED_FILES")
if " D cli.py" in changed_proc.stdout or "\nD cli.py" in changed_proc.stdout:
if "DEVIATIONS:" not in changed_proc.stdout or "cli.py" not in changed_proc.stdout:
return _fail("changed_files", "cli.py delete must be tagged under DEVIATIONS")
full_pytest = subprocess.run(
[sys.executable, "-m", "pytest", "tests/", "-q", "--tb=line"],
cwd=ROOT,
capture_output=True,
text=True,
encoding="utf-8",
)
_write(scratch / "pytest_full.log", full_pytest.stdout)
if full_pytest.returncode != 0:
return _fail("pytest_full", f"full suite exit {full_pytest.returncode}")
full_lines = [ln for ln in full_pytest.stdout.splitlines() if ln.strip()]
if not full_lines or "passed" not in full_lines[-1]:
return _fail("pytest_full", f"unexpected pytest_full tail: {full_lines[-3:]}")
print(f"GATING_OK evidence={scratch}")
return 0
if __name__ == "__main__":
raise SystemExit(main())