Files
rune/scripts/check_execution_trace_parity.py
Nick 4bda61cdc5 feat(execution_trace): port to FULL Rust — dichotomy wave
Language verdict: RUST per docs/language-dichotomy-ledger.md (W6 law —
deterministic record assembly with many fields; the journal FILE I/O
stays host-side per Law 7, the parse + receipt-assembly laws cross FFI).

- et_read_outcome_events/et_build_receipt/et_trace_task doc-envelope FFI
  (et_* symbols; free via capability_scoring cs_free_string — no free
  re-export); et_loader mirroring broker_loader.py.
- Facade delegation with RUNE_ET_CORE=python pin; kernel floor on any
  kernel error.
- Python laws replicated: malformed-line skip, falsy task_id skip,
  duplicate task_id last-wins, assigned_model→payload.model falsy
  chain, payload-status-key-present (even null) law, completed_at
  fallback, task_record or {task_id} falsy replacement, None-None→null.
- Fuzz divergence caught pre-landing: the oracle CRASHES (AttributeError)
  on non-object journal lines ([1,2], 5, "x") — kernel now errors
  (AttributeError envelope) so the facade reproduces the oracle crash
  identically instead of silently skipping.
- 20-case frozen oracle truth (RUNE_ET_CORE=python pinned, deep-copied
  inputs, every public function + branch) embedded in the crate and
  replayed VERBATIM by the branch battery; live parity 820/820 (20
  frozen + 800 fuzz, seed 2479); pytest 24 passed BOTH ways
  (RUNE_ET_CORE=python and auto); clippy -D warnings clean.
2026-10-13 03:06:44 +01:00

197 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""Live parity: execution_trace Rust kernel vs Python oracle (dichotomy
wave). Layers:
1. FROZEN — the 20 frozen-oracle cases replayed through the facade in
BOTH core modes (RUNE_ET_CORE=python floor vs auto kernel); exact
compare, journal contents rebuilt verbatim from the truth.
2. FUZZ — 400 seeded random journals/tasks/payloads (seed 2479)
driven through both modes; exact compare.
"""
import copy
import json
import os
import random
import sys
import tempfile
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
import execution_trace as et # noqa: E402
from rune import et_loader # noqa: E402
if not et_loader.available():
print("FATAL: Rust execution_trace core not built")
sys.exit(2)
TRUTH = json.loads(
(REPO / "src" / "rust" / "execution_trace" / "frozen_et_truth.json")
.read_text())
fails = []
counts = {"frozen": 0, "fuzz": 0}
def fail(layer, name, detail):
fails.append((layer, name, detail))
print(f"FAIL[{layer}] {name}: {detail}")
def drive(mode, fn, *args, **kwargs):
os.environ["RUNE_ET_CORE"] = mode
try:
return ("ok", fn(*copy.deepcopy(args), **copy.deepcopy(kwargs)))
except Exception as e: # noqa: BLE001 — crash parity is part of the law
return ("raise", type(e).__name__)
finally:
os.environ["RUNE_ET_CORE"] = "auto"
def journal_file(lines):
if lines is None:
fd, path = tempfile.mkstemp(suffix=".jsonl")
os.close(fd)
os.unlink(path)
return path
with tempfile.NamedTemporaryFile("w", suffix=".jsonl", delete=False,
encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
return f.name
def frozen():
for group, fn, key in (
("build_receipt_cases", et.build_receipt, "args"),
("trace_task_cases", None, "args"),
):
for c in TRUTH[group]:
name = c["name"]
counts["frozen"] += 1
if group == "build_receipt_cases":
py = drive("python", et.build_receipt,
c["args"]["task"], c["args"]["outcome_payload"])
rs = drive("auto", et.build_receipt,
c["args"]["task"], c["args"]["outcome_payload"])
if py != rs:
fail("frozen", f"build_receipt/{name}", f"{py!r} != {rs!r}")
continue
path = journal_file(c["args"]["lines"])
try:
py = drive("python", et.trace_task, c["args"]["task_id"],
c["args"]["task_record"], path)
rs = drive("auto", et.trace_task, c["args"]["task_id"],
c["args"]["task_record"], path)
finally:
if c["args"]["lines"] is not None:
os.unlink(path)
if py != rs:
fail("frozen", f"trace_task/{name}", f"{py!r} != {rs!r}")
for c in TRUTH["read_outcome_events_cases"]:
name = c["name"]
counts["frozen"] += 1
path = journal_file(c["lines"])
try:
py = drive("python", et._read_outcome_events, path)
rs = drive("auto", et._read_outcome_events, path)
finally:
if c["lines"] is not None:
os.unlink(path)
if py != rs:
fail("frozen", f"read_outcome_events/{name}",
f"{py!r} != {rs!r}")
WORDS = ["done", "failed", "running", "", "détaché", "line\\nbreak"]
MODELS = ["", "qwen2.5:3b", "cloud-x", None]
def rand_journal(rng):
lines = []
for _ in range(rng.randint(0, 6)):
r = rng.random()
tid = f"t{rng.randint(0, 5)}"
if r < 0.15:
lines.append(rng.choice(["{not json", "", "[1,2]"]))
continue
ev = rng.choice(["task.outcome", "task.started", "task.outcome"])
payload = {"task_id": tid if rng.random() > 0.1 else "",
"status": rng.choice(WORDS)}
if rng.random() < 0.5:
payload["model"] = rng.choice(MODELS)
if rng.random() < 0.5:
payload["completed_at"] = rng.choice(["", "T-a", "T-b"])
lines.append(json.dumps({"event_type": ev, "payload": payload}))
return lines
def rand_task(rng):
task = {}
if rng.random() < 0.9:
task["task_id"] = f"t{rng.randint(0, 7)}"
if rng.random() < 0.7:
task["description"] = rng.choice(WORDS)
if rng.random() < 0.8:
task["status"] = rng.choice(WORDS)
if rng.random() < 0.6:
task["assigned_model"] = rng.choice(MODELS)
if rng.random() < 0.5:
task["memories_used"] = ["m1"] if rng.random() < 0.5 else []
if rng.random() < 0.4:
task["rules_used"] = []
if rng.random() < 0.3:
task["capability"] = rng.choice(["", "forge"])
if rng.random() < 0.4:
task["actions"] = ["a"] if rng.random() < 0.5 else []
if rng.random() < 0.5:
task["completed_at"] = rng.choice(["", "T-task"])
return task
def rand_record(rng):
r = rng.random()
if r < 0.4:
return None
if r < 0.5:
return {}
return rand_task(rng)
def fuzz():
rng = random.Random(2479)
for i in range(400):
lines = rand_journal(rng)
task = rand_task(rng)
record = rand_record(rng)
tid = f"t{rng.randint(0, 7)}"
path = journal_file(lines)
try:
py = drive("python", et.trace_task, tid, record, path)
rs = drive("auto", et.trace_task, tid, record, path)
finally:
if lines is not None:
os.unlink(path)
counts["fuzz"] += 1
if py != rs:
fail("fuzz", f"case-{i}", f"{py!r} != {rs!r}")
continue
payload_rng = random.Random(1000 + i)
t = rand_task(payload_rng)
p = None if payload_rng.random() < 0.4 else {
"model": rng.choice(MODELS), "status": rng.choice(WORDS),
"completed_at": rng.choice(["", "T-p"])}
py = drive("python", et.build_receipt, t, p)
rs = drive("auto", et.build_receipt, t, p)
counts["fuzz"] += 1
if py != rs:
fail("fuzz", f"receipt-{i}", f"{py!r} != {rs!r}")
frozen()
fuzz()
total = counts["frozen"] + counts["fuzz"]
print(f"parity: {total - len(fails)}/{total} "
f"(frozen {counts['frozen']}, fuzz {counts['fuzz']})")
sys.exit(1 if fails else 0)