"""Infra-stop repair handoff verifier for blocked reviewer workflows (#289).""" from __future__ import annotations import re from typing import Any from capability_stop_terminal import ( TERMINAL_REPORT_HEADING, assess_capability_stop_report, ) _REPAIR_HANDOFF_RE = re.compile( r"repair handoff|control-checkout repair mode", re.IGNORECASE, ) _CONTROL_REPAIR_RE = re.compile(r"control-checkout repair mode", re.IGNORECASE) _PR_QUEUE_ADVANCE_RE = re.compile( r"(?:selected pr|pr #\d+ (?:to review|selected)|eligible pr|" r"next (?:eligible )?pr(?: to review)?|oldest eligible pr|pinned (?:review )?head)", re.IGNORECASE, ) _REVIEW_MUTATION_RE = re.compile( r"(?:gitea_review_pr|gitea_merge_pr|request_changes|submitted\s+approve|" r"merge result|review decision\s*:\s*approve)", re.IGNORECASE, ) _BACKGROUND_TOOL_RE = re.compile(r"\b(?:schedule|manage_task)\b", re.IGNORECASE) _PR_REVIEW_REPORT_RE = re.compile( r"(?:review summary|merge recommendation|validation result\s*:\s*pass|" r"queue status report with selected pr)", re.IGNORECASE, ) _DIAGNOSTIC_FIELDS = { "mcp process root": re.compile(r"mcp process root\s*:", re.IGNORECASE), "inspected git root": re.compile(r"inspected git root\s*:", re.IGNORECASE), "conflict marker path": re.compile( r"(?:conflict marker path|exact conflict marker path)\s*:", re.IGNORECASE, ), "merge/rebase control path": re.compile( r"(?:merge/rebase control path|exact merge/rebase control path)\s*:", re.IGNORECASE, ), "safe next repair action": re.compile( r"safe next (?:repair )?action\s*:", re.IGNORECASE, ), } def assess_infra_stop_handoff_report( report_text: str, *, stop_session: dict | None = None, ) -> dict[str, Any]: """Validate repair handoff purity when infra_stop blocks reviewer capability (#289).""" text = report_text or "" session = dict(stop_session or {}) reasons: list[str] = [] infra_stop = bool( session.get("infra_stop") or session.get("infra_stop_blocked") or session.get("route_result") == "infra_stop" ) if not infra_stop: return { "proven": True, "block": False, "reasons": [], "infra_stop": False, "safe_next_action": "proceed", } lower = text.lower() repair_handoff = bool( _REPAIR_HANDOFF_RE.search(text) or TERMINAL_REPORT_HEADING.lower() in lower ) if not repair_handoff: reasons.append( "infra_stop requires a repair handoff, not a PR review report" ) if _PR_REVIEW_REPORT_RE.search(text) and not _REPAIR_HANDOFF_RE.search(text): reasons.append( "final output must be a repair handoff instead of stale PR review state" ) if _PR_QUEUE_ADVANCE_RE.search(text): reasons.append( "infra_stop blocks PR queue advancement; do not select or advance next PR" ) if _REVIEW_MUTATION_RE.search(text): reasons.append( "review, approval, request-changes, merge, or comment mutations " "forbidden while infra_stop is active" ) if session.get("pinned_head_sha") or re.search( r"pinned (?:review )?head sha\s*:", text, re.IGNORECASE ): if session.get("capability_cleared") is not True: reasons.append( "cannot pin PR head SHA while review capability never cleared" ) if session.get("main_checkout_diagnostics") and not _CONTROL_REPAIR_RE.search(text): reasons.append( "main-checkout diagnostics must be labeled CONTROL-CHECKOUT REPAIR MODE" ) if _BACKGROUND_TOOL_RE.search(text): reasons.append( "background schedule/manage_task tools must not be used during " "blocked infra_stop recovery" ) assessment = session.get("infra_stop_assessment") or {} if assessment.get("infra_stop") or infra_stop: missing = [ label for label, pattern in _DIAGNOSTIC_FIELDS.items() if not pattern.search(text) ] if missing and session.get("require_infra_diagnostics", True): reasons.append( "infra_stop repair handoff missing diagnostic fields: " + ", ".join(missing) ) conflict_file = assessment.get("conflict_file") if conflict_file and conflict_file.lower() not in lower: reasons.append( f"infra_stop assessment conflict file {conflict_file!r} " "not reflected in repair handoff" ) capability = assess_capability_stop_report( text, trust_gate_status=session.get("trust_gate_status"), capability_denied=True, ) if not capability.get("pure"): reasons.extend(capability.get("reasons") or []) proven = not reasons return { "proven": proven, "block": not proven, "reasons": list(dict.fromkeys(reasons)), "infra_stop": True, "repair_handoff": repair_handoff, "safe_next_action": ( "stop PR queue work; emit CONTROL-CHECKOUT REPAIR MODE handoff " "with infra diagnostics and safe next repair action" if reasons else "proceed" ), }