Compare commits

...
2 Commits
Author SHA1 Message Date
conductor ffa8608f95 chore(release): auto-bump version to 0.263.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 6s
CI Pipeline / canary (push) Canceled after 5s
CI Pipeline / rust-surface (push) Canceled after 5s
CI Pipeline / best-practices (push) Canceled after 5s
CI Pipeline / unit-tests (push) Canceled after 5s
CI Pipeline / go-surface (push) Canceled after 5s
CI Pipeline / lint (push) Canceled after 5s
Promotion record: (unknown)
2026-10-13 07:30:06 +01:00
Nick 3612786d33 feat(build_mode): close PARTIAL gaps — dichotomy wave
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).
2026-10-13 07:21:00 +01:00
12 changed files with 8726 additions and 30 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.262.0**
**Current release: v0.263.0**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.262.0"
version = "0.263.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+161 -24
View File
@@ -17,6 +17,23 @@ Usage (as a library, from a generator script):
("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>
@@ -29,10 +46,12 @@ 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]
@@ -44,42 +63,136 @@ def _pin_oracle_cores(module_name: str) -> None:
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 (fn_name, args, kwargs) against the live oracle; return truth.
"""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.
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:
if 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 = getattr(mod, fn_name, None)
if fn is None:
raise AttributeError(f"{oracle_module or module_name}.{fn_name} missing — case list must name real functions")
a_snap = _snap(list(args))
k_snap = _snap(dict(kwargs))
result = fn(*copy.deepcopy(a_snap), **copy.deepcopy(k_snap))
truth.append({
"fn": fn_name,
"args": a_snap,
"kwargs": k_snap,
"expected": _snap(result),
})
truth.append(_run_case(mod, entry))
return truth
@@ -88,14 +201,38 @@ def write(truth: list[dict], path: Path) -> None:
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.
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).
"""
frozen = json.loads(path.read_text())
cases = [(c["fn"], c["args"], c["kwargs"]) for c in frozen]
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}")
+245
View File
@@ -0,0 +1,245 @@
#!/usr/bin/env python3
"""gen_build_mode_gap_truth.py — build-mode gap-closing truth battery
(contract BUILDER-2026-10-13T02:02:02Z-dad27f-factory-gap-bm).
Closes the PARTIAL audit gaps: _load_state, _save_state, assess_complexity
and the full start_* phase-transition family (start_research, start_feasible,
start_consult, start_consent, start_plan, start_build_phase, start_verify,
start_integrate, start_record) — the build-pipeline state machine, frozen
transition-by-transition in declaration order (phase names and ordering are
load-bearing for the pipeline).
Uses the SHARED harness (scripts/freeze_truth.py): dotted fn names + recv
receiver rebuild + RUNE_FROZEN_NOW pinned clock. The envelope carries its
own frozen_now, so `--verify` needs no env:
PYTHONPATH=src python3 scripts/freeze_truth.py \
--verify src/go/build-mode/truth_buildmode_gap.json \
--module rune.build_mode
NOTE: the oracle's _log_phase/_save_state write to BUILDS_DIR (module
global from expanduser). Run generator AND verify with a sandboxed HOME to
contain the incidental state files (truth never reads them back):
HOME=$(mktemp -d) PYTHONPATH=src python3 scripts/freeze_truth.py ...
_load_state/_save_state are IO seams: _save_state's byte contract is frozen
as ledger_load_fixture.json (exact bytes of json.dump(asdict(state),
indent=2)); the generator also proves the oracle roundtrip (write fixture →
_load_state → equal state). _load_state's FileNotFoundError path is pinned
by the Go test (error text "Build <id> not found").
Usage: python3 scripts/gen_build_mode_gap_truth.py (from repo root)
"""
import dataclasses
import json
import os
import sys
import tempfile
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "scripts"))
sys.path.insert(0, str(REPO / "src"))
from freeze_truth import _frozen_clock, freeze, write_envelope # noqa: E402
from rune import build_mode as bm # noqa: E402
FROZEN_NOW = "2026-10-13T02:20:00"
STAMP = FROZEN_NOW + "+00:00"
BUILD_ID = "20261013-022000"
os.environ["RUNE_FROZEN_NOW"] = FROZEN_NOW # pinned clock for the harness
os.environ["RUNE_BUILD_MODE_CORE"] = "python" # poisoned-truth law
bm.datetime = _frozen_clock(FROZEN_NOW) # pin the SEED walk too (recv states
# must not carry live-clock timestamps — the truth must be self-consistent)
# BYTE-PARITY LAW for the IO fixture: Go maps marshal dict keys SORTED, so
# every generator-supplied dict below uses sorted keys (Python insertion
# order == sorted order) — then SaveState can reproduce json.dump bytes
# exactly. The oracle-internal feasible payload (feasible, risk, details)
# is the one unavoidable exception — fixture therefore comes from the
# correction-cycle state (single-key payloads only); dict-heavy states are
# parity-checked at the parsed level (truth replay + roundtrip test).
def fresh_mgr() -> bm.BuildManager:
m = bm.BuildManager.__new__(bm.BuildManager)
m.state = bm.BuildState(
build_id=BUILD_ID, description="gap battery build", created_at=STAMP)
return m
def seed(mgr) -> dict:
return dataclasses.asdict(mgr.state)
def main():
cases = []
mgr = fresh_mgr()
def call(fn, *args, **kwargs):
"""Freeze one transition: recv = PRE-state, then apply to the live
walk manager (the harness re-runs it on a rebuilt receiver)."""
cases.append({
"fn": f"BuildManager.{fn.__name__}",
"recv": {"cls": "BuildManager", "state": seed(mgr)},
"args": list(args), "kwargs": kwargs,
})
fn(mgr, *args, **kwargs)
# ── assess_complexity ────────────────────────────────────────────
# bare receiver (the fn never touches state) + a seeded receiver.
cases.append({"fn": "BuildManager.assess_complexity",
"recv": {"cls": "BuildManager"}, "args": [], "kwargs": {}})
cases.append({"fn": "BuildManager.assess_complexity",
"recv": {"cls": "BuildManager", "state": seed(mgr)},
"args": [], "kwargs": {}})
# ── the canonical 11-phase happy path, IN DECLARATION ORDER ──────
call(bm.BuildManager.complete_assess,
bm.BuildManager.assess_complexity(mgr))
call(bm.BuildManager.start_research)
call(bm.BuildManager.complete_research,
{"findings": "gap battery pattern", "source": "oracle"})
call(bm.BuildManager.start_feasible)
call(bm.BuildManager.complete_feasible, True, "low",
{"loc_estimate": 120, "worktree_isolated": True})
call(bm.BuildManager.start_consult)
call(bm.BuildManager.complete_consult, ["gap-closing pattern"])
call(bm.BuildManager.start_consent)
call(bm.BuildManager.complete_consent, True)
call(bm.BuildManager.start_plan)
call(bm.BuildManager.complete_plan,
[{"batch": 1, "files": ["a.py"]}, {"batch": 2, "files": ["b.py"]}])
call(bm.BuildManager.start_build_phase)
call(bm.BuildManager.complete_build_phase,
[{"batch": 1, "ok": True}, {"batch": 2, "ok": True}])
call(bm.BuildManager.start_verify)
call(bm.BuildManager.complete_verify, True, [])
call(bm.BuildManager.start_integrate)
call(bm.BuildManager.complete_integrate, True, [])
call(bm.BuildManager.start_record)
call(bm.BuildManager.complete_record)
# ── _save_state branches ─────────────────────────────────────────
# state None → early return (no file, no dirs)
cases.append({"fn": "BuildManager._save_state",
"recv": {"cls": "BuildManager", "state": None},
"args": [], "kwargs": {}})
# state present → writes BUILDS_DIR/<id>/ledger.json (sandboxed HOME);
# result None + state unchanged.
call(bm.BuildManager._save_state)
# ── correction cycle (corrections increment; fixed False and True) ─
cMgr = fresh_mgr()
cMgr.state.corrections = 1
def ccall(fn, *args, **kwargs):
cases.append({
"fn": f"BuildManager.{fn.__name__}",
"recv": {"cls": "BuildManager", "state": seed(cMgr)},
"args": list(args), "kwargs": kwargs,
})
fn(cMgr, *args, **kwargs)
ccall(bm.BuildManager.start_correct)
ccall(bm.BuildManager.complete_correct, False)
ccall(bm.BuildManager.start_correct)
ccall(bm.BuildManager.complete_correct, True)
# ── verify/integrate FAILURE branches (failure lives in DETAILS —
# status stays "complete", the pinned oracle quirk) ─────────────
fMgr = fresh_mgr()
def fcall(fn, *args, **kwargs):
cases.append({
"fn": f"BuildManager.{fn.__name__}",
"recv": {"cls": "BuildManager", "state": seed(fMgr)},
"args": list(args), "kwargs": kwargs,
})
fn(fMgr, *args, **kwargs)
fcall(bm.BuildManager.complete_verify, False, ["test_x failed"])
fcall(bm.BuildManager.start_integrate)
fcall(bm.BuildManager.complete_integrate, False, ["scope breach"])
# ── feasibility details=None branch (default {}) ─────────────────
dMgr = fresh_mgr()
cases.append({
"fn": "BuildManager.complete_feasible",
"recv": {"cls": "BuildManager", "state": seed(dMgr)},
"args": [True, "low"], "kwargs": {},
})
# ── resume scan branches (get_resume_point / can_resume) ─────────
# state None → None/False; empty phases → None; in_progress → found;
# failed → found; all complete → None.
def scan_case(state_dict):
cases.append({"fn": "BuildManager.get_resume_point",
"recv": {"cls": "BuildManager", "state": state_dict},
"args": [], "kwargs": {}})
cases.append({"fn": "BuildManager.can_resume",
"recv": {"cls": "BuildManager", "state": state_dict},
"args": [], "kwargs": {}})
scan_case(None)
scan_case(seed(fresh_mgr()))
p = seed(fresh_mgr())
p["phases"]["plan"] = "in_progress"
scan_case(p)
f = seed(fresh_mgr())
f["phases"]["verify"] = "failed"
scan_case(f)
scan_case(seed(mgr)) # fully walked: every phase complete
# ── get_status branches ──────────────────────────────────────────
cases.append({"fn": "BuildManager.get_status",
"recv": {"cls": "BuildManager", "state": None},
"args": [], "kwargs": {}})
cases.append({"fn": "BuildManager.get_status",
"recv": {"cls": "BuildManager", "state": seed(mgr)},
"args": [], "kwargs": {}})
# ── _load_state IO roundtrip + byte fixture ──────────────────────
# The fixture is the CORRECTION-CYCLE state: every dict in it is
# single-key or sorted-key (see BYTE-PARITY LAW) — the one state whose
# json.dump bytes Go can reproduce exactly. The full-walk state (with
# the oracle-internal feasible payload) is roundtrip-proven at the
# parsed level instead.
full_state = dataclasses.asdict(mgr.state)
fixture_state = dataclasses.asdict(cMgr.state)
with tempfile.TemporaryDirectory() as tmp:
bm.BUILDS_DIR = tmp # oracle reads the module global at call time
build_dir = Path(tmp) / BUILD_ID
build_dir.mkdir(parents=True)
ledger_bytes = json.dumps(fixture_state, indent=2,
ensure_ascii=False).encode("utf-8")
(build_dir / "ledger.json").write_bytes(ledger_bytes)
full_bytes = json.dumps(full_state, indent=2,
ensure_ascii=False).encode("utf-8")
(build_dir.parent / "full-walk-build").mkdir()
(build_dir.parent / "full-walk-build" / "ledger.json").write_bytes(
full_bytes)
m2 = bm.BuildManager.__new__(bm.BuildManager)
loaded = bm.BuildManager._load_state(m2, BUILD_ID)
assert dataclasses.asdict(loaded) == fixture_state, "roundtrip broke"
full_id = "full-walk-build"
m3 = bm.BuildManager.__new__(bm.BuildManager)
loaded_full = bm.BuildManager._load_state(m3, full_id)
assert dataclasses.asdict(loaded_full) == full_state, "full broke"
# missing build → FileNotFoundError("Build <id> not found")
try:
bm.BuildManager._load_state(m2, "no-such-build")
raise SystemExit("_load_state missing-path did NOT raise")
except FileNotFoundError as e:
assert str(e) == f"Build no-such-build not found", str(e)
fixture = REPO / "src/go/build-mode" / "ledger_load_fixture.json"
fixture.write_bytes(ledger_bytes)
print(f"wrote {fixture} ({len(ledger_bytes)} bytes, oracle roundtrip proven)")
truth = freeze("rune.build_mode", cases, "rune.build_mode")
out = REPO / "src/go/build-mode" / "truth_buildmode_gap.json"
write_envelope(FROZEN_NOW, truth, out)
print(f"wrote {out} ({len(truth)} frozen cases)")
if __name__ == "__main__":
main()
+5
View File
@@ -223,7 +223,12 @@ func StartBuildID(nowUTC string) string {
// ResumePoint — reversed declaration-order scan: the first phase whose
// status is in_progress or failed; "" when all complete (Python None).
// Nil state → "" (the Python's `if not self.state: return None` guard,
// pinned by truth_buildmode_gap.json).
func ResumePoint(state *BuildState) string {
if state == nil {
return ""
}
for i := len(Phases) - 1; i >= 0; i-- {
status, has := state.Phases[Phases[i]]
if !has {
+185
View File
@@ -0,0 +1,185 @@
package main
// build_mode_gap.go — gap-closing batch: the PARTIAL audit's missing fns of
// src/rune/build_mode.py (contract BUILDER-2026-10-13T02:02:02Z-dad27f-
// factory-gap-bm). _load_state/_save_state (the ledger IO seam, buildsDir
// INJECTED — the Python reads a module global), assess_complexity, and the
// full start_* phase-transition family. Phase names and ordering are
// load-bearing for the pipeline: every transition here is frozen
// transition-by-transition in declaration order in
// truth_buildmode_gap.json (live oracle, clock pinned via the shared
// freeze_truth harness).
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
)
// LoadState — _load_state: read BUILDS_DIR/<build_id>/ledger.json into a
// BuildState. Missing file → the Python FileNotFoundError text.
func LoadState(buildsDir, buildID string) (*BuildState, error) {
ledgerPath := filepath.Join(buildsDir, buildID, "ledger.json")
if _, err := os.Stat(ledgerPath); err != nil {
return nil, fmt.Errorf("Build %s not found", buildID)
}
data, err := os.ReadFile(ledgerPath)
if err != nil {
return nil, err
}
var state BuildState
if err := json.Unmarshal(data, &state); err != nil {
return nil, err
}
return &state, nil
}
// SaveState — _save_state: write the state as asdict-shape JSON (indent 2,
// no HTML escaping, no trailing newline — Python json.dump byte contract,
// pinned by ledger_load_fixture.json). Nil state → no-op (the Python's
// `if not self.state: return`).
//
// BYTE PARITY TRAP: Python dict preserves INSERTION order and json.dump
// emits it; Go maps marshal sorted. The live manager creates the phases
// keys via _log_phase, so the ledger's first-occurrence order IS the dict
// order — the phases object is spliced in that order (keys missing from
// the ledger fall back to pipeline declaration order). The nil sentinel
// replaces exactly the one top-level "phases" key.
func SaveState(state *BuildState, buildsDir string) error {
if state == nil {
return nil
}
buildDir := filepath.Join(buildsDir, state.BuildID)
if err := os.MkdirAll(buildDir, 0o755); err != nil {
return err
}
data, err := stateJSON(state)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(buildDir, "ledger.json"), data, 0o644)
}
// stateJSON — the Python json.dump byte contract (indent 2, no HTML
// escaping, no trailing newline, phases in dict-insertion order).
func stateJSON(state *BuildState) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
saved := state.Phases
state.Phases = nil
err := enc.Encode(state)
state.Phases = saved
if err != nil {
return nil, err
}
out := bytes.Replace(buf.Bytes(), []byte(`"phases": null`),
orderedPhasesJSON(state), 1)
return bytes.TrimRight(out, "\n"), nil
}
// orderedPhasesJSON — the phases object in Python-dict insertion order
// (ledger first-occurrence, then declaration order, then sorted rest).
func orderedPhasesJSON(state *BuildState) []byte {
order := make([]string, 0, len(state.Phases))
seen := make(map[string]bool, len(state.Phases))
for _, e := range state.Ledger {
if _, has := state.Phases[e.Phase]; has && !seen[e.Phase] {
seen[e.Phase] = true
order = append(order, e.Phase)
}
}
for _, p := range Phases {
if _, has := state.Phases[p]; has && !seen[p] {
seen[p] = true
order = append(order, p)
}
}
rest := make([]string, 0)
for p := range state.Phases {
if !seen[p] {
rest = append(rest, p)
}
}
sort.Strings(rest)
order = append(order, rest...)
var b bytes.Buffer
// Python json.dump(indent=2) nests the phases entries one level deeper
// (4-space entries, 2-space closing brace).
b.WriteString(`"phases": {`)
for i, p := range order {
if i > 0 {
b.WriteByte(',')
}
b.WriteString("\n ")
k, _ := json.Marshal(p)
v, _ := json.Marshal(state.Phases[p])
b.Write(k)
b.WriteString(": ")
b.Write(v)
}
if len(order) > 0 {
b.WriteString("\n ")
}
b.WriteString("}")
return b.Bytes()
}
// AssessComplexity — assess_complexity: the placeholder report, verbatim.
func AssessComplexity() map[string]interface{} {
return map[string]interface{}{
"complexity": "complex", // simple/medium/complex
"estimated_steps": 10,
"requires_build_mode": true,
}
}
// StartResearch — start_research.
func StartResearch(state *BuildState, nowISO string) {
LogPhase(state, "research", StatusInProgress, nil, nil, nowISO)
}
// StartFeasible — start_feasible.
func StartFeasible(state *BuildState, nowISO string) {
LogPhase(state, "feasible", StatusInProgress, nil, nil, nowISO)
}
// StartConsult — start_consult.
func StartConsult(state *BuildState, nowISO string) {
LogPhase(state, "consult", StatusInProgress, nil, nil, nowISO)
}
// StartConsent — start_consent.
func StartConsent(state *BuildState, nowISO string) {
LogPhase(state, "consent", StatusInProgress, nil, nil, nowISO)
}
// StartPlan — start_plan.
func StartPlan(state *BuildState, nowISO string) {
LogPhase(state, "plan", StatusInProgress, nil, nil, nowISO)
}
// StartBuildPhase — start_build_phase.
func StartBuildPhase(state *BuildState, nowISO string) {
LogPhase(state, "build", StatusInProgress, nil, nil, nowISO)
}
// StartVerify — start_verify.
func StartVerify(state *BuildState, nowISO string) {
LogPhase(state, "verify", StatusInProgress, nil, nil, nowISO)
}
// StartIntegrate — start_integrate.
func StartIntegrate(state *BuildState, nowISO string) {
LogPhase(state, "integrate", StatusInProgress, nil, nil, nowISO)
}
// StartRecord — start_record.
func StartRecord(state *BuildState, nowISO string) {
LogPhase(state, "record", StatusInProgress, nil, nil, nowISO)
}
@@ -0,0 +1,55 @@
{
"build_id": "20261013-022000",
"description": "gap battery build",
"created_at": "2026-10-13T02:20:00+00:00",
"worktree_path": null,
"current_phase": "assess",
"phases": {
"correct": "complete"
},
"ledger": [
{
"phase": "correct",
"status": "in_progress",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {},
"errors": []
},
{
"phase": "correct",
"status": "complete",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {
"fixed": false
},
"errors": []
},
{
"phase": "correct",
"status": "in_progress",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {},
"errors": []
},
{
"phase": "correct",
"status": "complete",
"timestamp": "2026-10-13T02:20:00+00:00",
"details": {
"fixed": true
},
"errors": []
}
],
"batches": [],
"corrections": 3,
"max_corrections": 3,
"current_build_phase": "foundation",
"trajectory": [],
"failure_patterns": [],
"complexity_score": 0,
"estimated_steps": 0,
"actual_steps": 0,
"files_changed": [],
"contracts": []
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,257 @@
package main
// truth_buildmode_gap_test.go — gap-closing parity gate: replays
// truth_buildmode_gap.json VERBATIM (go:embed — the shared freeze_truth
// harness output, live oracle, clock pinned) through the ported functions.
// Every case carries a recv receiver (the PRE state), the call args, and
// the expected post-call state + result; the Go replay must match
// byte-for-byte after canonical JSON. Contract
// BUILDER-2026-10-13T02:02:02Z-dad27f-factory-gap-bm.
import (
_ "embed"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
//go:embed truth_buildmode_gap.json
var gapTruth []byte
//go:embed ledger_load_fixture.json
var ledgerFixture []byte
type gapEnvelope struct {
FrozenNow string `json:"frozen_now"`
Cases []struct {
Fn string `json:"fn"`
Recv struct {
Cls string `json:"cls"`
State json.RawMessage `json:"state"`
} `json:"recv"`
Args []interface{} `json:"args"`
Expected struct {
Result json.RawMessage `json:"result"`
State json.RawMessage `json:"state"`
} `json:"expected"`
} `json:"cases"`
}
func TestBuildModeGapParity(t *testing.T) {
var doc gapEnvelope
if err := json.Unmarshal(gapTruth, &doc); err != nil {
t.Fatalf("unparseable: %v", err)
}
stamp := doc.FrozenNow + "+00:00"
for i, c := range doc.Cases {
name := c.Fn
t.Run(name, func(t *testing.T) {
// Rebuild the receiver: recv.state null → nil state.
var state *BuildState
if string(c.Recv.State) != "null" && len(c.Recv.State) > 0 {
state = &BuildState{}
if err := json.Unmarshal(c.Recv.State, state); err != nil {
t.Fatalf("recv state unparseable: %v", err)
}
}
got, gotState := dispatchGap(t, state, c.Fn, c.Args, stamp)
canonEq(t, "result", got, c.Expected.Result)
// state: absent/null expected → no state attr or nil state;
// else canonical compare.
if len(c.Expected.State) == 0 || string(c.Expected.State) == "null" {
if len(c.Expected.State) == 0 {
return // receiver never had the attr — nothing to compare
}
if gotState != nil {
t.Fatalf("expected nil state, got one")
}
} else {
canonEq(t, "state", gotState, c.Expected.State)
}
})
_ = i
}
}
// dispatchGap — replay one frozen call through the ported function; return
// the JSON-comparable result and the post-call state pointer.
func dispatchGap(t *testing.T, state *BuildState, fn string, args []interface{}, stamp string) (result interface{}, outState *BuildState) {
t.Helper()
name := strings.TrimPrefix(fn, "BuildManager.")
arg := func(i int) interface{} {
if i < len(args) {
return args[i]
}
return nil
}
switch name {
case "assess_complexity":
return AssessComplexity(), state
case "complete_assess":
CompleteAssess(state, arg(0).(map[string]interface{}), stamp)
case "start_research":
StartResearch(state, stamp)
case "complete_research":
CompleteResearch(state, arg(0).(map[string]interface{}), stamp)
case "start_feasible":
StartFeasible(state, stamp)
case "complete_feasible":
CompleteFeasible(state, arg(0) == true, arg(1).(string),
argMap(arg(2)), stamp)
case "start_consult":
StartConsult(state, stamp)
case "complete_consult":
CompleteConsult(state, strList(arg(0)), stamp)
case "start_consent":
StartConsent(state, stamp)
case "complete_consent":
CompleteConsent(state, arg(0) == true, stamp)
case "start_plan":
StartPlan(state, stamp)
case "complete_plan":
CompletePlan(state, objList(arg(0)), stamp)
case "start_build_phase":
StartBuildPhase(state, stamp)
case "complete_build_phase":
CompleteBuildPhase(state, objList(arg(0)), stamp)
case "start_verify":
StartVerify(state, stamp)
case "complete_verify":
CompleteVerify(state, arg(0) == true, strList(arg(1)), stamp)
case "start_correct":
StartCorrect(state, stamp)
case "complete_correct":
CompleteCorrect(state, arg(0) == true, stamp)
case "start_integrate":
StartIntegrate(state, stamp)
case "complete_integrate":
CompleteIntegrate(state, arg(0) == true, strList(arg(1)), stamp)
case "start_record":
StartRecord(state, stamp)
case "complete_record":
CompleteRecord(state, stamp)
case "_save_state":
dir := t.TempDir()
if err := SaveState(state, dir); err != nil {
t.Fatalf("save: %v", err)
}
return nil, state
case "get_resume_point":
rp := ResumePoint(state)
if rp == "" {
return nil, state // Python None
}
return rp, state
case "can_resume":
return CanResume(state), state
case "get_status":
return BuildStatus(state), state
default:
t.Fatalf("unknown frozen fn %q", fn)
}
return nil, state
}
func argMap(v interface{}) map[string]interface{} {
m, _ := v.(map[string]interface{})
return m
}
// canonEq — canonical-JSON comparison (Go-built values vs the frozen
// oracle snapshot).
func canonEq(t *testing.T, what string, got interface{}, wantRaw json.RawMessage) {
t.Helper()
gb, err := json.Marshal(got)
if err != nil {
t.Fatalf("marshal got %s: %v", what, err)
}
var gotV, wantV interface{}
if err := json.Unmarshal(gb, &gotV); err != nil {
t.Fatalf("reparse got %s: %v", what, err)
}
if err := json.Unmarshal(wantRaw, &wantV); err != nil {
t.Fatalf("reparse want %s: %v", what, err)
}
gc, _ := json.Marshal(gotV)
wc, _ := json.Marshal(wantV)
if string(gc) != string(wc) {
t.Fatalf("%s divergence:\n got %s\nwant %s", what, gc, wc)
}
}
// TestLoadStateFixture — the IO seam: a build whose ledger.json holds the
// oracle's exact save bytes must load into the identical state.
func TestLoadStateFixture(t *testing.T) {
dir := t.TempDir()
buildDir := filepath.Join(dir, "20261013-022000")
if err := os.MkdirAll(buildDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(buildDir, "ledger.json"), ledgerFixture, 0o644); err != nil {
t.Fatal(err)
}
state, err := LoadState(dir, "20261013-022000")
if err != nil {
t.Fatalf("load: %v", err)
}
var want BuildState
if err := json.Unmarshal(ledgerFixture, &want); err != nil {
t.Fatal(err)
}
canonEq(t, "loadstate", state, ledgerFixture)
if _, err := LoadState(dir, "no-such-build"); err == nil ||
err.Error() != "Build no-such-build not found" {
t.Fatalf("missing-build error: got %v", err)
}
}
// TestSaveStateBytes — the byte contract: Python json.dump(asdict(state),
// indent=2) — the fixture IS those bytes; SaveState must reproduce them.
func TestSaveStateBytes(t *testing.T) {
var state BuildState
if err := json.Unmarshal(ledgerFixture, &state); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
if err := SaveState(&state, dir); err != nil {
t.Fatal(err)
}
got, err := os.ReadFile(filepath.Join(dir, "20261013-022000", "ledger.json"))
if err != nil {
t.Fatal(err)
}
if string(got) != string(ledgerFixture) {
t.Fatalf("save bytes diverge from oracle fixture:\n got %d bytes\nwant %d bytes",
len(got), len(ledgerFixture))
}
// nil state → no-op, nothing written (fresh dir: nothing created).
nilDir := t.TempDir()
if err := SaveState(nil, nilDir); err != nil {
t.Fatalf("nil save: %v", err)
}
entries, _ := os.ReadDir(nilDir)
if len(entries) != 0 {
t.Fatalf("nil state must not create the build dir, found %d entries", len(entries))
}
}
// TestSaveStateRoundTrip — the phases splice must never corrupt content:
// save the fixture state, load it back, require canonical equality.
func TestSaveStateRoundTrip(t *testing.T) {
var state BuildState
if err := json.Unmarshal(ledgerFixture, &state); err != nil {
t.Fatal(err)
}
dir := t.TempDir()
if err := SaveState(&state, dir); err != nil {
t.Fatal(err)
}
back, err := LoadState(dir, state.BuildID)
if err != nil {
t.Fatal(err)
}
canonEq(t, "roundtrip", back, ledgerFixture)
}
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.262.0"
__version__ = "0.263.0"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.262.0"' in rune_version,
'__version__ = "0.263.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+2 -2
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.262.0
Version: v0.263.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -2037,7 +2037,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.262.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.263.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours