check.py file-level

at main · 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 """Post-merge land closeout probe (§PMHF.5) — never merges, never writes docs.
2
3 Composes the GFG freshness probe (D1/D2/marker authority) with the handover
4 ``land-phase`` posture (§PMHF.4) to resolve one of the frozen states:
5 ``not_applicable`` | ``land_a_in_progress`` | ``post_merge_incomplete`` |
6 ``land_b_in_progress`` | ``complete`` | ``unreadable``.
7 """
8
9 from __future__ import annotations
10
11 import json
12 from dataclasses import dataclass
13 from pathlib import Path
14
15 from adapters.base import VcsAdapter
16 from adapters.config import OverseerConfig, resolve_lane_docs
17 from adapters.factory import create_adapter
18 from adapters.runner import CommandRunner, SubprocessRunner
19 from cli.docs_paths import lane_living_doc_abs
20 from cli.version_lock import LockError, lock_path, read_version_lock
21 from tools.governance_freshness import (
22 GovernanceFreshnessReport,
23 check_governance_freshness,
24 )
25 from tools.governance_hygiene.next_regen import (
26 LAND_B_REMEDIATION,
27 LAND_PHASE_A,
28 LAND_PHASE_B,
29 LAND_PHASE_UNREADABLE,
30 extract_land_id,
31 extract_paste_pr_number,
32 land_queue_conflict,
33 resolve_land_phase,
34 )
35
36 STATE_NOT_APPLICABLE = "not_applicable"
37 STATE_LAND_A_IN_PROGRESS = "land_a_in_progress"
38 STATE_POST_MERGE_INCOMPLETE = "post_merge_incomplete"
39 STATE_LAND_B_IN_PROGRESS = "land_b_in_progress"
40 STATE_COMPLETE = "complete"
41 STATE_UNREADABLE = "unreadable"
42
43 CONFLICT_TOKEN = "land_phase_conflicts_queue_done"
44 LAND_B_FINISH_REMEDIATION = (
45 "finish land-b: ok governance-sync --dry-run then apply; "
46 "clear land-phase once NEXT matches merged main"
47 )
48
49
50 @dataclass(frozen=True)
51 class LandCloseoutReport:
52 """Result of a land-closeout probe (§PMHF.5)."""
53
54 state: str
55 message: str
56 remediation: str | None
57 land_phase: str | None # land-a | land-b | None
58 freshness_ok: bool
59 d1: str | None
60 optional_pr_merged: bool | None # None = not probed
61
62 @property
63 def ok(self) -> bool:
64 """Status-floor semantics: mid-land wait must not fail the tree (§PMHF.5)."""
65 return self.state in {
66 STATE_NOT_APPLICABLE,
67 STATE_LAND_A_IN_PROGRESS,
68 STATE_COMPLETE,
69 }
70
71
72 def land_complete(report: LandCloseoutReport) -> bool:
73 """§PMHF.3.3 — stricter than ``report.ok``; ``land_a_in_progress`` is never complete."""
74 return report.freshness_ok and (
75 report.state == STATE_COMPLETE
76 or (report.state == STATE_NOT_APPLICABLE and report.freshness_ok)
77 )
78
79
80 def check_land_closeout(
81 config: OverseerConfig,
82 repo_root: Path,
83 *,
84 adapter: VcsAdapter | None = None,
85 runner: CommandRunner | None = None,
86 probe_merged_pr: bool = False,
87 freshness: GovernanceFreshnessReport | None = None,
88 ) -> LandCloseoutReport:
89 """Resolve land closeout per §PMHF.5.2. Reuses the GFG probe; no re-derivation.
90
91 ``freshness`` may be injected by callers (``ok status``) that already ran the
92 GFG probe. ``probe_merged_pr`` is the only path that may invoke ``gh``
93 (§PMHF.5.3); it is skipped for ``muse-only`` regardless of the flag.
94 """
95 try:
96 lock_file = lock_path(repo_root)
97 if not lock_file.is_file():
98 return LandCloseoutReport(
99 state=STATE_NOT_APPLICABLE,
100 message="not initialized — no land closeout obligation yet",
101 remediation=None,
102 land_phase=None,
103 freshness_ok=True,
104 d1=None,
105 optional_pr_merged=None,
106 )
107 read_version_lock(lock_file)
108 except LockError as exc:
109 return _unreadable(f"version.lock unreadable: {exc}", remediation="ok sync")
110 except OSError as exc:
111 return _unreadable(f"could not load install state: {exc}", remediation="ok sync")
112
113 active_runner = runner or SubprocessRunner()
114 if freshness is None:
115 active_adapter = adapter or create_adapter(config, repo_root, runner=active_runner)
116 freshness = check_governance_freshness(
117 config,
118 repo_root,
119 adapter=active_adapter,
120 runner=active_runner,
121 )
122
123 if freshness.state == "unreadable":
124 # §PMHF.5.2 step 3 / R2-M2: never mask unreadable as post_merge_incomplete.
125 return _unreadable(
126 f"governance freshness unreadable — failing closed: {freshness.message}",
127 remediation=freshness.remediation,
128 freshness_ok=False,
129 d1=freshness.d1,
130 )
131
132 try:
133 handover_text, roadmap_text = _load_default_lane_docs(config, repo_root)
134 except OSError as exc:
135 return _unreadable(
136 f"governance docs unreadable: {exc}",
137 remediation="ok governance-sync --dry-run",
138 freshness_ok=freshness.ok,
139 d1=freshness.d1,
140 )
141
142 land_phase = resolve_land_phase(handover_text)
143 if land_phase == LAND_PHASE_UNREADABLE:
144 return _unreadable(
145 "land-phase unreadable — unknown marker value or conflicting "
146 "land-a/land-b vocabulary in the paste fence",
147 remediation="fix the handover NEXT marker land-phase attribute",
148 freshness_ok=freshness.ok,
149 d1=freshness.d1,
150 )
151
152 main_branch = config.vcs.git.main_branch or "main"
153 if land_phase == LAND_PHASE_A:
154 land_id = extract_land_id(handover_text)
155 if land_id and land_queue_conflict(roadmap_text, land_id, main_branch=main_branch):
156 # §PMHF.3.3 frozen check: queue row DONE/MERGED while handover says land-a.
157 return LandCloseoutReport(
158 state=STATE_UNREADABLE,
159 message=(
160 f"{CONFLICT_TOKEN} — matching land queue row is DONE/MERGED "
161 "while handover land-phase=land-a"
162 ),
163 remediation=LAND_B_REMEDIATION,
164 land_phase=LAND_PHASE_A,
165 freshness_ok=freshness.ok,
166 d1=freshness.d1,
167 optional_pr_merged=None,
168 )
169
170 if land_phase is None:
171 return LandCloseoutReport(
172 state=STATE_NOT_APPLICABLE,
173 message="no land posture on NEXT — closeout not applicable",
174 remediation=None,
175 land_phase=None,
176 freshness_ok=freshness.ok,
177 d1=freshness.d1,
178 optional_pr_merged=None,
179 )
180
181 optional_pr_merged: bool | None = None
182 if probe_merged_pr and config.vcs.regime != "muse-only":
183 optional_pr_merged = _probe_merged_pr(handover_text, active_runner)
184
185 if land_phase == LAND_PHASE_A:
186 if (
187 freshness.d1 == "drifted"
188 or freshness.state in {"drifted", "stale_marker"}
189 or optional_pr_merged is True
190 ):
191 return LandCloseoutReport(
192 state=STATE_POST_MERGE_INCOMPLETE,
193 message=(
194 "merge reflected on main but handover NEXT still says land-a "
195 "(wait-for-merge)"
196 ),
197 remediation=LAND_B_REMEDIATION,
198 land_phase=LAND_PHASE_A,
199 freshness_ok=freshness.ok,
200 d1=freshness.d1,
201 optional_pr_merged=optional_pr_merged,
202 )
203 return LandCloseoutReport(
204 state=STATE_LAND_A_IN_PROGRESS,
205 message="land-a in progress — waiting for Tier 3 merge; main tip still aligned",
206 remediation=None,
207 land_phase=LAND_PHASE_A,
208 freshness_ok=freshness.ok,
209 d1=freshness.d1,
210 optional_pr_merged=optional_pr_merged,
211 )
212
213 # land-b
214 if not freshness.ok or freshness.d1 == "drifted":
215 return LandCloseoutReport(
216 state=STATE_LAND_B_IN_PROGRESS,
217 message="land-b in progress — living docs not yet synced to merged main",
218 remediation=LAND_B_FINISH_REMEDIATION,
219 land_phase=LAND_PHASE_B,
220 freshness_ok=freshness.ok,
221 d1=freshness.d1,
222 optional_pr_merged=optional_pr_merged,
223 )
224 return LandCloseoutReport(
225 state=STATE_COMPLETE,
226 message="land closeout complete — freshness ok and land-b posture satisfied",
227 remediation=None,
228 land_phase=LAND_PHASE_B,
229 freshness_ok=freshness.ok,
230 d1=freshness.d1,
231 optional_pr_merged=optional_pr_merged,
232 )
233
234
235 def _unreadable(
236 message: str,
237 *,
238 remediation: str | None,
239 freshness_ok: bool = False,
240 d1: str | None = None,
241 ) -> LandCloseoutReport:
242 return LandCloseoutReport(
243 state=STATE_UNREADABLE,
244 message=message,
245 remediation=remediation,
246 land_phase=None,
247 freshness_ok=freshness_ok,
248 d1=d1,
249 optional_pr_merged=None,
250 )
251
252
253 def _probe_merged_pr(handover_text: str, runner: CommandRunner) -> bool | None:
254 """§PMHF.5.3 optional enrichment; gh missing/failed → None (never fail open)."""
255 number = extract_paste_pr_number(handover_text)
256 if number is None:
257 return None
258 command = f"gh pr view {number} --json state,mergedAt"
259 result = runner.run(command)
260 if not result.ok:
261 return None
262 try:
263 payload = json.loads(result.stdout)
264 except json.JSONDecodeError:
265 return None
266 if not isinstance(payload, dict):
267 return None
268 state = str(payload.get("state", "")).strip().upper()
269 if state == "MERGED" or payload.get("mergedAt"):
270 return True
271 return False
272
273
274 def _load_default_lane_docs(config: OverseerConfig, repo_root: Path) -> tuple[str, str]:
275 lane = config.docs.default_lane if config.docs.lanes is not None else None
276 lane_docs = resolve_lane_docs(config, lane)
277 handover_path = lane_living_doc_abs(repo_root, config, lane_docs, lane_docs.handover)
278 roadmap_path = lane_living_doc_abs(repo_root, config, lane_docs, lane_docs.roadmap)
279 handover = handover_path.read_text(encoding="utf-8") if handover_path.is_file() else ""
280 roadmap = roadmap_path.read_text(encoding="utf-8") if roadmap_path.is_file() else ""
281 return handover, roadmap
282
283
284 def land_closeout_payload(report: LandCloseoutReport) -> dict:
285 """JSON payload shape shared by ``ok status`` and ``ok land-closeout`` (§PMHF.6.1)."""
286 return {
287 "state": report.state,
288 "ok": report.ok,
289 "message": report.message,
290 "remediation": report.remediation,
291 "land_phase": report.land_phase,
292 "freshness_ok": report.freshness_ok,
293 "d1": report.d1,
294 "optional_pr_merged": report.optional_pr_merged,
295 }