Files
rune/scripts/gen_message_graph_truth.py
T
Nick 1f7a266497 feat(message_graph): port to FULL Rust — dichotomy wave
parity: every public function of MessageGraph ported branch-for-branch —
_load (schema-v1 gate, corrupt/wrong-schema silent-empty, duplicate ids
last-wins with BOTH order entries kept, verbatim extra fields, non-dict
doc AttributeError, non-list nodes TypeError), save (arrival-order doc;
mkdir + json.dumps indent=2 FILE IO stays in the facade), add (the falsy
law: an explicit None parent still picks up the head but carries no edge,
a falsy "" parent stores "" and also carries no edge, ghost parents
allowed, kind checked before edge, Python-repr error strings), tool_call
(json.dumps content rendering stays in the facade — serialization
isomorphism), tool_result (existence check only — any node can parent a
result), revise (the root-revision quirk: old parent None substitutes
the current head, which at add-time is the old node itself), branch,
lineage (superseded skip, ghost superseded_by fallthrough, seen-set
cycle guard, missing-parent break), history (supersedes walk back to
root then forward via superseded_by — the FULL trail from any point on
it), migrate_flat (role/content defaults, existing-file seed), cue
(first supersedes/branches_from edge in iteration order wins, falsy
superseded_by is no cue).

Determinism seams: the uuid4 hex id and the _now() timestamp are kernel
ARGUMENTS (id/ts) — the frozen truth replays them verbatim. Graph wire:
{nodes: {id: node} insertion-ordered (serde_json preserve_order),
order: [ids]} — nodes ride as opaque Values so loaded extra fields
survive byte-verbatim. Errors ride typed (ValueError/KeyError/
AttributeError/TypeError) and the loader raises the matching Python
exception. The facade keeps the oracle's guard-clause ORDER (kind/edge/
existence checks BEFORE the uuid draw) so the id sequence is identical
under both cores. mg_loader mirrors broker_loader (doc-envelope,
cs_free_string, MG_RUST_LIB override); RUNE_MG_CORE=python pins the
fallback.

The freeze caught a real divergence first: Python 3.14's string-index
TypeError now reads "string indices must be integers, not 'str'" — the
kernel mirrors the runtime's wording, pinned by truth. Battery: 12
tests replay 68 frozen-oracle cases VERBATIM (include_str!); live
parity 68/68 byte-identical (truth regenerated through the kernel path
and diffed against the freeze); clippy 0; Python suite green under both
pins (9+9).
2026-10-12 02:26:30 +01:00

655 lines
29 KiB
Python

#!/usr/bin/env python3
"""Freeze message_graph oracle truth for the Rust port battery (dichotomy
wave #2479 tranche).
Exercises EVERY public function of src/rune/message_graph.py across every
branch: load (missing file, corrupt JSON, wrong/missing schema, valid doc,
duplicate ids, verbatim extra fields, non-dict doc -> AttributeError,
non-list nodes -> TypeError), save (doc shape + file bytes incl. mkdir),
add (default-parent chain, explicit parent with/without edge, explicit
None -> head, falsy parent "", edge to a ghost parent, unicode, every
kind, bad kind/edge -> ValueError), tool_call (json.dumps content shape,
nested args, parent / default head), tool_result (link, missing call ->
KeyError, non-tool-call parent allowed), revise (root revision picks up
the default-head parent quirk, child revision, chained revisions, missing
-> KeyError), branch (ok, missing -> KeyError), lineage (empty graph,
default head, explicit id, superseded skip, missing-parent break, ghost
superseded_by target, unknown id, parent cycle), history (single, full
chain, from the middle, missing id), migrate_flat (chain, empty
transcript, missing role/content keys, existing-file seed) and cue (no
cue, replaces, branched from, superseded by, falsy superseded_by, edge
iteration order).
Nondeterminism seams: the facade generates the uuid4 hex id and the
_now() timestamp and passes them to the kernel as plain arguments — here
both are pinned (sequential fake uuid4, frozen clock) so the frozen truth
replays VERBATIM on the Rust side and the kernel-side parity pass is a
byte-diff of this file.
Default run pins RUNE_MG_CORE=python (the ORACLE). The kernel-side parity
pass re-runs with RUNE_MG_CORE=auto and MG_TRUTH_OUT set; identical bytes
== 100% live parity.
Output: /tmp/mg_truth.json — replayed VERBATIM by
src/rust/message_graph/src/branch_battery.rs.
"""
from __future__ import annotations
import copy
import json
import os
import sys
import tempfile
import uuid
from pathlib import Path
os.environ.setdefault("RUNE_MG_CORE", "python") # freeze the ORACLE, never the kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import message_graph as mg # noqa: E402
# ── determinism seams (see module docstring) ─────────────────────────────
class _FakeUUID:
def __init__(self, hex_: str) -> None:
self.hex = hex_
_seq = [0]
def _fake_uuid4():
_seq[0] += 1
return _FakeUUID(f"deadbeef{_seq[0]:04d}")
uuid.uuid4 = _fake_uuid4
CLOCK = "2031-01-01T00:00:00.000000+00:00"
mg._now = lambda: CLOCK # noqa: E731 (the oracle's clock, pinned)
TMP = Path(tempfile.mkdtemp(prefix="mg_truth_"))
GROUPS = ("load", "save", "add", "tool_call", "tool_result", "revise",
"branch", "lineage", "history", "migrate_flat", "cue")
groups: dict[str, list] = {g: [] for g in GROUPS}
def case(group: str, name: str, input_, expected) -> None:
groups[group].append({"name": name, "input": input_, "expected": expected})
def wire(g: "mg.MessageGraph") -> dict:
"""Graph state over the wire: nodes as an insertion-ordered object,
order as the arrival list."""
return {"nodes": copy.deepcopy(g.nodes), "order": list(g.order)}
def exc_of(fn, *a, **kw) -> dict:
try:
fn(*a, **kw)
except Exception as e: # noqa: BLE001 — the oracle's exception IS the truth
return {"exception": {"type": type(e).__name__, "message": str(e)}}
raise AssertionError("expected the oracle to raise")
def fresh(name: str, doc_text: str | None = None) -> "mg.MessageGraph":
p = TMP / f"{name}.json"
if doc_text is not None:
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(doc_text)
return mg.MessageGraph(path=p)
# ══ load ═════════════════════════════════════════════════════════════════
# missing file -> empty graph
g = fresh("load_missing")
case("load", "load-missing-file", {"text": None}, {"graph": wire(g)})
# empty object: no schema -> ignored
g = fresh("load_empty_obj", "{}")
case("load", "load-no-schema", {"text": "{}"}, {"graph": wire(g)})
# wrong schema
g = fresh("load_wrong_schema", json.dumps({"schema": 2, "nodes": []}))
case("load", "load-wrong-schema", {"text": json.dumps({"schema": 2, "nodes": []})},
{"graph": wire(g)})
# corrupt JSON
g = fresh("load_corrupt", "not json {")
case("load", "load-corrupt-json", {"text": "not json {"}, {"graph": wire(g)})
# valid doc: nodes ingested verbatim in file order
doc = {"schema": 1, "nodes": [
{"id": "aaa000000001", "kind": "message", "role": "user", "content": "q",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
{"id": "bbb000000002", "kind": "reply", "role": "assistant", "content": "a",
"ts": CLOCK, "parent": "aaa000000001", "edges": [], "superseded_by": None},
]}
g = fresh("load_valid", json.dumps(doc))
case("load", "load-valid", {"text": json.dumps(doc)}, {"graph": wire(g)})
# duplicate ids: last wins in nodes, BOTH stay in order
doc = {"schema": 1, "nodes": [
{"id": "ccc000000003", "kind": "message", "role": "user", "content": "v1",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
{"id": "ccc000000003", "kind": "message", "role": "user", "content": "v2",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
]}
g = fresh("load_dup", json.dumps(doc))
case("load", "load-duplicate-ids-last-wins-order-keeps-both",
{"text": json.dumps(doc)}, {"graph": wire(g)})
# extra fields ride verbatim
doc = {"schema": 1, "nodes": [
{"id": "ddd000000004", "kind": "message", "role": "user", "content": "x",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None,
"extra": {"deep": [1, 2.5, None, True]}, "π": "unicode key"},
]}
g = fresh("load_extra", json.dumps(doc))
case("load", "load-extra-fields-verbatim", {"text": json.dumps(doc)},
{"graph": wire(g)})
# non-dict doc -> the oracle's doc.get raises AttributeError
def _err_file(name: str, text: str) -> Path:
p = TMP / f"{name}.json"
p.write_text(text)
return p
_p = _err_file("load_list", "[1, 2]")
case("load", "load-list-doc-attributeerror", {"text": "[1, 2]"},
exc_of(mg.MessageGraph, path=_p))
_p = _err_file("load_str", '"hello"')
case("load", "load-string-doc-attributeerror", {"text": '"hello"'},
exc_of(mg.MessageGraph, path=_p))
_p = _err_file("load_int", "5")
case("load", "load-int-doc-attributeerror", {"text": "5"},
exc_of(mg.MessageGraph, path=_p))
# nodes present but not a list -> the for-loop raises TypeError
_p = _err_file("load_dictnodes", json.dumps({"schema": 1, "nodes": {"aaa": {}}}))
case("load", "load-dict-nodes-typeerror",
{"text": json.dumps({"schema": 1, "nodes": {"aaa": {}}})},
exc_of(mg.MessageGraph, path=_p))
# ══ add ══════════════════════════════════════════════════════════════════
# default parent on an empty graph: parent None, edges []
g = fresh("add_empty")
node = g.add("user", "hello")
case("add", "add-default-parent-empty-graph",
{"graph": {"nodes": {}, "order": []}, "id": node["id"],
"ts": node["ts"], "role": "user", "content": "hello", "kind": "message",
"parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# default parent chains to the head; edges stay [] (parent arg was None)
g = fresh("add_chain")
g.add("user", "q1")
before = wire(g)
node = g.add("assistant", "a1")
case("add", "add-default-parent-head-no-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a1", "kind": "message", "parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# explicit parent + explicit edge
g = fresh("add_explicit")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", kind="reply", parent=a["id"], edge="reply_to")
case("add", "add-explicit-parent-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "reply", "parent": a["id"], "edge": "reply_to"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# explicit parent, default edge "parent"
g = fresh("add_explicit_default_edge")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent=a["id"])
case("add", "add-explicit-parent-default-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": a["id"], "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# explicit None parent with a non-empty order: parent becomes the HEAD but
# edges stay [] (the arg was None — falsy)
g = fresh("add_none_parent")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent=None)
case("add", "add-explicit-none-parent-becomes-head",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# falsy parent "": stored as parent, but NO edge (falsy)
g = fresh("add_falsy_parent")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent="")
case("add", "add-falsy-empty-parent-no-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": "", "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# edge to a ghost parent is allowed (no referential check in add)
g = fresh("add_ghost_parent")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent="ghost0000001", edge="reply_to")
case("add", "add-ghost-parent-allowed",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": "ghost0000001",
"edge": "reply_to"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# unicode content
g = fresh("add_unicode")
node = g.add("user", "こんにちは ☕ héllo")
case("add", "add-unicode-content",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"role": "user", "content": "こんにちは ☕ héllo", "kind": "message",
"parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# every kind
for k in ("message", "reply", "tool_call", "tool_result", "revision", "branch_head"):
g = fresh(f"add_kind_{k}")
node = g.add("user", f"content-{k}", kind=k)
case("add", f"add-kind-{k}",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"role": "user", "content": f"content-{k}", "kind": k,
"parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# bad kind / bad edge (ValueError with the oracle's repr formatting)
g = fresh("add_bad_kind")
case("add", "add-unknown-kind-valueerror",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"role": "user", "content": "x", "kind": "nonsense", "parent": None,
"edge": "parent"},
exc_of(g.add, "user", "x", kind="nonsense"))
case("add", "add-unknown-edge-valueerror",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"role": "user", "content": "x", "kind": "message", "parent": None,
"edge": "nope"},
exc_of(g.add, "user", "x", edge="nope"))
# kind checked BEFORE edge
case("add", "add-both-bad-kind-wins",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"role": "user", "content": "x", "kind": "nonsense", "parent": None,
"edge": "nope"},
exc_of(g.add, "user", "x", kind="nonsense", edge="nope"))
# ══ tool_call ════════════════════════════════════════════════════════════
g = fresh("tc_basic")
node = g.tool_call("read", {"path": "a.py"})
case("tool_call", "tool-call-basic",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"content": json.dumps({"call": "read", "args": {"path": "a.py"}}),
"parent": None},
{"node": copy.deepcopy(node), "graph": wire(g)})
g = fresh("tc_parent")
a = g.add("assistant", "running")
before = wire(g)
node = g.tool_call("write", {"path": "b.py", "bytes": 3}, parent=a["id"])
case("tool_call", "tool-call-explicit-parent",
{"graph": before, "id": node["id"], "ts": node["ts"],
"content": json.dumps({"call": "write", "args": {"path": "b.py", "bytes": 3}}),
"parent": a["id"]},
{"node": copy.deepcopy(node), "graph": wire(g)})
g = fresh("tc_nested")
node = g.tool_call("search", {"q": "π", "opts": {"deep": [1, 2, {"k": None}],
"flag": True}, "n": 0.5})
case("tool_call", "tool-call-nested-args",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"content": json.dumps({"call": "search",
"args": {"q": "π", "opts": {"deep": [1, 2, {"k": None}],
"flag": True}, "n": 0.5}}),
"parent": None},
{"node": copy.deepcopy(node), "graph": wire(g)})
# ══ tool_result ══════════════════════════════════════════════════════════
g = fresh("tr_ok")
c = g.tool_call("read", {"path": "a.py"})
before = wire(g)
node = g.tool_result(c["id"], "file contents")
case("tool_result", "tool-result-links-call",
{"graph": before, "id": node["id"], "ts": node["ts"], "call_id": c["id"],
"result": "file contents"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# no kind check on the parent: any node can be a "call"
g = fresh("tr_any_node")
a = g.add("user", "not a call")
before = wire(g)
node = g.tool_result(a["id"], "result anyway")
case("tool_result", "tool-result-non-call-parent-allowed",
{"graph": before, "id": node["id"], "ts": node["ts"], "call_id": a["id"],
"result": "result anyway"},
{"node": copy.deepcopy(node), "graph": wire(g)})
g = fresh("tr_missing")
case("tool_result", "tool-result-missing-keyerror",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"call_id": "ghost0000001", "result": "x"},
exc_of(g.tool_result, "ghost0000001", "x"))
# ══ revise ═══════════════════════════════════════════════════════════════
# root revision: old.get("parent") is None -> add substitutes the HEAD,
# which at that moment is the old node itself (quirk, frozen verbatim)
g = fresh("rev_root")
a = g.add("assistant", "v1")
before = wire(g)
rev = g.revise(a["id"], "v2")
case("revise", "revise-root-head-parent-quirk",
{"graph": before, "node_id": a["id"], "new_content": "v2",
"id": rev["id"], "ts": rev["ts"]},
{"node": copy.deepcopy(rev), "graph": wire(g)})
# child revision: keeps the old parent, edges revises + supersedes
g = fresh("rev_child")
a = g.add("user", "q")
b = g.add("assistant", "v1", parent=a["id"])
before = wire(g)
rev = g.revise(b["id"], "v2")
case("revise", "revise-child-keeps-parent",
{"graph": before, "node_id": b["id"], "new_content": "v2",
"id": rev["id"], "ts": rev["ts"]},
{"node": copy.deepcopy(rev), "graph": wire(g)})
# chained revision: revise the revision
g = fresh("rev_chain")
a = g.add("assistant", "v1")
r1 = g.revise(a["id"], "v2")
before = wire(g)
r2 = g.revise(r1["id"], "v3")
case("revise", "revise-of-revision",
{"graph": before, "node_id": r1["id"], "new_content": "v3",
"id": r2["id"], "ts": r2["ts"]},
{"node": copy.deepcopy(r2), "graph": wire(g)})
g = fresh("rev_missing")
case("revise", "revise-missing-keyerror",
{"graph": {"nodes": {}, "order": []}, "node_id": "ghost0000001",
"new_content": "x", "id": "deadbeef_err", "ts": CLOCK},
exc_of(g.revise, "ghost0000001", "x"))
# ══ branch ═══════════════════════════════════════════════════════════════
g = fresh("branch_ok")
a = g.add("user", "root")
g.add("assistant", "answer", parent=a["id"])
before = wire(g)
head = g.branch(a["id"])
case("branch", "branch-from-message",
{"graph": before, "node_id": a["id"], "id": head["id"], "ts": head["ts"]},
{"node": copy.deepcopy(head), "graph": wire(g)})
g = fresh("branch_missing")
case("branch", "branch-missing-keyerror",
{"graph": {"nodes": {}, "order": []}, "node_id": "ghost0000001",
"id": "deadbeef_err", "ts": CLOCK},
exc_of(g.branch, "ghost0000001"))
# ══ lineage ══════════════════════════════════════════════════════════════
g = fresh("lin_empty")
case("lineage", "lineage-empty-graph", {"graph": wire(g), "node_id": None},
{"result": []})
# default head: full chain oldest-first
g = fresh("lin_chain")
a = g.add("user", "q")
b = g.add("assistant", "a1", parent=a["id"])
c = g.add("assistant", "a2", parent=b["id"])
case("lineage", "lineage-default-head-full-chain",
{"graph": wire(g), "node_id": None},
{"result": [copy.deepcopy(n) for n in g.lineage()]})
assert [n["id"] for n in g.lineage()] == [a["id"], b["id"], c["id"]]
# explicit id: subchain
case("lineage", "lineage-explicit-id-subchain",
{"graph": wire(g), "node_id": b["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(b["id"])]})
assert [n["id"] for n in g.lineage(b["id"])] == [a["id"], b["id"]]
# superseded nodes yield to their revision; unrelated branches excluded
g = fresh("lin_superseded")
a = g.add("user", "q")
b = g.add("assistant", "v1", parent=a["id"])
other = g.add("user", "unrelated")
rev = g.revise(b["id"], "v2")
case("lineage", "lineage-skips-superseded-default-head",
{"graph": wire(g), "node_id": None},
{"result": [copy.deepcopy(n) for n in g.lineage()]})
assert [n["id"] for n in g.lineage()] == [a["id"], rev["id"]]
case("lineage", "lineage-from-root-excludes-other-branch",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(a["id"])]})
assert [n["id"] for n in g.lineage(a["id"])] == [a["id"]]
# parent chain into a ghost: walk breaks quietly
g = fresh("lin_ghost_parent")
a = g.add("user", "q")
g.nodes[a["id"]]["parent"] = "ghost0000001"
case("lineage", "lineage-ghost-parent-breaks",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(a["id"])]})
assert [n["id"] for n in g.lineage(a["id"])] == [a["id"]]
# superseded_by pointing OUTSIDE the graph: node included (guard fails)
g = fresh("lin_ghost_superseded")
a = g.add("user", "q")
g.nodes[a["id"]]["superseded_by"] = "ghost0000001"
case("lineage", "lineage-ghost-superseded-by-included",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(a["id"])]})
assert [n["id"] for n in g.lineage(a["id"])] == [a["id"]]
# unknown id: empty
g = fresh("lin_unknown")
case("lineage", "lineage-unknown-id-empty",
{"graph": wire(g), "node_id": "ghost0000001"}, {"result": []})
# parent cycle: the seen-set guard stops the walk
doc = {"schema": 1, "nodes": [
{"id": "aaa000000001", "kind": "message", "role": "user", "content": "a",
"ts": CLOCK, "parent": "bbb000000002", "edges": [], "superseded_by": None},
{"id": "bbb000000002", "kind": "message", "role": "user", "content": "b",
"ts": CLOCK, "parent": "aaa000000001", "edges": [], "superseded_by": None},
]}
g = fresh("lin_cycle", json.dumps(doc))
case("lineage", "lineage-parent-cycle-guarded",
{"graph": wire(g), "node_id": "aaa000000001"},
{"result": [copy.deepcopy(n) for n in g.lineage("aaa000000001")]})
assert [n["id"] for n in g.lineage("aaa000000001")] == ["bbb000000002",
"aaa000000001"]
# ══ history ══════════════════════════════════════════════════════════════
# single node: no supersedes edge -> just itself
g = fresh("hist_single")
a = g.add("assistant", "v1")
case("history", "history-single-node",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.history(a["id"])]})
assert [n["content"] for n in g.history(a["id"])] == ["v1"]
# full chain oldest-first, asked from the tip AND from the middle
g = fresh("hist_chain")
a = g.add("assistant", "v1")
r1 = g.revise(a["id"], "v2")
r2 = g.revise(r1["id"], "v3")
case("history", "history-chain-from-tip",
{"graph": wire(g), "node_id": r2["id"]},
{"result": [copy.deepcopy(n) for n in g.history(r2["id"])]})
assert [n["content"] for n in g.history(r2["id"])] == ["v1", "v2", "v3"]
case("history", "history-chain-from-middle-full-trail",
{"graph": wire(g), "node_id": r1["id"]},
{"result": [copy.deepcopy(n) for n in g.history(r1["id"])]})
assert [n["content"] for n in g.history(r1["id"])] == ["v1", "v2", "v3"]
# unknown id: empty trail
g = fresh("hist_missing")
case("history", "history-unknown-id-empty",
{"graph": wire(g), "node_id": "ghost0000001"}, {"result": []})
# revision whose superseded_by target is a ghost: trail stops there
g = fresh("hist_ghost_target")
a = g.add("assistant", "v1")
r1 = g.revise(a["id"], "v2")
g.nodes[r1["id"]]["superseded_by"] = "ghost0000001"
case("history", "history-ghost-superseded-by-stops",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.history(a["id"])]})
assert [n["content"] for n in g.history(a["id"])] == ["v1", "v2"]
# ══ migrate_flat ═════════════════════════════════════════════════════════
def mig_ids(g, n):
"""The ids the oracle's migrate consumed (the last n arrivals)."""
return list(g.order[len(g.order) - n:]) if n else []
flat = [{"role": "user", "content": "q1"}, {"role": "assistant", "content": "a1"}]
g = mg.MessageGraph.migrate_flat(copy.deepcopy(flat), path=TMP / "mig_basic.json")
case("migrate_flat", "migrate-basic-chain",
{"text": None, "transcript": copy.deepcopy(flat),
"ids": mig_ids(g, 2), "tss": [CLOCK, CLOCK]},
{"graph": wire(g)})
assert [g.nodes[i]["parent"] for i in g.order] == [None, g.order[0]]
# empty transcript
g = mg.MessageGraph.migrate_flat([], path=TMP / "mig_empty.json")
case("migrate_flat", "migrate-empty-transcript",
{"text": None, "transcript": [], "ids": [], "tss": []}, {"graph": wire(g)})
# missing role/content keys -> system / "" defaults
flat = [{"role": "user"}, {}, {"content": "no role"}]
g = mg.MessageGraph.migrate_flat(copy.deepcopy(flat), path=TMP / "mig_defaults.json")
case("migrate_flat", "migrate-missing-keys-default-system-empty",
{"text": None, "transcript": copy.deepcopy(flat), "ids": mig_ids(g, 3),
"tss": [CLOCK] * 3},
{"graph": wire(g)})
assert [g.nodes[i]["role"] for i in g.order] == ["user", "system", "system"]
assert [g.nodes[i]["content"] for i in g.order] == ["", "", "no role"]
# existing file seeds the graph first, migration appends after
seed_doc = {"schema": 1, "nodes": [
{"id": "eee000000005", "kind": "message", "role": "user", "content": "old",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
]}
_seed = _err_file("mig_seed", json.dumps(seed_doc))
g = mg.MessageGraph.migrate_flat([{"role": "user", "content": "new"}],
path=_seed)
case("migrate_flat", "migrate-existing-file-seed",
{"text": json.dumps(seed_doc), "transcript": [{"role": "user", "content": "new"}],
"ids": mig_ids(g, 1), "tss": [CLOCK]},
{"graph": wire(g)})
# ══ cue ══════════════════════════════════════════════════════════════════
g = fresh("cue_plain")
a = g.add("assistant", "v1")
plain = copy.deepcopy(g.nodes[a["id"]])
case("cue", "cue-plain-node", {"node": plain}, {"result": ""})
assert mg.MessageGraph.cue(plain) == ""
rev = g.revise(a["id"], "v2")
case("cue", "cue-revision-replaces",
{"node": copy.deepcopy(g.nodes[rev["id"]])},
{"result": f"replaces {a['id'][:8]}"})
assert mg.MessageGraph.cue(g.nodes[rev["id"]]) == f"replaces {a['id'][:8]}"
case("cue", "cue-superseded-by",
{"node": copy.deepcopy(g.nodes[a["id"]])},
{"result": f"superseded by {rev['id'][:8]}"})
assert mg.MessageGraph.cue(g.nodes[a["id"]]) == f"superseded by {rev['id'][:8]}"
g = fresh("cue_branch")
a = g.add("user", "root")
head = g.branch(a["id"])
case("cue", "cue-branch-head",
{"node": copy.deepcopy(g.nodes[head["id"]])},
{"result": f"branched from {a['id'][:8]}"})
assert mg.MessageGraph.cue(g.nodes[head["id"]]) == f"branched from {a['id'][:8]}"
# falsy superseded_by "" is NOT a cue
node = copy.deepcopy(plain)
node["superseded_by"] = ""
case("cue", "cue-falsy-superseded-by-empty", {"node": node}, {"result": ""})
assert mg.MessageGraph.cue(node) == ""
# first supersedes/branches_from edge in iteration order wins
node = copy.deepcopy(plain)
node["edges"] = [{"type": "parent", "to": "aaa000000001"},
{"type": "supersedes", "to": "bbb000000002"}]
case("cue", "cue-first-supersedes-edge-wins", {"node": node},
{"result": "replaces bbb00000"})
assert mg.MessageGraph.cue(node) == "replaces bbb00000"
node2 = copy.deepcopy(plain)
node2["edges"] = [{"type": "branches_from", "to": "ccc000000003"},
{"type": "supersedes", "to": "bbb000000002"}]
case("cue", "cue-branches-from-beats-later-supersedes", {"node": node2},
{"result": "branched from ccc00000"})
assert mg.MessageGraph.cue(node2) == "branched from ccc00000"
# ══ save ═════════════════════════════════════════════════════════════════
# basic save: doc shape + exact file bytes (json.dumps indent=2)
g = fresh("save_basic")
a = g.add("user", "persist me")
g.save()
doc = {"schema": mg.SCHEMA, "nodes": [g.nodes[i] for i in g.order]}
case("save", "save-basic-doc-and-bytes",
{"graph": {"nodes": {a["id"]: copy.deepcopy(g.nodes[a["id"]])},
"order": [a["id"]]}},
{"doc": copy.deepcopy(doc), "file": (TMP / "save_basic.json").read_text()})
assert (TMP / "save_basic.json").read_text() == json.dumps(doc, indent=2)
# save creates missing parent dirs
deep = TMP / "deep" / "nested" / "dir" / "g.json"
g2 = mg.MessageGraph(path=deep)
g2.add("user", "deep")
g2.save()
doc2 = {"schema": mg.SCHEMA, "nodes": [g2.nodes[i] for i in g2.order]}
case("save", "save-creates-parent-dirs",
{"graph": wire(g2)}, {"doc": copy.deepcopy(doc2), "file": deep.read_text()})
assert deep.exists()
# round trip: save -> fresh load reproduces nodes AND order
g3 = mg.MessageGraph(path=TMP / "save_basic.json")
case("load", "load-save-round-trip",
{"text": (TMP / "save_basic.json").read_text()}, {"graph": wire(g3)})
assert g3.order == g.order
assert g3.nodes[a["id"]]["content"] == "persist me"
# ══ oracle health (the repo's own battery must pass on the oracle) ══════
import unittest # noqa: E402
suite = unittest.defaultTestLoader.discover(
str(Path(__file__).resolve().parents[1] / "tests"),
pattern="test_message_graph.py")
runner = unittest.TextTestRunner(verbosity=0)
if not runner.run(suite).wasSuccessful():
raise AssertionError("oracle regression: test_message_graph.py failed")
# ══ write ════════════════════════════════════════════════════════════════
truth = {
"frozen": "message_graph load/save/add/tool_call/tool_result/revise/"
"branch/lineage/history/migrate_flat/cue",
"oracle": "src/rune/message_graph.py",
"note": "Graph wire: {nodes: {id: node} insertion-ordered, order: [ids]}. "
"Nondeterminism seams (uuid4 hex id, _now timestamp) are facade "
"arguments — frozen CLOCK and sequential deadbeefNNNN ids replay "
"verbatim. save() builds the doc in the kernel; the file write "
"(mkdir + json.dumps indent=2) and tool_call's json.dumps content "
"stay in the facade (serialization isomorphism).",
}
for gname in GROUPS:
truth[gname] = groups[gname]
out = Path(os.environ.get("MG_TRUTH_OUT", "/tmp/mg_truth.json"))
out.write_text(json.dumps(truth, indent=1, ensure_ascii=False) + "\n")
counts = ", ".join(f"{len(groups[g])} {g}" for g in GROUPS)
print(f"froze {sum(len(v) for v in groups.values())} cases -> {out}: {counts}")