260 lines
8.7 KiB
Python
260 lines
8.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Live parity: Rust task_gate kernel vs the Python oracle (dichotomy
|
|
wave #2479). Replays the frozen truth corpus PLUS seeded fuzz over every
|
|
public function and branch. Timestamps are frozen (no-clock law — the
|
|
kernel takes `now` as an argument), so nothing is masked.
|
|
|
|
The facade reads RUNE_TG_CORE per call, so one process can flip between
|
|
the pinned oracle (RUNE_TG_CORE=python) and the kernel (unset).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import random
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src" / "rune"))
|
|
|
|
import task_gate as tg # noqa: E402
|
|
|
|
FROZEN_NOW = "2026-10-11T23:00:00.123456+00:00"
|
|
|
|
|
|
class FrozenDatetime(datetime):
|
|
"""No-clock law: the facade's `now` stamps are wall clock, not logic —
|
|
freeze them so the two core runs stamp identically."""
|
|
@classmethod
|
|
def now(cls, tz=None):
|
|
return datetime(2026, 10, 11, 23, 0, 0, 123456, tzinfo=timezone.utc)
|
|
|
|
|
|
tg.datetime = FrozenDatetime
|
|
|
|
TRUTH_PATH = Path(__file__).resolve().parents[1] / (
|
|
"src/rust/task_gate/src/frozen_tg_truth.json")
|
|
|
|
|
|
def call_core(core, fn, *args, **kwargs):
|
|
old = os.environ.get("RUNE_TG_CORE")
|
|
os.environ["RUNE_TG_CORE"] = core
|
|
try:
|
|
return getattr(tg, fn)(*args, **kwargs)
|
|
finally:
|
|
if old is None:
|
|
os.environ.pop("RUNE_TG_CORE", None)
|
|
else:
|
|
os.environ["RUNE_TG_CORE"] = old
|
|
|
|
|
|
def fresh_db():
|
|
tmp = tempfile.mkdtemp(prefix="tg_parity_")
|
|
tg.DB_PATH = os.path.join(tmp, "task_gate.db")
|
|
db = tg.connect()
|
|
db.close()
|
|
|
|
|
|
def read_rows():
|
|
db = sqlite3.connect(tg.DB_PATH)
|
|
db.row_factory = sqlite3.Row
|
|
rows = [dict(r) for r in db.execute(
|
|
"SELECT * FROM instructions ORDER BY id").fetchall()]
|
|
db.close()
|
|
return rows
|
|
|
|
|
|
def seed_rows(rows):
|
|
fresh_db()
|
|
db = tg.connect()
|
|
for i, r in enumerate(rows, start=1):
|
|
db.execute(
|
|
"INSERT INTO instructions (id, instruction, pattern, count,"
|
|
" threshold, status, promoted_at, created_at, last_seen)"
|
|
" VALUES (?,?,?,?,?,?,?,?,?)",
|
|
(
|
|
r.get("id", i), r.get("instruction", "seed %d" % i),
|
|
r.get("pattern", r"\bseed\b"), r.get("count", 1),
|
|
r.get("threshold", 3), r.get("status", "observing"),
|
|
r.get("promoted_at"), r.get("created_at", FROZEN_NOW),
|
|
r.get("last_seen", FROZEN_NOW),
|
|
),
|
|
)
|
|
db.commit()
|
|
db.close()
|
|
|
|
|
|
def scenario_record(rows_before, text):
|
|
"""Run record under both cores on identical pre-state; compare
|
|
(return, rows_after)."""
|
|
seed_rows(rows_before)
|
|
py_ret = call_core("python", "record_instruction", text)
|
|
py_rows = read_rows()
|
|
seed_rows(rows_before)
|
|
rs_ret = call_core("auto", "record_instruction", text)
|
|
rs_rows = read_rows()
|
|
ok = (json.dumps(py_ret, sort_keys=True)
|
|
== json.dumps(rs_ret, sort_keys=True)
|
|
and json.dumps(py_rows, sort_keys=True)
|
|
== json.dumps(rs_rows, sort_keys=True))
|
|
return ok, py_ret, rs_ret, py_rows, rs_rows
|
|
|
|
|
|
def scenario_ctx(rows, context):
|
|
seed_rows(rows)
|
|
py = call_core("python", "check_context", context)
|
|
rs = call_core("auto", "check_context", context)
|
|
return (json.dumps(py, sort_keys=True)
|
|
== json.dumps(rs, sort_keys=True)), py, rs
|
|
|
|
|
|
def scenario_list(rows):
|
|
seed_rows(rows)
|
|
py = call_core("python", "list_gates")
|
|
rs = call_core("auto", "list_gates")
|
|
return (json.dumps(py, sort_keys=True)
|
|
== json.dumps(rs, sort_keys=True)), py, rs
|
|
|
|
|
|
def scenario_promote(rows, gate_id):
|
|
seed_rows(rows)
|
|
py = call_core("python", "promote_gate", gate_id)
|
|
py_rows = read_rows()
|
|
seed_rows(rows)
|
|
rs = call_core("auto", "promote_gate", gate_id)
|
|
rs_rows = read_rows()
|
|
return (json.dumps(py, sort_keys=True)
|
|
== json.dumps(rs, sort_keys=True)
|
|
and json.dumps(py_rows, sort_keys=True)
|
|
== json.dumps(rs_rows, sort_keys=True)), py, rs, py_rows, rs_rows
|
|
|
|
|
|
def scenario_hook(event):
|
|
py = call_core("python", "handle_hook", event, {"ignored": True})
|
|
rs = call_core("auto", "handle_hook", event, {"ignored": True})
|
|
return (json.dumps(py, sort_keys=True)
|
|
== json.dumps(rs, sort_keys=True)), py, rs
|
|
|
|
|
|
# ── fuzz pools ─────────────────────────────────────────────────────────
|
|
WORDS = ["always", "check", "the", "benchmark", "before", "building",
|
|
"ship", "it", "do", "stop", "now", "café", "über", "v1_2",
|
|
"test_it", "fix", "bug", "release", "deploy", "kraken", "ab",
|
|
"cd", "research", "first", "wash", "spin", "gate", "task"]
|
|
PATTERNS = [r"\bcheck\b", r"\bship\b", r"\bbenchmark\b", r"do\ it",
|
|
r"\balways\b|\bcheck\b", r"\bcafé\b", r"\bresearch\b",
|
|
r"\bgate\b|\btask\b", r"\bfirst\b"]
|
|
STATUSES = ["observing", "active", "retired"]
|
|
|
|
|
|
def rand_text(rng):
|
|
n = rng.randint(1, 8)
|
|
sep = rng.choice([" ", " ", " ", " ", "! ", " ("])
|
|
return sep.join(rng.choice(WORDS) for _ in range(n)) + rng.choice(
|
|
["", "!", "?", " (now)"])
|
|
|
|
|
|
def rand_rows(rng, max_rows=4):
|
|
rows = []
|
|
for _ in range(rng.randint(0, max_rows)):
|
|
rows.append({
|
|
"pattern": rng.choice(PATTERNS),
|
|
"count": rng.randint(1, 5),
|
|
"threshold": rng.randint(2, 4),
|
|
"status": rng.choice(STATUSES),
|
|
"instruction": rand_text(rng),
|
|
"promoted_at": FROZEN_NOW if rng.random() < 0.3 else None,
|
|
})
|
|
return rows
|
|
|
|
|
|
def fuzz_cases(rng):
|
|
cases = []
|
|
for _ in range(300):
|
|
cases.append(("record", rand_rows(rng), rand_text(rng)))
|
|
for _ in range(250):
|
|
cases.append(("ctx", rand_rows(rng), rand_text(rng)))
|
|
for _ in range(200):
|
|
cases.append(("list", rand_rows(rng, 5), None))
|
|
for _ in range(150):
|
|
cases.append(("promote", rand_rows(rng), rng.randint(1, 5)))
|
|
for _ in range(50):
|
|
cases.append(("hook", None, rand_text(rng)))
|
|
return cases
|
|
|
|
|
|
def main() -> int:
|
|
failures = []
|
|
|
|
def check(label, ok, detail=""):
|
|
if not ok:
|
|
failures.append((label, detail))
|
|
print(f"FAIL {label}\n{detail}")
|
|
|
|
# 1. frozen truth corpus (facade level, both cores already proven by
|
|
# gen_task_gate_truth.py --replay; re-run kernel level here live)
|
|
truth = json.loads(TRUTH_PATH.read_text())["cases"]
|
|
n_truth = 0
|
|
for group, cases in truth.items():
|
|
for case in cases:
|
|
if group == "record_instruction":
|
|
inp = case["input"]
|
|
ok, p, r, pr, rr = scenario_record(
|
|
inp["instructions"], inp["text"])
|
|
elif group == "check_context":
|
|
inp = case["input"]
|
|
ok, p, r = scenario_ctx(inp["instructions"], inp["context"])
|
|
elif group == "list_gates":
|
|
ok, p, r = scenario_list(
|
|
case["rows_after"] and _rows_before(case))
|
|
elif group == "promote_gate":
|
|
inp = case["input"]
|
|
ok, p, r, pr, rr = scenario_promote(
|
|
[{"pattern": r"\bx\b"}], inp["id"])
|
|
else: # handle_hook
|
|
ok, p, r = scenario_hook(case["input"]["event"])
|
|
n_truth += 1
|
|
check(f"truth:{group}:{case['name']}", ok,
|
|
f"py={json.dumps(p, sort_keys=True)[:300]}\n"
|
|
f"rs={json.dumps(r, sort_keys=True)[:300]}")
|
|
|
|
# 2. seeded fuzz
|
|
rng = random.Random(2479)
|
|
n_fuzz = 0
|
|
for kind, rows, arg in fuzz_cases(rng):
|
|
n_fuzz += 1
|
|
label = f"fuzz:{kind}:{n_fuzz}"
|
|
if kind == "record":
|
|
ok, p, r, pr, rr = scenario_record(rows, arg)
|
|
elif kind == "ctx":
|
|
ok, p, r = scenario_ctx(rows, arg)
|
|
elif kind == "list":
|
|
ok, p, r = scenario_list(rows)
|
|
elif kind == "promote":
|
|
ok, p, r, pr, rr = scenario_promote(rows, arg)
|
|
else:
|
|
ok, p, r = scenario_hook(arg)
|
|
check(label, ok,
|
|
f"py={json.dumps(p, sort_keys=True)[:300]}\n"
|
|
f"rs={json.dumps(r, sort_keys=True)[:300]}\n"
|
|
f"rows={json.dumps(rows, sort_keys=True)[:200]}")
|
|
|
|
total = n_truth + n_fuzz
|
|
print(f"parity: {total - len(failures)}/{total} "
|
|
f"({n_truth} truth + {n_fuzz} fuzz seed 2479)")
|
|
return 1 if failures else 0
|
|
|
|
|
|
def _rows_before(case):
|
|
"""list_gates truth carries rows_after (the full pre-state — listing
|
|
never mutates); replay it as the seed state."""
|
|
return case["rows_after"]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|