test_land_closeout_status.py file-level

at sha256:a · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:6 fix(ISR): default require_independent_second_reviewer to require Opera… · aaronrene · Sep 2, 2026
1 """Integration: land-closeout wiring across status / land-closeout / land-check /
2 governance-sync (§PMHF.10 integration)."""
3
4 from __future__ import annotations
5
6 import io
7 import json
8 import subprocess
9 from contextlib import redirect_stdout
10 from pathlib import Path
11
12 from adapters.config import load_config
13 from cli.kit_root import kit_root
14 from tests.support import (
15 FIXTURES,
16 git_status_runner,
17 land_a_fence_body,
18 land_handover_text,
19 land_roadmap_text,
20 make_runner,
21 ok,
22 run_cli,
23 seed_land_repo,
24 )
25 from tools.close_ritual.land_check import run_land_check
26
27
28 def _seed_via_init(tmp_path: Path, *, claim: str, marker_tip: str | None) -> None:
29 assert (
30 run_cli(
31 ["init", "--from-config", str(FIXTURES / "config-git-only.yaml"), "--non-interactive"],
32 cwd=tmp_path,
33 runner=git_status_runner(tip="cafebabe"),
34 )
35 == 0
36 )
37 (tmp_path / "docs" / "OVERSEER-HANDOVER.md").write_text(
38 land_handover_text(claim), encoding="utf-8"
39 )
40 (tmp_path / "docs" / "ROADMAP.md").write_text(land_roadmap_text(), encoding="utf-8")
41 marker = tmp_path / ".overseer" / "last_governance_sync"
42 if marker_tip is None:
43 if marker.exists():
44 marker.unlink()
45 else:
46 marker.write_text(
47 f"2026-07-30T00:00:00Z\nr1={marker_tip}\nr3={marker_tip}\n", encoding="utf-8"
48 )
49
50
51 def test_status_exit_2_post_merge_incomplete_when_land_a_and_d1_drifted(
52 tmp_path: Path, capsys
53 ) -> None:
54 _seed_via_init(tmp_path, claim="deadbeef", marker_tip="cafebabe")
55 capsys.readouterr()
56 code = run_cli(
57 ["status", "--json", "--exit-code"],
58 cwd=tmp_path,
59 runner=git_status_runner(tip="cafebabe"),
60 json_mode=True,
61 )
62 assert code == 2
63 payload = json.loads(capsys.readouterr().out)
64 assert payload["land_closeout"]["state"] == "post_merge_incomplete"
65 assert payload["land_closeout"]["ok"] is False
66 assert payload["land_closeout"]["land_phase"] == "land-a"
67 assert payload["land_closeout"]["remediation"].startswith("land-b required:")
68
69
70 def test_status_exit_0_when_land_a_and_aligned(tmp_path: Path, capsys) -> None:
71 _seed_via_init(tmp_path, claim="cafebabe", marker_tip="cafebabe")
72 capsys.readouterr()
73 code = run_cli(
74 ["status", "--json", "--exit-code"],
75 cwd=tmp_path,
76 runner=git_status_runner(tip="cafebabe"),
77 json_mode=True,
78 )
79 assert code == 0
80 payload = json.loads(capsys.readouterr().out)
81 assert payload["land_closeout"]["state"] == "land_a_in_progress"
82 assert payload["land_closeout"]["ok"] is True
83
84
85 def test_land_closeout_command_exit_codes(tmp_path: Path, capsys) -> None:
86 _seed_via_init(tmp_path, claim="cafebabe", marker_tip="cafebabe")
87 capsys.readouterr()
88 code = run_cli(
89 ["land-closeout", "--json"],
90 cwd=tmp_path,
91 runner=git_status_runner(tip="cafebabe"),
92 json_mode=True,
93 )
94 assert code == 0
95 payload = json.loads(capsys.readouterr().out)
96 assert payload["state"] == "land_a_in_progress"
97 assert payload["exit_code"] == 0
98
99 (tmp_path / "docs" / "OVERSEER-HANDOVER.md").write_text(
100 land_handover_text("deadbeef"), encoding="utf-8"
101 )
102 capsys.readouterr()
103 code = run_cli(
104 ["land-closeout", "--json"],
105 cwd=tmp_path,
106 runner=git_status_runner(tip="cafebabe"),
107 json_mode=True,
108 )
109 assert code == 2
110 payload = json.loads(capsys.readouterr().out)
111 assert payload["state"] == "post_merge_incomplete"
112 assert payload["exit_code"] == 2
113
114
115 def test_land_closeout_human_output_frozen_tokens(tmp_path: Path, capsys) -> None:
116 _seed_via_init(tmp_path, claim="deadbeef", marker_tip="cafebabe")
117 capsys.readouterr()
118 code = run_cli(
119 ["land-closeout"],
120 cwd=tmp_path,
121 runner=git_status_runner(tip="cafebabe"),
122 )
123 out = capsys.readouterr().out
124 assert code == 2
125 assert "land_closeout: post_merge_incomplete" in out
126 assert (
127 "land_closeout-remediation: land-b required: ok governance-sync --dry-run "
128 "then apply; paste land-b; do not re-paste land-a"
129 ) in out
130
131
132 # --- ok land-check refusal (§PMHF.6.2) ---
133
134
135 def _ritual_config_yaml(tmp_path: Path) -> Path:
136 cfg = tmp_path / ".overseer" / "config.yaml"
137 cfg.parent.mkdir(parents=True, exist_ok=True)
138 cfg.write_text(
139 """
140 overseer_config_version: 1
141 repo:
142 name: fixture
143 root_relative_docs: "docs"
144 vcs:
145 regime: git-only
146 canonical: git
147 git:
148 remote: origin
149 main_branch: main
150 mirror_branch: null
151 feature_branch_pattern: "feat/{slug}"
152 muse:
153 staging_remote: null
154 main_branch: null
155 working_dir: null
156 docs:
157 handover: OVERSEER-HANDOVER.md
158 roadmap: ROADMAP.md
159 standing_decisions: ROADMAP.md
160 thresholds:
161 realign_max_commits: 50
162 drift_warn_only: true
163 freeze_contract:
164 enabled: true
165 reviewer:
166 mode: agent
167 model: thinking-high
168 provider: local
169 fallback: human
170 human_escalation: [security]
171 close_ritual:
172 enabled: true
173 mode: verify_landed
174 require_paths: ["BOARD.json"]
175 """,
176 encoding="utf-8",
177 )
178 return cfg
179
180
181 def _seed_land_check_repo(tmp_path: Path, *, handover_text: str) -> object:
182 init = subprocess.run(
183 ["git", "init", "-b", "main"], cwd=tmp_path, capture_output=True, text=True
184 )
185 assert init.returncode == 0, init.stderr
186 subprocess.run(
187 ["git", "config", "user.email", "[email protected]"],
188 cwd=tmp_path,
189 check=True,
190 capture_output=True,
191 )
192 subprocess.run(
193 ["git", "config", "user.name", "t"], cwd=tmp_path, check=True, capture_output=True
194 )
195 (tmp_path / "BOARD.json").write_text('{"ok": true}\n', encoding="utf-8")
196 subprocess.run(["git", "add", "-A"], cwd=tmp_path, check=True, capture_output=True)
197 subprocess.run(
198 ["git", "commit", "-m", "init"], cwd=tmp_path, check=True, capture_output=True
199 )
200 cfg_path = _ritual_config_yaml(tmp_path)
201 (tmp_path / ".overseer" / "version.lock").write_text(
202 "lock_version: 1\nkit_version: 0.1.0\nconfig_version: 1\n"
203 "footprint_digest: sha256:" + ("0" * 64) + "\n"
204 'installed_at: "2026-01-01T00:00:00Z"\nsynced_at: "2026-01-01T00:00:00Z"\n'
205 "footprint: []\n",
206 encoding="utf-8",
207 )
208 docs = tmp_path / "docs"
209 docs.mkdir(parents=True, exist_ok=True)
210 (docs / "OVERSEER-HANDOVER.md").write_text(handover_text, encoding="utf-8")
211 (docs / "ROADMAP.md").write_text(land_roadmap_text(), encoding="utf-8")
212 (tmp_path / ".overseer" / "last_governance_sync").write_text(
213 "2026-07-30T00:00:00Z\nr1=cafebabe\nr3=cafebabe\n", encoding="utf-8"
214 )
215 return load_config(cfg_path)
216
217
218 def test_land_check_refuses_landed_while_land_a(tmp_path: Path) -> None:
219 config = _seed_land_check_repo(tmp_path, handover_text=land_handover_text("cafebabe"))
220 runner = make_runner(
221 {
222 "git rev-parse --abbrev-ref HEAD": ok("main"),
223 "git status --porcelain": ok(""),
224 "git rev-parse origin/main": ok("cafebabe"),
225 }
226 )
227 result = run_land_check(config, tmp_path, runner=runner)
228 assert result.exit_code == 2
229 assert result.landed is False
230 assert any("land_closeout: land_a_in_progress" in m for m in result.messages)
231
232
233 def test_land_check_refuses_landed_on_post_merge_incomplete(tmp_path: Path) -> None:
234 handover = land_handover_text(
235 "cafebabe",
236 fence_body=land_a_fence_body(paste_extra="PR #206 open — waiting for merge.\n"),
237 )
238 config = _seed_land_check_repo(tmp_path, handover_text=handover)
239 runner = make_runner(
240 {
241 "git rev-parse --abbrev-ref HEAD": ok("main"),
242 "git status --porcelain": ok(""),
243 "git rev-parse origin/main": ok("cafebabe"),
244 "gh pr view 206 --json state,mergedAt": ok(
245 '{"state": "MERGED", "mergedAt": "2026-07-30T12:00:00Z"}'
246 ),
247 }
248 )
249 result = run_land_check(config, tmp_path, runner=runner)
250 assert result.exit_code == 2
251 assert result.landed is False
252 assert any("land_closeout: post_merge_incomplete" in m for m in result.messages)
253 assert any("land-b required:" in m for m in result.messages)
254 assert not any("landed: true" in m.lower() for m in result.messages)
255
256
257 # --- governance-sync dry-run plans land-b (§PMHF.3.4) ---
258
259
260 def _sync_runner():
261 return make_runner(
262 {
263 "git rev-parse --abbrev-ref HEAD": ok("main"),
264 "git status --porcelain": ok(""),
265 "git rev-parse origin/main": ok("cafebabe"),
266 "gh pr list --state merged --limit 5 --json number,title,mergeCommit,mergedAt": ok(
267 "[]"
268 ),
269 "git remote get-url origin": ok("[email protected]:owner/repo.git"),
270 }
271 )
272
273
274 def test_governance_sync_dry_run_plans_land_b_when_land_a_and_drifted(
275 tmp_path: Path,
276 ) -> None:
277 seed_land_repo(tmp_path, claim="deadbeef")
278 handover = tmp_path / "docs" / "OVERSEER-HANDOVER.md"
279 before_h = handover.read_text(encoding="utf-8")
280
281 buf = io.StringIO()
282 with redirect_stdout(buf):
283 code = run_cli(
284 ["governance-sync"],
285 cwd=tmp_path,
286 runner=_sync_runner(),
287 kit=kit_root(),
288 )
289 out = buf.getvalue()
290 assert code == 0
291 assert "next_regen: regenerated (land-b)" in out
292 # §PMHF.3.4 rule 4: dry-run shows the planned land-b body; no doc writes.
293 assert "land-phase: land-b" in out
294 assert "ID: PMHF land-b (post-merge sync)" in out
295 assert handover.read_text(encoding="utf-8") == before_h
296
297
298 def test_governance_sync_preserves_land_a_paste_mid_wait(tmp_path: Path) -> None:
299 # Land-a posture + D3-only drift must not clobber the land-a paste (fail closed).
300 seed_land_repo(
301 tmp_path,
302 claim="cafebabe",
303 roadmap_text=land_roadmap_text(
304 "| **PMHF → main** | Operator + Auto | **TODO** | Land PMHF |",
305 ),
306 )
307 runner = make_runner(
308 {
309 "git rev-parse --abbrev-ref HEAD": ok("main"),
310 "git status --porcelain": ok(""),
311 "git rev-parse origin/main": ok("cafebabe"),
312 "gh pr list --state merged --limit 5 --json number,title,mergeCommit,mergedAt": ok(
313 '[{"number": 999, "title": "PMHF land hygiene", '
314 '"mergeCommit": {"oid": "abcabcabc"}, "mergedAt": "2026-07-30T00:00:00Z"}]'
315 ),
316 "git remote get-url origin": ok("[email protected]:owner/repo.git"),
317 }
318 )
319 buf = io.StringIO()
320 with redirect_stdout(buf):
321 code = run_cli(["governance-sync"], cwd=tmp_path, runner=runner, kit=kit_root())
322 out = buf.getvalue()
323 assert code == 0
324 assert "next_regen: human_authorship_required (land_a_in_progress)" in out