Files
rune/scripts/gen_microkernel_gap_truth.py
Nick b9b089fa49 feat(microkernel): close PARTIAL gaps — dichotomy wave
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).
2026-10-14 02:30:29 +01:00

417 lines
16 KiB
Python

#!/usr/bin/env python3
"""gen_microkernel_gap_truth.py — microkernel gap-closing truth battery
(dichotomy wave #2479, worktree rune-wave-cq).
Closes the PARTIAL audit gaps: now_iso, discover, _extract_manifest,
_load_registry, _save_registry, _run_hook (fire's hook runner).
Uses the SHARED harness (scripts/freeze_truth.py): RUNE core pinned to
python, deep-copied inputs, envelope with its own frozen_now:
PYTHONPATH=src python3 scripts/freeze_truth.py \
--verify src/go/microkernel/truth_microkernel_gap.json \
--module rune.microkernel
STATEFULNESS / DETERMINISM LAWS:
* _extract_manifest takes an fpath — the shim carries the file CONTENT
in args, writes a temp file, calls the real oracle fn, returns the
parsed manifest (paths never enter the result).
* discover iterates os.listdir (filesystem order) and stamps absolute
_file paths — the shim installs a fixture dir, pins os.listdir to the
EXACT frozen filename order (sorted — matches the Go port's
os.ReadDir), calls the real discover(), and normalizes every path to
<dir>/<fname> stubs. Machine-independent, replay-stable.
* _run_hook is a closure inside fire() — it is exercised through the
real fire() with ONE listener per branch class (fixture scripts are
in-file constants: deterministic stdout/stderr). wall-clock durations
(duration_ms / total_duration_ms) are normalized to "<ms>" — the
no-wall-clock law.
* _load_registry/_save_registry: missing file, corrupt file, full-shape
file (verbatim load), and a save->load roundtrip of the default shape.
Usage: python3 scripts/gen_microkernel_gap_truth.py (from repo root)
python3 scripts/gen_microkernel_gap_truth.py --verify
"""
import copy
import json
import os
import sys
import tempfile
from datetime import datetime as _dt, timezone as _tz
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "scripts"))
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
from freeze_truth import freeze, write_envelope # noqa: E402
FROZEN_NOW = "2026-10-13T11:05:00"
os.environ["RUNE_FROZEN_NOW"] = FROZEN_NOW
os.environ["RUNE_MICROKERNEL_CORE"] = "python" # poisoned-truth law
import microkernel as mk # noqa: E402
SHIM = "gen_microkernel_gap_truth_shims"
def _pin_mk_clock():
"""microkernel's fire() mixes datetime.now() (naive) and
datetime.now(timezone.utc) (aware) — the harness clock is naive-only,
so shims that reach register()/fire() pin an AWARE frozen clock.
Durations collapse to 0.0 (normalized to <ms> by the rig regardless)."""
base = _dt.fromisoformat(FROZEN_NOW).replace(tzinfo=_tz.utc)
class _Frozen(_dt):
@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
# ── fixture hook scripts (deterministic output; stdlib only) ──────────────
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 ' * 120)\n" # 1200 codepoints, non-JSON
)
BOOM = (
"import sys\n"
"sys.stderr.write('\\u00e9rr\\u00f4r-' * 100)\n" # 600 codepoints
"sys.exit(3)\n"
)
SLOW = "import time\ntime.sleep(5)\n"
def _norm_fire(result):
"""Normalize wall-clock fields + as_completed order (the rig law)."""
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>"
if isinstance(r.get("error"), str):
r["error"] = r["error"]
out["results"] = sorted(out.get("results", []), key=lambda r: r.get("name", ""))
return out
def _norm_log(log):
out = []
for e in log:
e = dict(e)
e["duration_ms"] = "<ms>"
out.append(e)
return out
def _registry_projection(reg, dir_token):
plugins = {}
for name, p in reg["plugins"].items():
plugins[name] = {
"active": p.get("active"),
"registered_at": p.get("registered_at"),
"file": p.get("file", "").replace(dir_token, "<dir>"),
}
return {
"plugins": plugins,
"capabilities": reg.get("capabilities", {}),
"hooks": reg.get("hooks", {}),
}
# ── wrapper shims: module-level names the harness can call ────────────────
def _mk_extract(content):
fd, fpath = tempfile.mkstemp(suffix=".py", prefix="mk_gap_")
try:
with os.fdopen(fd, "w") as f:
f.write(content)
return {"result": copy.deepcopy(mk._extract_manifest(fpath))}
finally:
os.unlink(fpath)
def _mk_discover(filenames, contents, now):
"""Fixture-driven discover: filenames (EXACT listing order — pass them
sorted, matching the Go port's os.ReadDir), contents {fname: source}."""
work = tempfile.mkdtemp(prefix="mk_disc_")
fixture = Path(work) / "scripts"
fixture.mkdir()
for fname, src in contents.items():
(fixture / fname).write_text(src)
tmpdb = os.path.join(work, "registry.json")
old_dir, old_db = mk.SCRIPTS_DIR, mk.REGISTRY_DB
real_listdir = os.listdir
old_clock = _pin_mk_clock()
mk.SCRIPTS_DIR = str(fixture)
mk.REGISTRY_DB = tmpdb
os.listdir = lambda p: list(filenames) if str(p) == str(fixture) else real_listdir(p)
try:
result = copy.deepcopy(mk.discover())
state = _registry_projection(mk._load_registry(), str(fixture))
finally:
os.listdir = real_listdir
mk.SCRIPTS_DIR, mk.REGISTRY_DB = old_dir, old_db
mk.datetime = old_clock
return {"result": result, "registry": state}
def _mk_load(content):
"""_load_registry over a fixture registry file (None = missing file).
Projection: the port stores TYPED manifests, so the frozen contract is
the round-tripped shape (active/registered_at/file + indexes + log),
not the verbatim manifest dict."""
work = tempfile.mkdtemp(prefix="mk_load_")
db = os.path.join(work, "registry.json")
old_db = mk.REGISTRY_DB
mk.REGISTRY_DB = db
try:
if content is not None:
with open(db, "w") as f:
f.write(content)
elif os.path.exists(db):
os.unlink(db)
loaded = mk._load_registry()
return {"result": _registry_projection(loaded, "<none>"), "log": loaded.get("log", [])}
finally:
mk.REGISTRY_DB = old_db
def _mk_save_load_default():
"""_save_registry -> _load_registry roundtrip of the default shape."""
work = tempfile.mkdtemp(prefix="mk_rt_")
db = os.path.join(work, "registry.json")
old_db = mk.REGISTRY_DB
mk.REGISTRY_DB = db
try:
loaded = mk._load_registry()
mk._save_registry(loaded)
again = mk._load_registry()
return {"first": copy.deepcopy(loaded), "again": copy.deepcopy(again),
"stable": loaded == again}
finally:
mk.REGISTRY_DB = old_db
def _mk_fire(script, manifest, event, data, timeout):
"""fire() with ONE registered listener whose _file is the fixture
script (or a ghost/empty path for the not-found class). Exercises the
oracle's _run_hook closure branch selected by the fixture."""
work = tempfile.mkdtemp(prefix="mk_fire_")
if script is not None:
fpath = os.path.join(work, "plugin.py")
with open(fpath, "w") as f:
f.write(script)
manifest = dict(manifest)
manifest["_file"] = fpath
else:
manifest = dict(manifest) # _file stays whatever the caller set
tmpdb = os.path.join(work, "registry.json")
old_db = mk.REGISTRY_DB
old_clock = _pin_mk_clock()
mk.REGISTRY_DB = tmpdb
try:
mk.register(manifest)
result = mk.fire(event, data=data, timeout=timeout)
state = mk._load_registry()
return {"result": _norm_fire(result), "log": _norm_log(state.get("log", []))}
finally:
mk.REGISTRY_DB = old_db
mk.datetime = old_clock
def _mk_fire_two(scripts, manifests, event, data, timeout):
"""fire() with TWO listeners (parallelism + log entry)."""
work = tempfile.mkdtemp(prefix="mk_fire2_")
tmpdb = os.path.join(work, "registry.json")
old_db = mk.REGISTRY_DB
old_clock = _pin_mk_clock()
mk.REGISTRY_DB = tmpdb
try:
for script, manifest in zip(scripts, manifests):
manifest = dict(manifest)
if script is not None:
fpath = os.path.join(work, manifest["name"] + ".py")
with open(fpath, "w") as f:
f.write(script)
manifest["_file"] = fpath
mk.register(manifest)
result = mk.fire(event, data=data, timeout=timeout)
state = mk._load_registry()
return {"result": _norm_fire(result), "log": _norm_log(state.get("log", []))}
finally:
mk.REGISTRY_DB = old_db
mk.datetime = old_clock
CACHEY = (
'PLUGIN = {\n'
' "name": "cache",\n'
' "version": "0.1.0",\n'
' "description": "Cache system — event-driven invalidation.",\n'
' "capabilities": ["cache", "invalidation"],\n'
' "hooks": {\n'
' "on_session_end": "handle_hook",\n'
' "on_memory_add": "handle_hook",\n'
' },\n'
' "dependencies": [],\n'
' "priority": 1,\n'
' "auto_activate": True,\n'
'}\n'
)
def main():
sys.modules[SHIM] = sys.modules["__main__"]
cases = []
# ── now_iso (pinned clock: RUNE_FROZEN_NOW → datetime frozen) ──
cases.append(("now_iso", [], {}))
# ── _extract_manifest branches ────────────────────────────────
cases.append((f"{SHIM}._mk_extract", [CACHEY], {}))
cases.append((f"{SHIM}._mk_extract", ["X = 1\n"], {}))
cases.append((f"{SHIM}._mk_extract", ["PLUGIN = {}\n"], {}))
cases.append((f"{SHIM}._mk_extract", ["PLUGIN = {}\nX = 1\n"], {}))
cases.append((f"{SHIM}._mk_extract", ['PLUGIN = {\n "name": "x",\n'], {}))
cases.append((f"{SHIM}._mk_extract",
["PLUGIN = {'name': 'café', 'n': 1.5, 'neg': -3, 'big': 1e3}\n"], {}))
cases.append((f"{SHIM}._mk_extract",
['PLUGIN = {\n # comment inside\n "name": "x",\n}\n'], {}))
cases.append((f"{SHIM}._mk_extract",
['PLUGIN = {\n "name": "x",\n "hooks": {"a": True, "b": False},\n "nested": {"deep": [1, 2, {"k": "v"}]},\n}\n'], {}))
# ── discover (fixture dir, pinned listing order = sorted) ─────
mixed_files = ["_private.py", "a_cache.py", "b_noname.py", "c_plain.py",
"d_falsy.py", "microkernel.py", "notes.txt"]
mixed_contents = {
"a_cache.py": CACHEY,
"b_noname.py": 'PLUGIN = {\n "version": "0.1.0",\n}\n',
"c_plain.py": "def run():\n pass\n",
"d_falsy.py": ('PLUGIN = {\n "name": "d",\n "capabilities": ["d"],\n'
' "hooks": {"on_x": False, "on_y": "", "on_z": 0},\n}\n'),
"_private.py": CACHEY,
"microkernel.py": CACHEY,
"notes.txt": "not a plugin",
}
cases.append((f"{SHIM}._mk_discover", [mixed_files, mixed_contents, FROZEN_NOW], {}))
cases.append((f"{SHIM}._mk_discover", [[], {}, FROZEN_NOW], {}))
shared_files = ["s1.py", "s2.py"]
shared_contents = {
"s1.py": 'PLUGIN = {\n "name": "s1",\n "capabilities": ["shared"],\n "priority": 5,\n}\n',
"s2.py": 'PLUGIN = {\n "name": "s2",\n "capabilities": ["shared"],\n "priority": 10,\n}\n',
}
cases.append((f"{SHIM}._mk_discover", [shared_files, shared_contents, FROZEN_NOW], {}))
# ── _load_registry / _save_registry ───────────────────────────
full_registry = json.dumps({
"plugins": {"p": {"manifest": {"name": "p", "priority": 1},
"registered_at": "2026-10-13T11:05:00+00:00",
"active": True, "file": "/x/p.py"}},
"capabilities": {"cap": ["p"]},
"hooks": {"evt": ["p"]},
"log": [{"event": "evt", "timestamp": "2026-10-13T11:05:00+00:00",
"hooks_fired": 1, "duration_ms": 12.5}],
}, indent=2)
cases.append((f"{SHIM}._mk_load", [None], {}))
cases.append((f"{SHIM}._mk_load", ["{oops"], {}))
cases.append((f"{SHIM}._mk_load", [full_registry], {}))
cases.append((f"{SHIM}._mk_save_load_default", [], {}))
# ── _run_hook (through fire, one branch class per case) ───────
evt = "on_session_end"
cases.append((f"{SHIM}._mk_fire",
[OK_JSON, {"name": "okp", "hooks": {evt: True}},
evt, {"k": "v", "n": 2}, 30], {}))
cases.append((f"{SHIM}._mk_fire",
[RAW_LONG, {"name": "rawp", "hooks": {evt: True}}, evt, None, 30], {}))
cases.append((f"{SHIM}._mk_fire",
[BOOM, {"name": "boomp", "hooks": {evt: True}}, evt, None, 30], {}))
cases.append((f"{SHIM}._mk_fire",
[SLOW, {"name": "slowp", "hooks": {evt: True}}, evt, None, 1], {}))
cases.append((f"{SHIM}._mk_fire",
[OK_JSON, {"name": "offp", "hooks": {evt: True}, "auto_activate": False},
evt, None, 30], {}))
cases.append((f"{SHIM}._mk_fire",
[None, {"name": "ghostp", "hooks": {evt: True}, "_file": "/nonexistent/ghost.py"},
evt, None, 30], {}))
cases.append((f"{SHIM}._mk_fire",
[None, {"name": "nofilep", "hooks": {evt: True}}, evt, None, 30], {}))
# fire with NO listeners: early return, no log entry
cases.append((f"{SHIM}._mk_fire",
[OK_JSON, {"name": "lonely", "capabilities": ["x"]}, "no_such_event", None, 30], {}))
# two listeners in parallel: sorted results + one log entry
cases.append((f"{SHIM}._mk_fire_two",
[[OK_JSON, OK_JSON],
[{"name": "zz", "hooks": {evt: True}},
{"name": "aa", "hooks": {evt: True}}],
evt, {"k": 1}, 30], {}))
# harness resolves dotted names against ONE module — freeze per case
# against the fn's OWN module (same law as gen_hook_engine_gap_truth).
truth = []
for fn, args, kwargs in cases:
if fn.startswith(f"{SHIM}."):
mod, short = SHIM, fn.split(".")[-1]
else:
mod, short = "microkernel", fn
rows = freeze(mod, [(short, args, kwargs)], oracle_module=mod)
rows[0]["fn"] = fn
rows[0]["oracle"] = mod
truth.append(rows[0])
target = REPO / "src" / "go" / "microkernel" / "truth_microkernel_gap.json"
write_envelope(FROZEN_NOW, truth, target)
print(f"wrote {target} ({len(truth)} cases)")
def verify(path):
"""Per-case-module verify (same byte-compare law as the harness)."""
raw = json.loads(path.read_text())
frozen = raw["cases"] if isinstance(raw, dict) else raw
if isinstance(raw, dict) and raw.get("frozen_now"):
os.environ["RUNE_FROZEN_NOW"] = raw["frozen_now"]
sys.modules.setdefault(SHIM, sys.modules["__main__"])
fresh = []
for c in frozen:
mod = c.get("oracle", "microkernel")
short = c["fn"].split(".")[-1]
rows = freeze(mod, [(short, c.get("args", []), c.get("kwargs", {}))],
oracle_module=mod)
rows[0]["fn"] = c["fn"]
rows[0]["oracle"] = mod
fresh.append(rows[0])
if json.dumps(fresh, sort_keys=True) != json.dumps(frozen, sort_keys=True):
print(f"MISMATCH: regenerated truth differs from {path}")
for f, g in zip(frozen, fresh):
if json.dumps(f, sort_keys=True) != json.dumps(g, sort_keys=True):
print(f" first divergence at fn={f['fn']}")
print(f" frozen: {json.dumps(f['expected'])[:300]}")
print(f" oracle: {json.dumps(g['expected'])[:300]}")
break
return 1
print(f"OK: {len(frozen)} cases byte-identical against live oracles")
return 0
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--verify":
sys.exit(verify(REPO / "src" / "go" / "microkernel" / "truth_microkernel_gap.json"))
main()