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
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).
263 lines
9.8 KiB
Python
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())
|