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
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.
149 lines
4.8 KiB
Python
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()
|