Add assess_already_landed_handoff_report verifier to block Pinned reviewed head, Scratch worktree used, legacy Mutations/Workspace mutations None when git fetch or other mutations occurred, and narrative/handoff drift after the already-landed gate fires. Wire into build_final_report and review_proofs. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
216 lines
7.0 KiB
Python
216 lines
7.0 KiB
Python
"""Already-landed controller handoff consistency verifier (#299)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from review_proofs import git_ref_mutating_commands
|
|
|
|
ALREADY_LANDED_STATE = "ALREADY_LANDED_RECONCILE_REQUIRED"
|
|
|
|
_HANDOFF_SECTION_RE = re.compile(r"^##\s*Controller Handoff\s*$", re.I | re.M)
|
|
|
|
_STALE_HANDOFF_FIELDS = (
|
|
"pinned reviewed head",
|
|
"scratch worktree used",
|
|
)
|
|
|
|
_LEGACY_MUTATIONS_NONE_RE = re.compile(
|
|
r"^\s*[-*]?\s*mutations\s*:\s*none\s*$",
|
|
re.I | re.M,
|
|
)
|
|
_LEGACY_WORKSPACE_NONE_RE = re.compile(
|
|
r"^\s*[-*]?\s*workspace\s+mutations\s*:\s*none\s*$",
|
|
re.I | re.M,
|
|
)
|
|
|
|
_FORBIDDEN_REVIEW_STATES_RE = re.compile(
|
|
r"review decision\s*:\s*approved?\b|"
|
|
r"merge result\s*:\s*merged\b|"
|
|
r"\bready[_ ]to[_ ]merge\b",
|
|
re.I,
|
|
)
|
|
|
|
_NARRATIVE_ELIGIBILITY_RE = re.compile(
|
|
r"eligibility class\s*:\s*([^\n]+)",
|
|
re.I,
|
|
)
|
|
_NARRATIVE_REVIEWED_SHA_RE = re.compile(
|
|
r"reviewed head sha\s*:\s*([^\n]+)",
|
|
re.I,
|
|
)
|
|
_NARRATIVE_WORKTREE_RE = re.compile(
|
|
r"review worktree used\s*:\s*([^\n]+)",
|
|
re.I,
|
|
)
|
|
|
|
|
|
def _handoff_field_map(report_text: str) -> dict[str, str]:
|
|
text = report_text or ""
|
|
match = _HANDOFF_SECTION_RE.search(text)
|
|
if not match:
|
|
return {}
|
|
fields: dict[str, str] = {}
|
|
for line in text[match.end() :].splitlines():
|
|
stripped = line.strip().lstrip("-*").strip()
|
|
if ":" not in stripped:
|
|
continue
|
|
key, value = stripped.split(":", 1)
|
|
fields[key.strip().lower()] = value.strip()
|
|
return fields
|
|
|
|
|
|
def _narrative_before_handoff(report_text: str) -> str:
|
|
text = report_text or ""
|
|
match = _HANDOFF_SECTION_RE.search(text)
|
|
if not match:
|
|
return text
|
|
return text[: match.start()]
|
|
|
|
|
|
def _is_truthy(value: str) -> bool:
|
|
lowered = (value or "").strip().lower()
|
|
return lowered not in {"", "none", "n/a", "not applicable", "false", "no", "—", "-"}
|
|
|
|
|
|
def _gate_active(text: str, fields: dict[str, str], session: dict) -> bool:
|
|
if session.get("gate_fired") or session.get("already_landed_gate_fired"):
|
|
return True
|
|
eligibility = (fields.get("eligibility class") or "").upper()
|
|
if ALREADY_LANDED_STATE in eligibility:
|
|
return True
|
|
return ALREADY_LANDED_STATE.lower() in text.lower()
|
|
|
|
|
|
def assess_already_landed_handoff_report(
|
|
report_text: str,
|
|
*,
|
|
handoff_session: dict | None = None,
|
|
command_log: list | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Reject stale handoff fields and narrative/handoff drift after the gate (#299)."""
|
|
text = report_text or ""
|
|
session = dict(handoff_session or {})
|
|
fields = _handoff_field_map(text)
|
|
reasons: list[str] = []
|
|
|
|
if not _gate_active(text, fields, session):
|
|
return {
|
|
"proven": True,
|
|
"block": False,
|
|
"reasons": [],
|
|
"gate_active": False,
|
|
"safe_next_action": "proceed",
|
|
}
|
|
|
|
for stale_field in _STALE_HANDOFF_FIELDS:
|
|
if stale_field in fields:
|
|
reasons.append(
|
|
f"already-landed handoff must not include stale field "
|
|
f"'{stale_field.title()}' (#299)"
|
|
)
|
|
|
|
if _LEGACY_WORKSPACE_NONE_RE.search(text):
|
|
reasons.append(
|
|
"already-landed handoff must not include legacy "
|
|
"'Workspace mutations: None' (#299)"
|
|
)
|
|
|
|
ref_commands = git_ref_mutating_commands(command_log or session.get("command_log"))
|
|
mutations_observed = bool(
|
|
ref_commands
|
|
or session.get("mutations_observed")
|
|
or session.get("mcp_mutations")
|
|
or session.get("review_mutations")
|
|
)
|
|
if mutations_observed and _LEGACY_MUTATIONS_NONE_RE.search(text):
|
|
reasons.append(
|
|
"already-landed handoff must not claim 'Mutations: None' when "
|
|
"git ref, MCP, review, merge, or cleanup mutations occurred (#299)"
|
|
)
|
|
|
|
git_ref_value = fields.get("git ref mutations", "").strip().lower()
|
|
if ref_commands and (not git_ref_value or git_ref_value == "none"):
|
|
reasons.append(
|
|
"git fetch/ref updates must be reported under Git ref mutations (#299)"
|
|
)
|
|
|
|
reviewed_sha = fields.get("reviewed head sha", "")
|
|
if reviewed_sha and _is_truthy(reviewed_sha):
|
|
reasons.append(
|
|
"already-landed handoff must use 'Reviewed head SHA: none' (#299)"
|
|
)
|
|
|
|
worktree_used = fields.get("review worktree used", "")
|
|
if worktree_used and _is_truthy(worktree_used):
|
|
reasons.append(
|
|
"already-landed handoff must set Review worktree used: false (#299)"
|
|
)
|
|
|
|
candidate_sha = fields.get("candidate head sha", "")
|
|
if not candidate_sha or candidate_sha.lower() in {"none", "n/a", "unknown"}:
|
|
reasons.append(
|
|
"already-landed handoff must include Candidate head SHA (#299)"
|
|
)
|
|
|
|
if _FORBIDDEN_REVIEW_STATES_RE.search(text):
|
|
reasons.append(
|
|
"already-landed handoff must not claim approved/merged/ready-to-merge (#299)"
|
|
)
|
|
|
|
narrative = _narrative_before_handoff(text)
|
|
handoff_eligibility = (fields.get("eligibility class") or "").strip()
|
|
narrative_eligibility = (
|
|
_NARRATIVE_ELIGIBILITY_RE.search(narrative).group(1).strip()
|
|
if _NARRATIVE_ELIGIBILITY_RE.search(narrative)
|
|
else ""
|
|
)
|
|
if handoff_eligibility and narrative_eligibility:
|
|
if handoff_eligibility.upper() != narrative_eligibility.upper():
|
|
reasons.append(
|
|
"narrative Eligibility class disagrees with controller handoff (#299)"
|
|
)
|
|
|
|
narrative_reviewed = (
|
|
_NARRATIVE_REVIEWED_SHA_RE.search(narrative).group(1).strip()
|
|
if _NARRATIVE_REVIEWED_SHA_RE.search(narrative)
|
|
else ""
|
|
)
|
|
if narrative_reviewed and reviewed_sha:
|
|
if narrative_reviewed.lower() != reviewed_sha.lower():
|
|
reasons.append(
|
|
"narrative Reviewed head SHA disagrees with controller handoff (#299)"
|
|
)
|
|
|
|
narrative_worktree = (
|
|
_NARRATIVE_WORKTREE_RE.search(narrative).group(1).strip()
|
|
if _NARRATIVE_WORKTREE_RE.search(narrative)
|
|
else ""
|
|
)
|
|
if narrative_worktree and worktree_used:
|
|
if narrative_worktree.lower() != worktree_used.lower():
|
|
reasons.append(
|
|
"narrative Review worktree used disagrees with controller handoff (#299)"
|
|
)
|
|
|
|
git_ref_narrative = "git ref mutations" in narrative.lower()
|
|
if git_ref_narrative and git_ref_value:
|
|
if "none" in git_ref_value and ref_commands:
|
|
reasons.append(
|
|
"narrative and handoff disagree on git ref mutation reporting (#299)"
|
|
)
|
|
|
|
proven = not reasons
|
|
return {
|
|
"proven": proven,
|
|
"block": not proven,
|
|
"reasons": list(dict.fromkeys(reasons)),
|
|
"gate_active": True,
|
|
"safe_next_action": (
|
|
"emit canonical ALREADY_LANDED_RECONCILE_REQUIRED handoff without "
|
|
"stale review/merge fields; align narrative and controller handoff"
|
|
if reasons
|
|
else "proceed"
|
|
),
|
|
} |