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).
417 lines
16 KiB
Python
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()
|