Files
rune/scripts/parity_persona_gate.py
T

108 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""Live parity harness for the persona_gate gap port (#2479 tranche, GO).
Runs the LIVE Python oracle (_py_decide / decide dispatch, embedder down
via a dead OLLAMA_URL on BOTH sides) against the Go binary's py-decide
command over every py_decide truth input plus extra live edge cases.
100% parity required. No socket, no model, no journal: the only IO is
the in-process decision itself.
"""
import json
import os
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
os.environ["RUNE_COGNITION_CORE"] = "python"
os.environ["OLLAMA_URL"] = "http://127.0.0.1:9" # embedder down BOTH sides
sys.path.insert(0, str(REPO / "src"))
import rune.persona_gate as pg # noqa: E402
GO_DIR = REPO / "src" / "go" / "persona-gate"
def go_py_decide(text, rules):
wire = [dict(r, words=sorted(r["words"])) for r in rules]
out = subprocess.run(
["go", "run", ".", "py-decide", "--rules", json.dumps(wire), text],
cwd=GO_DIR, capture_output=True, text=True,
env={**os.environ, "OLLAMA_URL": "http://127.0.0.1:9"},
timeout=120,
)
if out.returncode != 0:
raise RuntimeError(f"go run failed: {out.stderr}")
return json.loads(out.stdout)
def norm(d):
d = json.loads(json.dumps(d))
if d.get("cited") is None:
d["cited"] = None
if d.get("engaged") is None:
d["engaged"] = []
return d
def mk_rule(text, topic="operating", priority=5, restrictive=False, user_rule=False):
words = pg._py_lex(text)
return {
"text": text, "topic": topic, "priority": priority,
"restrictive": restrictive, "user_rule": user_rule,
"words": set(words),
"core": " ".join(sorted(words)),
}
cases = []
# 1. every frozen py_decide input, replayed LIVE (embedder down on both sides)
truth = json.loads((GO_DIR / "truth.json").read_text())
for row in truth["rows"]:
if row["fn"] == "py_decide":
rules = [dict(r, words=set(r["words"])) for r in row["rules"]]
cases.append((f"frozen:{row['name']}", row["text"], rules))
# 2. extra live edge cases
R = [
mk_rule("never publish dangerous devices", topic="constitution", priority=0,
restrictive=True, user_rule=True),
mk_rule("keep the memory store tidy", priority=3),
mk_rule("protect the live game environment from cold calculation"),
]
LIVE = [
("", R),
("never publish the foundry vtt system", R),
("I NEVER ignore a point raised in review", R),
("update the github for the memory project", R),
("what time is it", R),
("the devices are dangerous and must be tested", R),
("homemade AK47 devices", R),
("tidy up the store please", R),
("protect live games from calculation", R),
(" surrounded by whitespace ", R),
("ÉMIGRÉ café devices naïve", R),
("w" * 400, R),
("no rules at all", []),
("must override bypass force the gate", R),
]
for i, (text, rules) in enumerate(LIVE):
cases.append((f"live:{i}", text, rules))
fails = 0
for name, text, rules in cases:
oracle = norm(pg._py_decide(text, [dict(r) for r in rules]))
dispatch = norm(pg.decide(text, [dict(r) for r in rules]))
assert oracle == dispatch, f"{name}: decide dispatch != _py_decide"
go = norm(go_py_decide(text, rules))
if go != oracle:
fails += 1
print(f"PARITY FAIL {name}\n go: {json.dumps(go, ensure_ascii=False)}\n"
f" oracle: {json.dumps(oracle, ensure_ascii=False)}")
print(f"live parity: {len(cases) - fails}/{len(cases)} cases ({len(cases)} py_decide inputs, "
f"decide dispatch == _py_decide on all)")
sys.exit(1 if fails else 0)