test_land_closeout_cycle.py python
123 lines 4.4 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 12 hours ago
1 """E2E: full land-a → merge → land-b → complete cycle (§PMHF.10 e2e).
2
3 Fixture cycle: land-a NEXT + aligned main → status 0 → advance main tip →
4 status 2 + land-b remediation → governance-sync apply → land-phase cleared to
5 land-b/complete → status 0 + land-closeout 0.
6 """
7
8 from __future__ import annotations
9
10 import io
11 import json
12 from contextlib import redirect_stdout
13 from datetime import date
14 from pathlib import Path
15
16 from cli.kit_root import kit_root
17 from tests.support import make_runner, ok, run_cli, seed_land_repo
18
19
20 def _runner(tip: str):
21 branch = f"feat/governance-sync-{date.today().isoformat()}"
22 return make_runner(
23 {
24 "git rev-parse --abbrev-ref HEAD": ok("main"),
25 "git status --porcelain": ok(""),
26 "git rev-parse origin/main": ok(tip),
27 "gh pr list --state merged --limit 5 --json number,title,mergeCommit,mergedAt": ok(
28 "[]"
29 ),
30 "git remote get-url origin": ok("[email protected]:owner/repo.git"),
31 f"git checkout -b {branch}": ok(""),
32 f"git checkout {branch}": ok(""),
33 "git add -- docs/OVERSEER-HANDOVER.md docs/ROADMAP.md": ok(""),
34 "git commit -m": ok(""),
35 "git rev-parse HEAD": ok("feedface"),
36 f"git push -u origin {branch}": ok(""),
37 }
38 )
39
40
41 def _status(tmp_path: Path, capsys, *, tip: str) -> tuple[int, dict]:
42 capsys.readouterr()
43 code = run_cli(
44 ["status", "--json", "--exit-code"],
45 cwd=tmp_path,
46 runner=_runner(tip),
47 json_mode=True,
48 )
49 return code, json.loads(capsys.readouterr().out)
50
51
52 def test_land_cycle_status_gate_land_b_then_complete(tmp_path: Path, capsys) -> None:
53 seed_land_repo(tmp_path, claim="cafebabe", marker_tip="cafebabe")
54 handover = tmp_path / "docs" / "OVERSEER-HANDOVER.md"
55
56 # 1. land-a mid-wait, main tip aligned → no false fail.
57 code, payload = _status(tmp_path, capsys, tip="cafebabe")
58 assert code == 0
59 assert payload["land_closeout"]["state"] == "land_a_in_progress"
60
61 # 2. merge lands on GitHub: main tip advances → fail-closed with land-b remediation.
62 code, payload = _status(tmp_path, capsys, tip="beefcafe")
63 assert code == 2
64 assert payload["land_closeout"]["state"] == "post_merge_incomplete"
65 assert payload["land_closeout"]["remediation"].startswith("land-b required:")
66
67 # 3. land-b step: governance-sync apply regenerates NEXT/paste as land-b.
68 capsys.readouterr()
69 buf = io.StringIO()
70 with redirect_stdout(buf):
71 code = run_cli(
72 ["governance-sync", "--write"],
73 cwd=tmp_path,
74 runner=_runner("beefcafe"),
75 kit=kit_root(),
76 )
77 assert code == 0
78 text = handover.read_text(encoding="utf-8")
79 assert "land-phase=land-b" in text
80 assert "ID: PMHF land-b (post-merge sync)" in text
81 assert "`beefcafe`" in text
82 assert "wait for merge" not in text.lower()
83
84 # 4. finish land-b: aligned dry-run re-stamps the sync marker (GFG carve-out).
85 capsys.readouterr()
86 buf = io.StringIO()
87 with redirect_stdout(buf):
88 code = run_cli(
89 ["governance-sync"],
90 cwd=tmp_path,
91 runner=_runner("beefcafe"),
92 kit=kit_root(),
93 )
94 assert code == 0
95
96 # 5. closeout complete: status 0 and land-closeout 0.
97 code, payload = _status(tmp_path, capsys, tip="beefcafe")
98 assert code == 0
99 assert payload["land_closeout"]["state"] == "complete"
100 assert payload["land_closeout"]["ok"] is True
101
102 capsys.readouterr()
103 code = run_cli(
104 ["land-closeout", "--json"],
105 cwd=tmp_path,
106 runner=_runner("beefcafe"),
107 json_mode=True,
108 )
109 assert code == 0
110 closeout = json.loads(capsys.readouterr().out)
111 assert closeout["state"] == "complete"
112
113
114 def test_re_pasting_land_a_after_merge_stays_fail_closed(tmp_path: Path, capsys) -> None:
115 # Agent ignores remediation and keeps the land-a paste: status keeps failing.
116 seed_land_repo(tmp_path, claim="cafebabe", marker_tip="cafebabe")
117 code, payload = _status(tmp_path, capsys, tip="beefcafe")
118 assert code == 2
119 assert payload["land_closeout"]["state"] == "post_merge_incomplete"
120 # Re-running without land-b changes nothing — still exit 2.
121 code, payload = _status(tmp_path, capsys, tip="beefcafe")
122 assert code == 2
123 assert "do not re-paste land-a" in payload["land_closeout"]["remediation"]
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 12 hours ago