302 lines
13 KiB
Python
302 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
"""Live parity: iteration Rust kernel vs Python oracle (dichotomy wave).
|
|
|
|
Three layers, all must be green for parity 100%:
|
|
1. FROZEN — the 29 frozen-oracle cases replayed VERBATIM through both
|
|
the Python floor (oracle sanity) and the Rust kernel.
|
|
2. LIVE — oracle (RUNE_ITR_CORE=python) vs facade (kernel auto) on the
|
|
same inputs; `iteration._now` is pinned so generated_at /
|
|
recorded_at / invalidation clocks are byte-comparable.
|
|
3. FUZZ — 400 seeded random structural models / ledgers / behaviour
|
|
docs (seed 2479), every public function, both cores.
|
|
"""
|
|
import copy
|
|
import json
|
|
import os
|
|
import random
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(REPO / "src"))
|
|
sys.path.insert(0, str(REPO / "src" / "rust" / "evidence_provenance"))
|
|
|
|
os.environ.setdefault("RUNE_LEDGER_CORE", "python")
|
|
|
|
import rune.iteration as it # noqa: E402
|
|
import rune.behaviour_ledger as bl_mod # noqa: E402
|
|
from rune.structural_model import StructuralModel # noqa: E402
|
|
from rune.capability_ledger import CapabilityLedger # noqa: E402
|
|
from rune.behaviour_ledger import BehaviourLedger # noqa: E402
|
|
from rune import itr_loader # noqa: E402
|
|
|
|
PIN_NOW = "2026-10-12T00:00:00Z"
|
|
it._now = lambda: PIN_NOW
|
|
bl_mod._now = lambda: PIN_NOW
|
|
|
|
TRUTH = json.loads(
|
|
(REPO / "src" / "rust" / "iteration" / "src" / "frozen_it_truth.json")
|
|
.read_text())
|
|
|
|
KINDS = ["project", "domain", "subsystem", "component", "interface",
|
|
"state_store", "test_suite", "deployment_surface"]
|
|
REL_TYPES = ["belongs_to", "owns", "depends_on", "calls", "provides",
|
|
"consumes", "verified_by", "deployed_by"]
|
|
MATURITIES = ["DECLARED", "implemented", "retired", "superseded", "candidate"]
|
|
|
|
fails = []
|
|
counts = {"frozen": 0, "live": 0, "fuzz": 0}
|
|
|
|
|
|
def fail(layer, name, detail):
|
|
fails.append((layer, name, detail))
|
|
print(f"FAIL[{layer}] {name}: {detail}")
|
|
|
|
|
|
def wrap(doc, cls):
|
|
o = cls.__new__(cls)
|
|
o.doc = doc
|
|
return o
|
|
|
|
|
|
# ── fixture factory (oracle-shaped) ─────────────────────────────────────
|
|
|
|
def make_fixture(rnd, tmp, i):
|
|
model = StructuralModel(Path(tmp) / f"m{i}", f"p{i}")
|
|
n_ent = rnd.randint(2, 9)
|
|
ids = []
|
|
for j in range(n_ent):
|
|
kind = rnd.choice(KINDS)
|
|
eid = model.add(kind, f"e{j}")
|
|
ids.append(eid)
|
|
if rnd.random() < 0.4:
|
|
for e in model.doc["entities"]:
|
|
if e["id"] == eid:
|
|
e.setdefault("identities", {})["source"] = f"src/{j}.py"
|
|
for _ in range(rnd.randint(0, 12)):
|
|
a, b = rnd.choice(ids), rnd.choice(ids)
|
|
if a == b:
|
|
continue
|
|
try:
|
|
model.relate(a, rnd.choice(REL_TYPES), b,
|
|
status=rnd.choice(["active", "stale"]))
|
|
except ValueError:
|
|
pass
|
|
model.doc["schema_version"] = 1
|
|
ledger = CapabilityLedger(Path(tmp) / f"l{i}")
|
|
for j in range(rnd.randint(1, 4)):
|
|
ledger.define(
|
|
capability_id=f"cap{j}", name=f"c{j}", purpose="p",
|
|
owning_project=f"p{i}",
|
|
component=rnd.choice(ids),
|
|
source_locations=[f"src/{rnd.randint(0, n_ent)}.py"])
|
|
bh = BehaviourLedger(Path(tmp) / f"b{i}")
|
|
for j in range(rnd.randint(1, 3)):
|
|
bh.define(behaviour_id=f"b{j}", owning_project=f"p{i}",
|
|
intended_behaviour="x",
|
|
owner_component=rnd.choice(ids),
|
|
dependencies=[rnd.choice(ids)])
|
|
if rnd.random() < 0.6:
|
|
bh.record_real_world(f"b{j}", passed=True, detail="ok")
|
|
return model, ledger, bh
|
|
|
|
|
|
def run_all(model, ledger, bh, target):
|
|
"""Call every public fn through whatever core is currently pinned."""
|
|
out = {}
|
|
out["context_packet"] = it.context_packet(
|
|
model, ledger, target, intent="i", basis_summary={"d": 1})
|
|
out["predictive_impact"] = it.predictive_impact(model, target)
|
|
out["descriptive_impact"] = it.descriptive_impact(
|
|
model, target, observed_changed=[target, "surprise:x"],
|
|
generation="gf")
|
|
out["post_change_obligations"] = it.post_change_obligations(
|
|
model, target, behaviours=bh)
|
|
out["post_change_obligations_nobh"] = it.post_change_obligations(
|
|
model, target)
|
|
out["invalidate"] = None
|
|
return out
|
|
|
|
|
|
# ── 1. frozen replay ────────────────────────────────────────────────────
|
|
|
|
def frozen():
|
|
for group, cases in TRUTH.items():
|
|
if group in ("module", "now"):
|
|
continue
|
|
for c in cases:
|
|
counts["frozen"] += 1
|
|
args = copy.deepcopy(c["args"])
|
|
fn = c["fn"]
|
|
now = args.get("now", PIN_NOW)
|
|
if fn == "context_packet":
|
|
m = wrap(args["model"], StructuralModel)
|
|
l = wrap(args.get("ledger") or {"schema_version": 1, "capabilities": {}}, CapabilityLedger)
|
|
want = c["expect"]
|
|
got_o = it.context_packet(
|
|
m, l, args["target"], intent=args.get("intent", ""),
|
|
basis_summary=args.get("basis_summary"),
|
|
max_local_files=args.get("max_local_files", 12))
|
|
got_k = itr_loader.context_packet(
|
|
args["model"], args["ledger"], args["target"],
|
|
intent=args.get("intent", ""),
|
|
basis_summary=args.get("basis_summary"),
|
|
max_local_files=args.get("max_local_files", 12),
|
|
now=now)
|
|
elif fn == "predictive_impact":
|
|
m = wrap(args["model"], StructuralModel)
|
|
want = c["expect"]
|
|
got_o = it.predictive_impact(m, args["target"])
|
|
got_k = itr_loader.predictive_impact(args["model"],
|
|
args["target"])
|
|
elif fn == "descriptive_impact":
|
|
m = wrap(args["model"], StructuralModel)
|
|
want = c["expect"]
|
|
got_o = it.descriptive_impact(
|
|
m, args["target"],
|
|
observed_changed=args["observed_changed"],
|
|
generation=args.get("generation", ""))
|
|
got_k = itr_loader.descriptive_impact(
|
|
args["model"], args["target"],
|
|
observed_changed=args["observed_changed"],
|
|
generation=args.get("generation", ""), now=now)
|
|
elif fn == "record_impact_delta":
|
|
want = c["expect"]
|
|
got_o = json.dumps(args["delta"], sort_keys=True)
|
|
got_k = itr_loader.record_delta_line(args["delta"])
|
|
elif fn == "invalidate_behaviours":
|
|
want = c["expect"]
|
|
b = wrap(copy.deepcopy(args["behaviours"]), BehaviourLedger)
|
|
recs_o = it.invalidate_behaviours(
|
|
wrap({"relationships": []}, StructuralModel), b,
|
|
changed_entities=set(args["changed"]),
|
|
generation=args["generation"])
|
|
got_o = {"doc": b.doc, "recs": recs_o}
|
|
doc_k, recs_k = itr_loader.invalidate(
|
|
args["behaviours"], args["changed"],
|
|
args["generation"], now)
|
|
got_k = {"doc": doc_k, "recs": recs_k}
|
|
elif fn == "post_change_obligations":
|
|
m = wrap(args["model"], StructuralModel)
|
|
want = c["expect"]
|
|
bh = (wrap(args["behaviours"], BehaviourLedger)
|
|
if "behaviours" in args else None)
|
|
got_o = it.post_change_obligations(
|
|
m, args["target"],
|
|
changed_entities=set(args["changed"])
|
|
if "changed" in args else None,
|
|
behaviours=bh)
|
|
got_k = itr_loader.post_change_obligations(
|
|
args["model"], args["target"],
|
|
changed=args.get("changed"),
|
|
behaviours_doc=args.get("behaviours"))
|
|
else: # pragma: no cover
|
|
continue
|
|
for tag, got in (("oracle", got_o), ("kernel", got_k)):
|
|
if json.dumps(got, sort_keys=True) != json.dumps(
|
|
want, sort_keys=True):
|
|
fail("frozen", f"{fn}/{c['name']}/{tag}",
|
|
f"want={json.dumps(want, sort_keys=True)[:300]} "
|
|
f"got={json.dumps(got, sort_keys=True)[:300]}")
|
|
|
|
|
|
# ── 2+3. live + fuzz ────────────────────────────────────────────────────
|
|
|
|
def compare_pair(name, o, k):
|
|
counts["live"] += 1
|
|
jo, jk = json.dumps(o, sort_keys=True), json.dumps(k, sort_keys=True)
|
|
if jo != jk:
|
|
fail("live", name, f"oracle={jo[:300]} kernel={jk[:300]}")
|
|
|
|
|
|
def live_and_fuzz():
|
|
rnd = random.Random(2479)
|
|
with tempfile.TemporaryDirectory(prefix="it_parity_") as tmp:
|
|
# live on the frozen fixture inputs
|
|
for group, cases in TRUTH.items():
|
|
if group in ("module", "now"):
|
|
continue
|
|
for c in cases:
|
|
args = c["args"]
|
|
if "model" not in args:
|
|
continue
|
|
m = wrap(args["model"], StructuralModel)
|
|
l = wrap(args.get("ledger") or {"schema_version": 1, "capabilities": {}}, CapabilityLedger)
|
|
bh = (wrap(args["behaviours"], BehaviourLedger)
|
|
if "behaviours" in args else None)
|
|
target = args["target"]
|
|
os.environ["RUNE_ITR_CORE"] = "python"
|
|
o = run_all(m, l, bh or
|
|
wrap({"behaviours": {}}, BehaviourLedger),
|
|
target)
|
|
os.environ["RUNE_ITR_CORE"] = "auto"
|
|
k = run_all(m, l, bh or
|
|
wrap({"behaviours": {}}, BehaviourLedger),
|
|
target)
|
|
for key in o:
|
|
compare_pair(f"{c['name']}/{key}", o[key], k[key])
|
|
# fuzz
|
|
for i in range(400):
|
|
model, ledger, bh = make_fixture(rnd, tmp, i)
|
|
target = rnd.choice(
|
|
[e["id"] for e in model.doc["entities"]])
|
|
os.environ["RUNE_ITR_CORE"] = "python"
|
|
o = run_all(model, ledger, bh, target)
|
|
os.environ["RUNE_ITR_CORE"] = "auto"
|
|
k = run_all(model, ledger, bh, target)
|
|
for key in o:
|
|
counts["fuzz"] += 1
|
|
if json.dumps(o[key], sort_keys=True) != json.dumps(
|
|
k[key], sort_keys=True):
|
|
fail("fuzz", f"fuzz{i}/{key}",
|
|
f"oracle={json.dumps(o[key], sort_keys=True)[:300]} "
|
|
f"kernel={json.dumps(k[key], sort_keys=True)[:300]}")
|
|
# record_impact_delta line parity
|
|
delta = o["descriptive_impact"]
|
|
counts["fuzz"] += 1
|
|
if json.dumps(delta, sort_keys=True) != \
|
|
itr_loader.record_delta_line(delta):
|
|
fail("fuzz", f"fuzz{i}/delta_line", "line mismatch")
|
|
# invalidate parity (deep-copied docs both sides)
|
|
changed = set(rnd.sample(
|
|
[e["id"] for e in model.doc["entities"]],
|
|
k=min(2, len(model.doc["entities"]))))
|
|
bo = BehaviourLedger.__new__(BehaviourLedger)
|
|
bo.doc = copy.deepcopy(bh.doc)
|
|
os.environ["RUNE_ITR_CORE"] = "python"
|
|
recs_o = it.invalidate_behaviours(model, bo,
|
|
changed_entities=changed,
|
|
generation="gz")
|
|
os.environ["RUNE_ITR_CORE"] = "auto"
|
|
bk = BehaviourLedger.__new__(BehaviourLedger)
|
|
bk.doc = copy.deepcopy(bh.doc)
|
|
recs_k = it.invalidate_behaviours(model, bk,
|
|
changed_entities=changed,
|
|
generation="gz")
|
|
counts["fuzz"] += 1
|
|
if json.dumps(bo.doc, sort_keys=True) != json.dumps(
|
|
bk.doc, sort_keys=True) or \
|
|
json.dumps(recs_o, sort_keys=True) != \
|
|
json.dumps(recs_k, sort_keys=True):
|
|
fail("fuzz", f"fuzz{i}/invalidate", "doc/recs mismatch")
|
|
|
|
|
|
def main():
|
|
if not itr_loader.available():
|
|
print("kernel not built — build src/rust/iteration first")
|
|
return 1
|
|
os.environ["RUNE_ITR_CORE"] = "python"
|
|
frozen()
|
|
live_and_fuzz()
|
|
os.environ["RUNE_ITR_CORE"] = "auto"
|
|
total = counts["frozen"] + counts["live"] + counts["fuzz"]
|
|
print(f"parity {total - len(fails)}/{total} "
|
|
f"(frozen {counts['frozen']}, live {counts['live']}, "
|
|
f"fuzz {counts['fuzz']})")
|
|
return 1 if fails else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|