Name-preserving port of the 6 unmatched fns into src/go/microkernel: NowIso (+clock seam), ExtractManifest (brace-counted PLUGIN block + literal_eval-subset parser), Discover, RunHook (subprocess hook runner), LoadRegistry/SaveRegistry (renamed; corrupt-file full-default parity), plus Fire wiring the CLI fire case to real 8-way parallel dispatch with the bounded fire-log. Register now honors Python truthiness for hook values (string targets index) and plumbs _file into entries. Frozen truth: 25-case envelope via the shared harness (regen-stable, --verify byte-identical x2); Go battery 25/25 subtests, -race clean; live oracle-vs-Go parity 24/24 (scripts/check_microkernel_parity.py); gofmt/vet/go build ./... clean; Python both-pins green for the module (18 test_p1_01 failures proven pre-existing on stashed base). Audit: microkernel 6/12 PARTIAL -> 12/12 FULL (evidence: verification/summary-microkernel.md + microkernel-*.exit).
345 lines
15 KiB
Python
345 lines
15 KiB
Python
#!/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 → "<ms>",
|
|
fire results name-sorted, fixture paths → <dir>. Data keys alphabetical
|
|
(the Go map marshal is key-sorted; plugins canonicalize the payload).
|
|
|
|
Usage: python3 scripts/check_microkernel_parity.py [--bin <path>]
|
|
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"] = "<ms>"
|
|
for r in out.get("results", []):
|
|
if "duration_ms" in r:
|
|
r["duration_ms"] = "<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": "<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), "<dir>")}
|
|
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), "<dir>")}
|
|
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())
|