Files
rune/scripts/check_operator_lifecycle_parity.py
Nick 5a3f501351 feat(rune_operator): close TASK-LIFECYCLE/STATE family gaps — dichotomy wave
Name-preserving port of the 12 audit-missing lifecycle fns into
src/go/operator-deep/gaps_lifecycle.go (family lane factory-heavy-ro-life):
GetConversationWindow, TasksToDictList (list_tasks x2), RoutingProfilesPath,
ConstructionStorePath, ConstructionNoteDoc, the _construction_activation
glue (SplitScaffoldOverride/DeriveConstructionTarget/observation rows),
InitConversationFiles+MigrateLegacyTasks, LoadOperatorState,
SaveOperatorStateOrd (ordered Python json.dump byte-parity writer),
ClearPendingApproval, TerminalizeExecutionFailure, EmitTaskOutcomePayload,
EmitTaskFailedPayload. CLI dispatch via the 'lifecycle' subcommand.

Truth: truth_lifecycle.json 37 rows frozen under scripts/olc_freeze_seams
(pinned clock, deterministic uuid counter, fixed scratch, cone cores,
event_bus recorder) — freeze_truth --verify byte-identical x3. Battery
replays verbatim (ordered parse; save rows byte-compare). Live parity
35/35 (scripts/check_operator_lifecycle_parity.py) incl. env-unset derive
branches. gofmt/vet/build/test green; Python 65x2 both pins.

Dichotomy audit rune_operator 29/68 -> 42/68 (family complete; remaining
fns are sibling lanes). Divergences documented in
verification/olc-summary.md (segments-store evidence sort, migration msg-id
placeholders, checkpoint-window input, classifier-as-input seams).
2026-10-15 01:28:50 +01:00

384 lines
16 KiB
Python

#!/usr/bin/env python3
"""check_operator_lifecycle_parity.py — live oracle-vs-Go parity sweep for
the rune_operator TASK-LIFECYCLE/STATE family gap closure (#2479, family
lane factory-heavy-ro-life).
The frozen envelope (truth_lifecycle.json, 37 rows) pins the family
byte-identically under the olc_freeze_seams rig. THIS script drives the
LIVE Python oracle and the Go binary (src/go/operator-deep, `lifecycle`
CLI dispatch) across every branch of the family surface the harness
cannot freeze directly — the repo-root DERIVE branches
(_routing_profiles_path / _construction_store with env UNSET), extra
override/target/activation branches, the migration kernel on live
fixtures (fresh + rerun + unknown-status), save byte-parity on richer
states, load degrade branches, and the terminalize/emit matrices —
comparing JSON with evidence-order normalization (the scaffold segments
store canonicalizes observation list values on round-trip; membership is
the contract).
Runs UNDER the rig (PYTHONPATH=scripts/olc_freeze_seams) so the oracle
side is clock/id pinned and event_bus recorded. Both sides use the same
pinned instants; no wall-clock normalization is needed.
Usage:
PYTHONPATH=scripts/olc_freeze_seams:src python3 \\
scripts/check_operator_lifecycle_parity.py \\
[--bin src/go/operator-deep/operator-deep]
Exit 0 on 100% parity, 1 with the first divergence.
"""
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
import rune.rune_operator as ro # noqa: E402 (rig pins clocks/cores)
FROZEN_ISO = "2026-10-14T22:40:00.123456+00:00"
FROZEN_UTC = "2026-10-14T22:40:00Z"
CHECKS = [
# _routing_profiles_path — env branch + DERIVE branch
("routing-profiles-path", {}, None),
# _construction_store — env branch + DERIVE branch
("construction-store", {}, None),
# get_conversation_window — channel/n matrix (incl. empty bucket)
("conv-window", {"channel": "operator", "n": 1}, None),
("conv-window", {"channel": "voice", "n": 8}, None),
("conv-window", {"channel": "nope", "n": 3}, None),
# list_tasks / module list_tasks
("list-tasks", {"rich": True}, None),
("list-tasks", {"rich": False}, None),
# _clear_pending_approval
("clear-pending", {"rich": True}, None),
# _construction_note — truncation boundaries
("construction-note", {"text": "n" * 299}, None),
("construction-note", {"text": "n" * 300}, None),
("construction-note", {"text": "n" * 301}, None),
# _construction_activation override/target branches
("construction-override", {"desc": "scaffold-mode: UNKNOWN just talk"}, None),
("construction-override", {"desc": "/scaffold-mode NOT_CONSTRUCTION\r\nmultiline body"}, None),
("construction-target", {"desc": "rune rune rune"}, None),
("construction-target", {"desc": "a new app appears"}, None),
("construction-target", {"desc": "patch the existing module"}, None),
("construction-target", {"desc": "whatever else"}, None),
# _emit_task_outcome / _emit_task_failed matrices
("emit-outcome", {"status": "completed", "len": 200}, None),
("emit-outcome", {"status": "completed", "len": 199}, None),
("emit-outcome", {"status": "failed", "len": 0, "ver": True, "route": "X"}, None),
("emit-failed", {"len": 299}, None),
("emit-failed", {"len": 300}, None),
("emit-failed", {"len": 301}, None),
# _save byte parity — pending blobs in insertion order
("save-state", {"pending": True}, None),
("save-state", {"pending": False}, None),
# _load branches
("load-state", {"fixture": "partial"}, None),
("load-state", {"fixture": "nulls"}, None),
("load-state", {"fixture": "not-json"}, None),
# migration kernel — fresh / rerun / unknown status
("migrate", {"rerun": False}, None),
("migrate", {"rerun": True}, None),
("migrate", {"fixture": "bad-status"}, None),
("migrate", {"fixture": "research"}, None),
# _terminalize_execution_failure matrix
("terminalize", {"error": None}, None),
("terminalize", {"error": "explicit"}, None),
("terminalize", {"error": "empty"}, None),
]
FIXTURES = {
"partial": '{"identity": "RuneX", "tasks": [{"task_id": "t1", "description": "d"}]}',
"nulls": '{"tasks": [{"task_id": "t1", "description": "d", "status": null}]}',
"not-json": "][",
"bad-status": '{"tasks": [{"task_id": "b", "description": "d", "status": "weird"}]}',
"research": ('{"tasks": [{"task_id": "r1", "description": "explore", '
'"status": "research_required"}], "current_task": "r1"}'),
}
def _task(status="running", length=0, model="m1"):
return {
"task_id": "t_parity", "description": "d" * length or "desc",
"status": status, "assigned_model": model if model else None,
"result": "r" * length, "created_at": "2026-10-14T20:00:00.000000+00:00",
"completed_at": None, "route": "", "route_instruction": "",
}
def _state(rich, pending):
tasks = [
_task("completed", 10, "m1"),
_task("pending", 0, None),
]
st = {
"identity": "Rune", "role": "Operator",
"current_task": "t_parity" if rich else None,
"active_model": "m1" if rich else None,
"tasks": tasks,
"pending_task_id": "t_parity" if pending else None,
"pending_request": {"name": "run_command", "args": ["make", "test"]} if pending else None,
"pending_runtime_state": {"exposed_tools": ["read_file"]} if pending else None,
}
return st
def _msgs():
return [
{"msg_id": "m1", "ts": "2026-10-14T19:00:01.000000+00:00", "role": "user",
"content": "a", "channel_id": "operator", "task_id": "t1", "token_estimate": 1},
{"msg_id": "m2", "ts": "2026-10-14T19:00:02.000000+00:00", "role": "assistant",
"content": "b", "channel_id": "voice", "task_id": "t1", "token_estimate": 1},
{"msg_id": "m3", "ts": "2026-10-14T19:00:03.000000+00:00", "role": "user",
"content": "c", "channel_id": "operator", "task_id": None, "token_estimate": 1},
]
def _norm(v):
"""Canonicalize for comparison: sort classification_evidence lists
(segments-store canonicalization law), stringify numbers stably."""
if isinstance(v, dict):
return {k: (_sorted_ev(x) if k == "classification_evidence" else _norm(x))
for k, x in v.items()}
if isinstance(v, list):
return [_norm(x) for x in v]
return v
def _sorted_ev(v):
if isinstance(v, list):
return sorted(v)
return v
def go(binpath, req, strip_env=()):
env = dict(os.environ)
for k in strip_env:
env.pop(k, None)
p = subprocess.run(
[binpath, "lifecycle"], input=json.dumps(req).encode(),
capture_output=True, cwd=str(REPO), env=env)
if p.returncode != 0:
raise RuntimeError(f"go CLI failed: {p.stderr.decode()[:400]}")
out = p.stdout.decode().strip()
return json.loads(out) if out else None
def oracle(op, kw):
"""Drive the LIVE oracle (rig seams / real fns); return JSON view."""
if op == "routing-profiles-path":
env_saved = os.environ.pop("RUNE_HOME", None)
try:
derived = str(ro.RuneOperator._routing_profiles_path(
ro.RuneOperator.__new__(ro.RuneOperator)))
finally:
if env_saved is not None:
os.environ["RUNE_HOME"] = env_saved
return derived.replace(str(REPO), "REPO_ROOT")
if op == "construction-store":
env_saved = os.environ.pop("RUNE_CONSTRUCTION_STORE", None)
try:
derived = ro.RuneOperator._construction_store(
ro.RuneOperator.__new__(ro.RuneOperator))
finally:
if env_saved is not None:
os.environ["RUNE_CONSTRUCTION_STORE"] = env_saved
return derived.replace(str(REPO), "REPO_ROOT")
if op == "conv-window":
return ro.olc_module_get_conversation_window(_msgs(), channel=kw["channel"], n=kw["n"])
if op == "list-tasks":
return ro.olc_module_list_tasks(_state(kw["rich"], False))
if op == "clear-pending":
return ro.olc_clear_pending_approval(_state(True, True))
if op == "construction-note":
return ro.olc_construction_note(kw["text"])["notes"]
if op == "construction-override":
ovr, stripped = ro.RuneOperator._construction_activation.__wrapped__ if False else (None, None)
m = __import__("re").match(
r"^(?:/scaffold-mode|scaffold-mode:)\s+"
r"(NOT_CONSTRUCTION|LOCAL_CHANGE|EXISTING_PROJECT_CHANGE|"
r"PROJECT_BUILD|UNKNOWN)\s+(.*)$",
kw["desc"], __import__("re").DOTALL)
if m:
return {"override": m.group(1), "stripped": m.group(2).strip()}
return {"override": None, "stripped": kw["desc"]}
if op == "construction-target":
from rune.construction_mode import _STANDALONE_WORDS
low = kw["desc"].lower()
tokens = set(__import__("re").split(r"[^a-z0-9]+", low))
if "rune" in tokens:
return {"target": "rune", "exists": True}
if [w for w in _STANDALONE_WORDS if w in low]:
return {"target": None, "exists": False}
if "existing" in tokens:
return {"target": "rune", "exists": True}
return {"target": None, "exists": None}
if op == "emit-outcome":
length = kw["len"]
t = _task(kw["status"], length, "m1" if length else None)
ver = {"verification_id": "vr_1", "passed": True} if kw.get("ver") else None
return ro.olc_emit_task_outcome(t, channel="op", verification=ver,
route=kw.get("route") or "")["emits"][0]["payload"]
if op == "emit-failed":
t = _task("failed", kw["len"], None)
return ro.olc_emit_task_failed(t, channel="")["emits"][0]["payload"]
if op == "save-state":
st = _state(True, kw["pending"])
return ro.olc_save(st)["text"]
if op == "load-state":
return ro.olc_load(FIXTURES[kw["fixture"]])
if op == "migrate":
fixture = FIXTURES.get(kw.get("fixture") or "full")
out = ro.olc_init_conv_state(
fixture if kw.get("fixture") else json.dumps({
"tasks": [
{"task_id": "m1", "description": "first task", "status": "completed",
"result": "done well", "created_at": "2026-10-14T19:00:00.000000+00:00",
"completed_at": "2026-10-14T19:01:00.000000+00:00"},
{"task_id": "m2", "description": "second task", "status": "pending"},
],
"current_task": "m2"}),
rerun=bool(kw.get("rerun")))
rep = out["report"] or {}
res = {"report": {k: rep.get(k) for k in ("imported", "skipped", "messages")}}
if not kw.get("rerun"):
res["window"] = out["window_task_channel"]
return res
if op == "terminalize":
t = _task("running", 30, "m1")
err = kw["error"]
out = ro.olc_terminalize(
t, "oref-parity", "parity failure text", channel="op", model="m1",
error=(None if err is None else ("" if err == "empty" else "ValueError: parity")))
return {"envelope": out["envelope"], "canonical": out["canonical"],
"emits": out["emits"]}
raise ValueError(op)
def go_req(op, kw):
"""Mirror each oracle case into the Go CLI request."""
if op == "routing-profiles-path":
return {"op": "routing-profiles-path"}
if op == "construction-store":
return {"op": "construction-store", "repo_root": str(REPO)}
if op == "conv-window":
return {"op": "conv-window", "channel": kw["channel"], "n": kw["n"], "tasks": _msgs()}
if op == "list-tasks":
return {"op": "list-tasks", "state": _state(kw["rich"], False)}
if op == "clear-pending":
return {"op": "clear-pending", "state": _state(True, True)}
if op == "construction-note":
return {"op": "construction-note", "text": kw["text"], "now_utc": FROZEN_UTC}
if op == "construction-override":
return {"op": "construction-override", "desc": kw["desc"]}
if op == "construction-target":
from rune.construction_mode import _STANDALONE_WORDS
low = kw["desc"].lower()
hits = sorted(w for w in _STANDALONE_WORDS if w in low)
return {"op": "construction-target", "desc": kw["desc"], "evidence": hits}
if op == "emit-outcome":
length = kw["len"]
ver = {"verification_id": "vr_1", "passed": True} if kw.get("ver") else None
return {"op": "emit-outcome", "task": _task(kw["status"], length, "m1" if length else None),
"channel": "op", "verification": ver, "route": kw.get("route") or ""}
if op == "emit-failed":
return {"op": "emit-failed", "task": _task("failed", kw["len"], None), "channel": ""}
if op == "save-state":
return {"op": "save-state", "state_raw": _state(True, kw["pending"])}
if op == "load-state":
return {"op": "load-state", "fixture": FIXTURES[kw["fixture"]]}
if op == "migrate":
fixture = FIXTURES.get(kw.get("fixture") or "full")
text = fixture if kw.get("fixture") else json.dumps({
"tasks": [
{"task_id": "m1", "description": "first task", "status": "completed",
"result": "done well", "created_at": "2026-10-14T19:00:00.000000+00:00",
"completed_at": "2026-10-14T19:01:00.000000+00:00"},
{"task_id": "m2", "description": "second task", "status": "pending"},
],
"current_task": "m2"})
present = []
if kw.get("rerun"):
present = ["m1", "m2"]
return {"op": "migrate", "fixture": text, "evidence": present, "now_iso": FROZEN_ISO}
if op == "terminalize":
err = kw["error"]
return {"op": "terminalize", "task": _task("running", 30, "m1"), "channel_seed": "operator",
"outcome_ref": "oref-parity", "result_text": "parity failure text",
"channel": "op", "model": "m1",
"error": (None if err is None else ("" if err == "empty" else "ValueError: parity")),
"now_iso": FROZEN_ISO}
raise ValueError(op)
def post_go(op, kw, got):
"""Adapt Go outputs to the oracle's JSON view for comparison."""
if op == "terminalize":
return {"canonical": got["canonical"], "emits": got["emits"],
"envelope": got["envelope"]}
if op == "routing-profiles-path":
return got.replace(str(REPO), "REPO_ROOT")
if op == "construction-store":
return got.replace(str(REPO), "REPO_ROOT")
if op == "construction-override":
return got
if op == "construction-target":
return got
if op == "construction-note":
return got
if op == "migrate":
if got is None:
return None
out = {"report": got["report"]}
if not kw.get("rerun"):
# rerun window rides the prior pass's hot checkpoint
# (orchestration surface) — the kernel replays fresh rows only
out["window"] = got.get("messages_out") or []
if got.get("error"):
# unknown legacy status: the oracle's migration degrades to
# report None (counters null); the kernel surfaces the error
out = {"report": {"imported": None, "skipped": None, "messages": None}}
if not kw.get("rerun"):
out["window"] = []
return out
return got
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--bin", default=str(REPO / "src/go/operator-deep/operator-deep"))
args = ap.parse_args()
passed = failed = 0
for op, kw, _ in CHECKS:
strip = ()
if op == "routing-profiles-path":
strip = ("RUNE_HOME",)
elif op == "construction-store":
strip = ("RUNE_CONSTRUCTION_STORE",)
try:
want = _norm(oracle(op, kw))
got = _norm(post_go(op, kw, go(args.bin, go_req(op, kw), strip)))
except Exception as exc: # noqa: BLE001 — report and continue
print(f"FAIL {op} {kw}: {exc}")
failed += 1
continue
if json.dumps(want, sort_keys=True) == json.dumps(got, sort_keys=True):
passed += 1
else:
failed += 1
print(f"FAIL {op} {kw}")
print(f" oracle: {json.dumps(want, sort_keys=True)[:400]}")
print(f" go: {json.dumps(got, sort_keys=True)[:400]}")
total = passed + failed
print(f"parity: {passed}/{total}")
return 0 if failed == 0 else 1
if __name__ == "__main__":
sys.exit(main())