Files
rune/scripts/gen_build_mode_wt_truth.py
T
Nick 61432632f7
Auto-version and Release / release (push) Successful in 8s
CI Pipeline / go-surface (push) Successful in 30s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / canary (push) Successful in 55s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / unit-tests (push) Successful in 5m32s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 6m35s
feat(go): build-mode batch 2 — worktree git operations, plan/exec split (contract BUILDER-2026-10-07T18:26:40Z-8be89a-unknown)
The worktree lifecycle ported to build-mode with a TESTABILITY split:
- the PLAN layer is pure and oracle-batteried: WorktreePlan / DeletePlan
  (two bare steps, no short-circuit — parity with the Python's separate
  subprocess calls) / MergePlan (exact message format 'Build {id}: {desc}')
- the EXEC is reactive and fixture-proven: a REAL tmp git repo round-trip
  (create → commit on the branch → merge lands the file in the repo root
  → delete clears worktree+branch), plus the duplicate-create and
  conflicting-merge failure texts (the wrapper 'Failed to merge
  worktree:' is the stable contract; git's own message varies by version —
  the ORACLE's fixture text pinned the shape, the live git pinned the
  variance)
- state transitions verified: worktree_path set on create, cleared by
  merge-follows-delete

PARITY: truth_worktree.json — 6 cases (3 argv plans, state round-trip,
both error texts) from the live BuildManager driving real git with the
clock frozen. Per-wave routine held: gates BARE, spine rebuilt, both
store snapshots shipped.
2026-10-07 19:35:04 +01:00

149 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""gen_build_mode_wt_truth.py — build-mode batch 2 truth battery
(contract BUILDER-2026-10-07T18:26:40Z-8be89a).
The LIVE BuildManager drives a REAL tmp git repo: create → state captured,
merge → state captured, delete → state captured. Cases carry the exact
argv sequences (from monkeypatched subprocess.run) + the state after each
op + the error texts. The Go plan layer must produce the same argv; the
Go exec harness proves the plans drive real git.
Usage: python3 scripts/gen_build_mode_wt_truth.py (from repo root)
"""
import json
import subprocess
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import build_mode as bm # noqa: E402
FROZEN = "2026-10-07T18:28:00"
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
return datetime(2026, 10, 7, 18, 28, 0, tzinfo=timezone.utc)
def main():
bm.datetime = FrozenDatetime
bm.REPO_DIR = str(make_git_repo())
calls = []
def spy_run(cmd, **kwargs):
calls.append(list(cmd))
return subprocess.CompletedProcess(cmd, 0, "", "")
bm.subprocess.run = spy_run
mgr = bm.BuildManager()
build_id = mgr.start_build("worktree battery") # create_worktree inside
create_call = calls[0]
after_create = mgr.state.worktree_path
n_before_merge = len(calls)
mgr.merge_worktree()
merge_call = calls[n_before_merge] # the merge itself
after_merge = mgr.state.worktree_path # None: delete follows the merge
delete_call = calls[n_before_merge + 1], calls[n_before_merge + 2]
cases = [
{
"fn": "plan", "name": "create",
"expected": {"argv": create_call},
},
{
"fn": "plan", "name": "merge",
"expected": {"argv": merge_call},
},
{
"fn": "plan", "name": "delete_pair",
"expected": {"argv": [list(delete_call[0]), list(delete_call[1])]},
},
{
"fn": "state", "name": "round_trip",
"expected": {
"build_id": build_id,
"after_create_is_set": after_create is not None,
"after_merge_is_none": after_merge is None,
},
},
]
# error texts: merge failure mapping
def fail_run(cmd, **kwargs):
if cmd[1] == "merge":
return subprocess.CompletedProcess(cmd, 128, "", "fatal: not something we can merge")
return subprocess.CompletedProcess(cmd, 0, "", "")
bm.subprocess.run = fail_run
mgr2 = bm.BuildManager()
mgr2.state = bm.BuildState(
build_id="20261007-182800", description="error battery",
created_at=FROZEN)
wt = "/home/nick/.rune/worktrees/20261007-182800"
mgr2.state.worktree_path = wt # merge_worktree requires a live worktree path
try:
mgr2.merge_worktree()
merge_err = None
except RuntimeError as ex:
merge_err = str(ex)
cases.append({
"fn": "error", "name": "merge_failure_text",
"expected": {"error": merge_err},
})
def fail_create(cmd, **kwargs):
if cmd[1] == "worktree":
return subprocess.CompletedProcess(cmd, 128, "", "'x' already exists")
return subprocess.CompletedProcess(cmd, 0, "", "")
bm.subprocess.run = fail_create
mgr3 = bm.BuildManager()
mgr3.state = bm.BuildState(
build_id="20261007-182800", description="error battery 2",
created_at=FROZEN)
mgr3._save_state()
try:
mgr3.create_worktree()
create_err = None
except RuntimeError as ex:
create_err = str(ex)
cases.append({
"fn": "error", "name": "create_failure_text",
"expected": {"error": create_err},
})
out = {
"oracle": "src/rune/build_mode.py (live BuildManager on a real tmp git repo, clock frozen)",
"generated": "2026-10-07",
"frozen_now": FROZEN,
"cases": cases,
}
target = REPO / "src/go/build-mode" / "truth_worktree.json"
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
print(f"wrote {target} ({len(cases)} cases)")
def make_git_repo():
root = tempfile.mkdtemp(prefix="bm-wt-truth-")
env_realloc = subprocess.run(
["git", "init", "-q"], cwd=root, capture_output=True, text=True)
assert env_realloc.returncode == 0
subprocess.run(["git", "config", "user.email", "t@t"], cwd=root, capture_output=True)
subprocess.run(["git", "config", "user.name", "t"], cwd=root, capture_output=True)
(Path(root) / "seed.txt").write_text("seed\n")
subprocess.run(["git", "add", "."], cwd=root, capture_output=True)
subprocess.run(["git", "commit", "-qm", "seed"], cwd=root, capture_output=True)
return root
if __name__ == "__main__":
main()