#!/usr/bin/env python3 """check_microkernel_parity.py — live oracle-vs-Go parity sweep for the microkernel gap closure (dichotomy wave #2479, worktree rune-wave-cq). The frozen envelope (truth_microkernel_gap.json, 25 cases) pins the gap fns byte-identically. THIS script drives the LIVE Python oracle (src/rune/microkernel.py) and the Go binary's parity op mode across every branch class with FRESH fixtures (not the frozen ones): extract_manifest (prefix traps, nested literals, unicode), discover (mixed dir), registry load/save shapes, and fire/_run_hook (ok-json / raw-stdout truncation / rc-error / timeout / skipped / ghost-file / empty-file / two-listeners / no-listeners). Normalization law (shared with the rig): wall-clock durations → "", fire results name-sorted, fixture paths → . Data keys alphabetical (the Go map marshal is key-sorted; plugins canonicalize the payload). Usage: python3 scripts/check_microkernel_parity.py [--bin ] Exit 0 on 100% parity, 1 with the first divergence. """ from __future__ import annotations import argparse import copy import json import os import re import subprocess import sys import tempfile from datetime import datetime, timezone from pathlib import Path REPO = Path(__file__).resolve().parent.parent sys.path.insert(0, str(REPO / "src")) sys.path.insert(0, str(REPO / "src" / "rune")) import microkernel as mk # noqa: E402 FROZEN_NOW = "2026-10-13T11:05:00" FROZEN_NOW_AWARE = FROZEN_NOW + "+00:00" OK_JSON = ( "import json, sys\n" "data = sys.argv[sys.argv.index('--data') + 1]\n" "print(json.dumps({'echo': json.loads(data)}))\n" ) RAW_LONG = "print('h\\u00e9llo-\\u2192ok ' * 90)\n" BOOM = ("import sys\n" "sys.stderr.write('\\u00e9rr\\u00f4r-' * 80)\n" "sys.exit(3)\n") SLOW = "import time\ntime.sleep(4)\n" FAILS: list[str] = [] CHECKS = [0] def check(name, oracle_val, go_val): CHECKS[0] += 1 a = json.dumps(oracle_val, sort_keys=True, ensure_ascii=False) b = json.dumps(go_val, sort_keys=True, ensure_ascii=False) if a != b: FAILS.append(f"{name}:\n oracle: {a[:300]}\n go: {b[:300]}") # ── clock pin (aware — fire() mixes naive/aware now()) ──────────────────── def pin_clock(): base = datetime.fromisoformat(FROZEN_NOW).replace(tzinfo=timezone.utc) class Frozen(datetime): @classmethod def now(cls, tz=None): if tz is not None: return base.replace(tzinfo=tz) return base old = mk.datetime mk.datetime = Frozen return old # ── Go side ──────────────────────────────────────────────────────────────── def run_go(binpath, req, env_dir=None): env = dict(os.environ) if env_dir: env["RUNE_MEMORY_DIR"] = env_dir p = subprocess.run([str(binpath), "parity"], input=json.dumps(req), capture_output=True, text=True, env=env, timeout=120) if p.returncode != 0: raise RuntimeError(f"go op {req.get('op')} exited {p.returncode}: " f"{p.stdout} {p.stderr}") return json.loads(p.stdout) # ── normalization ────────────────────────────────────────────────────────── def norm_fire(result): out = copy.deepcopy(result) if "total_duration_ms" in out: out["total_duration_ms"] = "" for r in out.get("results", []): if "duration_ms" in r: r["duration_ms"] = "" out["results"] = sorted(out.get("results", []), key=lambda r: r.get("name", "")) return out def norm_logs(log): return [{"event": e.get("event"), "timestamp": e.get("timestamp"), "hooks_fired": e.get("hooks_fired"), "duration_ms": ""} for e in log] def oracle_fire_with(manifest, event, data, timeout): """Fire through the live oracle; the manifest's _file must already point at an existing fixture script (or a ghost for not-found).""" work = tempfile.mkdtemp(prefix="mk_par_fire_") mk.REGISTRY_DB = os.path.join(work, "registry.json") mk.register(dict(manifest)) result = mk.fire(event, data=data, timeout=timeout) log = mk._load_registry().get("log", []) return {"result": norm_fire(result), "log": norm_logs(log)} def go_registry_blob(manifest): return { "plugins": {manifest["name"]: { "manifest": {"name": manifest["name"]}, "registered_at": FROZEN_NOW_AWARE, "active": bool(manifest.get("auto_activate", True)), "file": manifest.get("_file", ""), }}, "capabilities": {}, "hooks": {ev: [manifest["name"]] for ev, on in manifest.get("hooks", {}).items() if on}, "log": [], } def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--bin", default="/tmp/mk-parity-bin") args = ap.parse_args() binpath = Path(args.bin) old_clock = pin_clock() # ── now_iso: FORMAT parity (wall clock — both must emit the shape) ── CHECKS[0] += 1 shape = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+00:00$") if not (shape.match(mk.now_iso()) and shape.match(run_go(binpath, {"op": "now_iso"})["result"])): FAILS.append(f"now_iso shape: oracle {mk.now_iso()!r} go {run_go(binpath, {'op': 'now_iso'})['result']!r}") # ── extract_manifest: content sweep ────────────────────────────── contents = [ 'PLUGIN = {\n "name": "x",\n "capabilities": ["a", "b"],\n "hooks": {"e": "handle_hook"},\n "priority": 2,\n "auto_activate": False,\n}\n', 'PLUGINX = {\n "name": "prefix-trap",\n}\n', # startswith("PLUGIN") 'PLUGIN={"name":"dense","n":0.5}\n', # no spaces "PLUGIN = {'k': 'v', 't': (1, 2), 'l': [1, [2, 3]]}\n", # tuple literal 'PLUGIN = {\n "name": "é中",\n "s": "tab\\tnewline\\nquote\\"q",\n}\n', 'PLUGIN = {\n "name": "trail",\n "arr": [1, 2, 3,],\n}\n', # trailing comma in list 'no manifest here\n', 'PLUGIN = {\n "name": "never-closed",\n', ] for i, content in enumerate(contents): fd, fpath = tempfile.mkstemp(suffix=".py") with os.fdopen(fd, "w") as f: f.write(content) try: oracle = copy.deepcopy(mk._extract_manifest(fpath)) finally: os.unlink(fpath) go = run_go(binpath, {"op": "extract_manifest", "content": content})["result"] check(f"extract_manifest[{i}]", oracle, go) # ── discover: mixed fixture dir ────────────────────────────────── work = tempfile.mkdtemp(prefix="mk_par_disc_") fixture = Path(work) / "scripts" fixture.mkdir() files = { "alpha.py": 'PLUGIN = {\n "name": "alpha",\n "capabilities": ["cap"],\n "hooks": {"evt": "run"},\n "priority": 1,\n}\n', "beta.py": 'PLUGIN = {\n "name": "beta",\n "capabilities": ["cap"],\n "priority": 9,\n}\n', "gamma.py": 'PLUGIN = {"version": "0"}\n', # no name → error row "delta.py": 'def plain():\n pass\n', # no PLUGIN → skipped "_under.py": 'PLUGIN = {"name": "under"}\n', "microkernel.py": 'PLUGIN = {"name": "self"}\n', } for fname, src in files.items(): (fixture / fname).write_text(src) real_listdir = os.listdir old_dir, old_db = mk.SCRIPTS_DIR, mk.REGISTRY_DB mk.SCRIPTS_DIR = str(fixture) mk.REGISTRY_DB = os.path.join(work, "registry.json") os.listdir = lambda p: sorted(files) if str(p) == str(fixture) else real_listdir(p) try: oracle_result = copy.deepcopy(mk.discover()) oracle_state = mk._load_registry() finally: os.listdir = real_listdir mk.SCRIPTS_DIR, mk.REGISTRY_DB = old_dir, old_db oracle_proj = { "plugins": {n: {"active": p.get("active"), "registered_at": p.get("registered_at"), "file": p.get("file", "").replace(str(fixture), "")} for n, p in oracle_state["plugins"].items()}, "capabilities": oracle_state["capabilities"], "hooks": oracle_state["hooks"], } go = run_go(binpath, {"op": "discover", "dir": str(fixture), "now": FROZEN_NOW_AWARE}) go_proj = { "plugins": {n: {"active": p["active"], "registered_at": p["registered_at"], "file": p["file"].replace(str(fixture), "")} for n, p in go["plugins"].items()}, "capabilities": go["capabilities"], "hooks": go["hooks"], } check("discover.result", oracle_result, go["result"]) check("discover.registry", oracle_proj, go_proj) # ── registry load/save shapes ──────────────────────────────────── old_db = mk.REGISTRY_DB load_dir = tempfile.mkdtemp(prefix="mk_par_load_") full = json.dumps({ "plugins": {"q": {"manifest": {"name": "q", "priority": 3}, "registered_at": FROZEN_NOW_AWARE, "active": False, "file": "/y/q.py"}}, "capabilities": {"c": ["q"]}, "hooks": {"e2": ["q"]}, "log": [{"event": "e2", "timestamp": FROZEN_NOW_AWARE, "hooks_fired": 2, "duration_ms": 3.25}], }, indent=2) def oracle_load(content): mk.REGISTRY_DB = os.path.join(load_dir, "registry.json") try: db = mk.REGISTRY_DB if content is None: if os.path.exists(db): os.unlink(db) else: with open(db, "w") as f: f.write(content) reg = mk._load_registry() return {"plugins": {n: {"active": p.get("active"), "registered_at": p.get("registered_at"), "file": p.get("file", "")} for n, p in reg.get("plugins", {}).items()}, "capabilities": reg.get("capabilities", {}), "hooks": reg.get("hooks", {}), "log": reg.get("log", [])} finally: mk.REGISTRY_DB = old_db for label, content in (("missing", None), ("corrupt", "}{ nope"), ("full", full)): oracle = oracle_load(content) go = run_go(binpath, {"op": "registry_load", "content": content}, env_dir=load_dir) check(f"registry_load.{label}", oracle, {"plugins": {n: {"active": p["active"], "registered_at": p["registered_at"], "file": p["file"]} for n, p in go["plugins"].items()}, "capabilities": go["capabilities"], "hooks": go["hooks"], "log": go["log"]}) # roundtrip mk.REGISTRY_DB = os.path.join(load_dir, "registry.json") try: first = mk._load_registry() mk._save_registry(first) again = mk._load_registry() oracle_rt = {"stable": first == again} finally: mk.REGISTRY_DB = old_db go_rt = run_go(binpath, {"op": "registry_roundtrip"}, env_dir=load_dir) check("registry_roundtrip", oracle_rt, {"stable": go_rt["stable"]}) # ── fire / _run_hook branch matrix ─────────────────────────────── evt = "evt" fire_cases = [ ("ok_json", OK_JSON, {"name": "okp", "hooks": {evt: True}}, evt, {"a": 1, "b": "x"}, 30), ("raw_text", RAW_LONG, {"name": "rawp", "hooks": {evt: True}}, evt, None, 30), ("rc_error", BOOM, {"name": "boomp", "hooks": {evt: True}}, evt, None, 30), ("timeout", SLOW, {"name": "slowp", "hooks": {evt: True}}, evt, None, 1), ("skipped", OK_JSON, {"name": "offp", "hooks": {evt: True}, "auto_activate": False}, evt, None, 30), ("ghost_file", None, {"name": "ghostp", "hooks": {evt: True}, "_file": "/nonexistent/ghost.py"}, evt, None, 30), ("empty_file", None, {"name": "nofilep", "hooks": {evt: True}}, evt, None, 30), ("no_listeners", OK_JSON, {"name": "lonely", "capabilities": ["c"]}, "nope_evt", None, 30), ] for label, script, manifest, event, data, timeout in fire_cases: manifest = dict(manifest) if script is not None: work = tempfile.mkdtemp(prefix="mk_par_fx_") fpath = os.path.join(work, "plugin.py") with open(fpath, "w") as f: f.write(script) manifest["_file"] = fpath oracle = oracle_fire_with(manifest, event, data, timeout) blob = go_registry_blob(manifest) go = run_go(binpath, {"op": "fire", "registry": blob, "event": event, "data": data, "timeout_ms": timeout * 1000, "now": FROZEN_NOW_AWARE}) go = {"result": norm_fire(go["result"]), "log": norm_logs(go["log"])} check(f"fire.{label}", oracle, go) # two listeners in parallel work2 = tempfile.mkdtemp(prefix="mk_par_two_") tmpdb2 = os.path.join(work2, "registry.json") old_db = mk.REGISTRY_DB mk.REGISTRY_DB = tmpdb2 try: for nm in ("zz", "aa"): fpath = os.path.join(work2, nm + ".py") with open(fpath, "w") as f: f.write(OK_JSON) mk.register({"name": nm, "hooks": {evt: True}, "_file": fpath}) oracle = {"result": norm_fire(mk.fire(evt, data={"z": 1}, timeout=30)), "log": norm_logs(mk._load_registry().get("log", []))} finally: mk.REGISTRY_DB = old_db blob = { "plugins": { "zz": {"manifest": {"name": "zz"}, "registered_at": FROZEN_NOW_AWARE, "active": True, "file": os.path.join(work2, "zz.py")}, "aa": {"manifest": {"name": "aa"}, "registered_at": FROZEN_NOW_AWARE, "active": True, "file": os.path.join(work2, "aa.py")}, }, "capabilities": {}, "hooks": {evt: ["zz", "aa"]}, "log": [], } go = run_go(binpath, {"op": "fire", "registry": blob, "event": evt, "data": {"z": 1}, "timeout_ms": 30000, "now": FROZEN_NOW_AWARE}) go = {"result": norm_fire(go["result"]), "log": norm_logs(go["log"])} check("fire.two_listeners", oracle, go) mk.datetime = old_clock print(f"{'FAIL' if FAILS else 'OK'}: {CHECKS[0] - len(FAILS)}/{CHECKS[0]} checks passed") for f in FAILS: print(" " + f) return 1 if FAILS else 0 if __name__ == "__main__": sys.exit(main())