Files
rune/scripts/freeze_truth.py
T
Nick 3612786d33
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 19s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 41s
Auto-version and Release / release (push) Canceled after 47s
CI Pipeline / type-check (push) Canceled after 32s
deploy / deploy (push) Canceled after 47s
CI Pipeline / lint (push) Canceled after 46s
CI Pipeline / unit-tests (push) Canceled after 46s
feat(build_mode): close PARTIAL gaps — dichotomy wave
Port the 12 audit-missing fns of src/rune/build_mode.py name-preserving
into the existing Go impl (contract BUILDER-2026-10-13T02:02:02Z-dad27f-
factory-gap-bm): _load_state/_save_state (ledger IO seam, buildsDir
injected), assess_complexity, and the full start_* phase-transition
family — the build-pipeline state machine frozen transition-by-transition
in declaration order via the SHARED freeze_truth harness (extended
backward-compatibly: dotted fn resolution + recv receiver rebuild for
class-based modules + the pinned clock its docstring promised, envelope
files carry frozen_now so --verify needs no env).

43 frozen cases byte-identical against the live oracle; _save_state byte
contract pinned by ledger_load_fixture.json (Python json.dump(asdict,
indent=2), phases in dict-insertion order via ledger first-occurrence);
_load_state error text pinned. Coverage 20/33 → 33/33 (audit-named).
2026-10-13 07:21:00 +01:00

263 lines
9.8 KiB
Python

#!/usr/bin/env python3
"""freeze_truth.py — shared truth-freezing harness for dichotomy ports.
ONE law for every factory agent freezing oracle truth. Replaces per-agent
reinvention of: pinned clock, deep-copy inputs, envelope shape, verbatim
embedding. Same conventions as the port laws in the verification ladder:
* RUNE_*_CORE=python pinned during capture (oracle runs pure Python)
* `now` is always an argument (no-clock law) — callers pass ISO-8601
* inputs are deep-copied before every call (no live-reference aliasing)
* truth is JSON: [{"fn", "args", "kwargs", "expected"}...] — embedded
VERBATIM into the battery (never hand-transcribed)
Usage (as a library, from a generator script):
from freeze_truth import freeze
cases = freeze("module_name", [
("fn_name", (arg1, arg2), {"kw": 1}),
("other_fn", (deep_copied_obj,), {}),
], oracle_module="rune.module_name")
Class-based modules: a case may be a dict with a dotted fn name and a
"recv" receiver spec — the receiver is rebuilt from JSON via shape-matched
module dataclasses, called as an unbound method, and the expected carries
BOTH the result and the post-call receiver attributes:
cases = freeze("rune.build_mode", [
{"fn": "BuildManager.start_plan", "recv": {"cls": "BuildManager", "state": {...}},
"args": [], "kwargs": {}},
], oracle_module="rune.build_mode")
Pinned clock: the harness docstring always promised one — now it exists.
Set RUNE_FROZEN_NOW (ISO-8601) to patch the oracle module's module-global
`datetime` with a frozen clock for BOTH freeze and verify (timestamp-bearing
fns stay byte-identical across processes). Envelope files written via
write_envelope({"frozen_now", "cases"}) carry their own clock, so `--verify`
needs no env at all.
Usage (as a script, to re-verify a frozen file against the live oracle):
python3 scripts/freeze_truth.py --verify src/rust/<crate>/frozen_truth.json \
--module rune.<module>
The generator script (per module) imports this, builds the case list
exercising EVERY public function and branch, then writes the JSON file.
"""
from __future__ import annotations
import argparse
import copy
import dataclasses
import importlib
import json
import os
import sys
from datetime import datetime
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
def _pin_oracle_cores(module_name: str) -> None:
"""Set RUNE_<MODULE>_CORE=python for the oracle's own loader."""
stem = module_name.rsplit(".", 1)[-1]
os.environ[f"RUNE_{stem.upper()}_CORE"] = "python"
def _frozen_clock(iso: str):
"""A datetime subclass pinned to `iso` (naive); .now(tz) honors tz."""
base = datetime.fromisoformat(iso)
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
if tz is not None:
return base.replace(tzinfo=tz)
return base
return FrozenDatetime
def _pin_clock(mod) -> None:
"""Patch the oracle module's module-global `datetime` when RUNE_FROZEN_NOW
is set (modules that read a global datetime — e.g. build_mode._log_phase —
then produce identical timestamps in every process)."""
frozen_now = os.environ.get("RUNE_FROZEN_NOW", "")
if frozen_now and hasattr(mod, "datetime"):
mod.datetime = _frozen_clock(frozen_now)
def _resolve_fn(mod, fn_name: str):
"""Module-level name OR dotted path to a class method ("Cls.method")."""
obj = mod
for part in fn_name.split("."):
obj = getattr(obj, part)
return obj
def _shape_rebuild(mod, value):
"""Rebuild a JSON value into module types: a dict whose key set EXACTLY
matches a module-level dataclass's field names is reconstructed as that
dataclass (the convention that lets a frozen `state` dict become a live
BuildState again in a fresh process). Everything else deep-copies."""
if isinstance(value, dict):
for name in dir(mod):
obj = getattr(mod, name)
if isinstance(obj, type) and dataclasses.is_dataclass(obj):
field_names = {f.name for f in dataclasses.fields(obj)}
if field_names and field_names == set(value.keys()):
return obj(**{k: _shape_rebuild(mod, v) for k, v in value.items()})
return {k: _shape_rebuild(mod, v) for k, v in value.items()}
if isinstance(value, list):
return [_shape_rebuild(mod, v) for v in value]
return copy.deepcopy(value)
def _rebuild_receiver(mod, recv: dict):
"""recv = {"cls": "BuildManager", <attr>: <json value>, ...} → a live
instance built via __new__ (no __init__ — that would touch the real
filesystem) with the named attrs rebuilt through _shape_rebuild."""
cls = getattr(mod, recv["cls"])
inst = cls.__new__(cls)
for key, value in recv.items():
if key == "cls":
continue
setattr(inst, key, _shape_rebuild(mod, value))
return inst
def _out(value):
"""JSON-safe outward snapshot: dataclass instances → asdict."""
if dataclasses.is_dataclass(value) and not isinstance(value, type):
return dataclasses.asdict(value)
return copy.deepcopy(value)
def _snap(value):
"""Deep-snapshot a value into JSON-safe form (live-reference aliasing law)."""
return copy.deepcopy(value)
def _run_case(mod, entry) -> dict:
"""Execute one case (tuple or dict form) against the live module."""
recv = None
if isinstance(entry, dict):
fn_name = entry["fn"]
args = entry.get("args", [])
kwargs = entry.get("kwargs", {})
recv = entry.get("recv")
elif len(entry) == 3:
fn_name, args, kwargs = entry
elif len(entry) == 2:
fn_name, args = entry
kwargs = {}
else:
raise ValueError(f"bad case entry: {entry!r}")
fn = _resolve_fn(mod, fn_name)
a_snap = _snap(list(args))
k_snap = _snap(dict(kwargs))
if recv is None:
result = fn(*copy.deepcopy(a_snap), **copy.deepcopy(k_snap))
expected = _snap(result)
return {"fn": fn_name, "args": a_snap, "kwargs": k_snap, "expected": expected}
inst = _rebuild_receiver(mod, dict(recv))
result = fn(inst, *copy.deepcopy(a_snap), **copy.deepcopy(k_snap))
expected = {"result": _snap(result)}
for key in recv:
if key == "cls":
continue
try:
expected[key] = _out(getattr(inst, key))
except AttributeError:
pass # attr was never set — the case reconstructed a bare receiver
return {
"fn": fn_name, "recv": dict(recv),
"args": a_snap, "kwargs": k_snap, "expected": expected,
}
def freeze(module_name: str, cases, oracle_module: str | None = None) -> list[dict]:
"""Run each case against the live oracle; return truth.
Args and kwargs are deep-copied before EVERY call — the oracle may
mutate its inputs (live-reference aliasing trap). The recorded "args"
are the post-copy snapshots, so the battery replays byte-identical
inputs. Case entries are (fn, args[, kwargs]) tuples or dicts with a
dotted "fn" and optional "recv" receiver spec (class-based modules).
"""
_pin_oracle_cores(oracle_module or module_name)
mod = importlib.import_module(oracle_module or module_name)
_pin_clock(mod)
truth = []
for entry in cases:
truth.append(_run_case(mod, entry))
return truth
def write(truth: list[dict], path: Path) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(truth, indent=2, ensure_ascii=False, sort_keys=False) + "\n")
def write_envelope(frozen_now: str, truth: list[dict], path: Path) -> None:
"""Self-contained envelope: the file carries its own pinned clock, so
`--verify` needs no RUNE_FROZEN_NOW env. List-format files (write)
stay legacy-valid."""
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(
{"frozen_now": frozen_now, "cases": truth},
indent=2, ensure_ascii=False, sort_keys=False) + "\n")
def verify(path: Path, oracle_module: str) -> int:
"""Re-freeze from the live oracle and byte-compare against the frozen file.
Catches self-consistent misreads: the battery and the generator can
agree while the oracle drifted. Returns 0 on byte-exact match. Accepts
a bare case list (legacy) or a {"frozen_now", "cases"} envelope (clock
pinned from the file itself).
"""
raw = json.loads(path.read_text())
if isinstance(raw, dict):
if raw.get("frozen_now"):
os.environ["RUNE_FROZEN_NOW"] = raw["frozen_now"]
frozen = raw["cases"]
else:
frozen = raw
cases = []
for c in frozen:
if isinstance(c, dict):
cases.append({"fn": c["fn"], "args": c.get("args", []),
"kwargs": c.get("kwargs", {}), "recv": c.get("recv")})
else:
cases.append((c["fn"], c["args"], c["kwargs"]))
fresh = freeze(oracle_module, cases, oracle_module)
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'])[:200]}")
print(f" oracle: {json.dumps(g['expected'])[:200]}")
break
return 1
print(f"OK: {len(frozen)} cases byte-identical against live oracle {oracle_module}")
return 0
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--verify", type=Path, help="frozen truth JSON to verify")
ap.add_argument("--module", required=True, help="dotted oracle module, e.g. rune.work_manager")
args = ap.parse_args()
if args.verify:
return verify(args.verify, args.module)
ap.print_help()
return 2
if __name__ == "__main__":
sys.exit(main())