Reviewer final reports must not claim proof-sensitive workflow steps unless each claim carries explicit command/tool evidence or structured MCP result metadata: - New reviewer_proof_backed_claims.py verifier covering the five claim families from the issue: inventory completeness (explicit pagination metadata required; page-size assumptions rejected), earlier-PR skips (per-PR command/tool or structured mergeability/blocker evidence), baseline validation (worktree path, target SHA, dirty-before, exact command, exact result, dirty-after), master integration (exact merge/rebase/simulation command), and cleanup (final git worktree list proof). - PROOF_PROVENANCE_CLASSES constant distinguishes command actually run, MCP metadata, inherited from previous blocker state, and not checked. - Re-export from review_proofs and wire into build_final_report as a downgrade check with proof_backed_claims_proven/violations fields. - New workflow section 30A documents the rule; contract test locks the marker and an export test locks the re-export. - 13 tests in tests/test_reviewer_proof_backed_claims.py covering the required scenarios: baseline claim without command, skipped-PR claim without evidence, inventory-complete from page-size assumption only, cleanup without final worktree proof, and a fully proof-backed report passing. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
133 lines
5.1 KiB
Python
133 lines
5.1 KiB
Python
"""Proof-backed reviewer handoff claim verifier (#395).
|
|
|
|
Reviewer final reports must not claim proof-sensitive workflow steps unless
|
|
each claim is backed by explicit command/tool evidence in the transcript or
|
|
structured MCP result metadata. This verifier scans report text for the five
|
|
proof-sensitive claim families from issue #395 (inventory completeness,
|
|
earlier-PR skips, baseline validation, master integration, cleanup) and fails
|
|
closed when a claim appears without its required evidence.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
# Issue #395 criterion 6: reports must distinguish these proof provenances.
|
|
PROOF_PROVENANCE_CLASSES = (
|
|
"command actually run",
|
|
"MCP metadata",
|
|
"inherited from previous blocker state",
|
|
"not checked",
|
|
)
|
|
|
|
_INVENTORY_CLAIM_RE = re.compile(
|
|
r"inventory\s+(?:complete|completeness\s*:\s*complete)", re.IGNORECASE
|
|
)
|
|
_PAGINATION_METADATA_RE = re.compile(
|
|
r"(?:has_more\s*=?\s*false|is_final_page\s*=?\s*true|"
|
|
r"inventory_complete\s*=?\s*true|total[_ ]count\s*[=:]\s*\d+|"
|
|
r"no next page|final page|pagination complete)",
|
|
re.IGNORECASE,
|
|
)
|
|
_PAGE_SIZE_ASSUMPTION_RE = re.compile(
|
|
r"(?:less|fewer)\s+than\s+(?:the\s+)?(?:default\s+)?page\s*size|"
|
|
r"under\s+the\s+(?:default\s+)?page\s*size|"
|
|
r"below\s+(?:the\s+)?(?:default\s+)?page\s*size",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
_SKIPPED_PR_RE = re.compile(r"skipped\s+PR\s*#?(\d+)", re.IGNORECASE)
|
|
_SKIP_EVIDENCE_RE = re.compile(
|
|
r"(?:mergeable\s*:\s*false|gitea_view_pr|gitea_get_pr_review_feedback|"
|
|
r"gitea_check_pr_eligibility|git\s+merge-tree|merge simulation|"
|
|
r"conflicting files?\s*:|REQUEST_CHANGES\s+at\s+(?:current\s+)?head|"
|
|
r"prior blocker reused|blocker revalidated live)",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
_BASELINE_CLAIM_RE = re.compile(r"\bbaseline\b", re.IGNORECASE)
|
|
_BASELINE_REQUIRED = (
|
|
("baseline worktree path", re.compile(r"branches/baseline-[\w./-]+", re.IGNORECASE)),
|
|
("baseline target SHA", re.compile(r"target\s+SHA\s*:?\s*[0-9a-f]{7,40}", re.IGNORECASE)),
|
|
("baseline dirty-before status", re.compile(r"dirty\s+before\s*:?\s*\w+", re.IGNORECASE)),
|
|
("exact baseline validation command", re.compile(r"(?:command\s*:|pytest|python\s+-m)", re.IGNORECASE)),
|
|
("exact baseline result", re.compile(r"(?:result\s*:|passed|failed)", re.IGNORECASE)),
|
|
("baseline dirty-after status", re.compile(r"dirty\s+after\s*:?\s*\w+", re.IGNORECASE)),
|
|
)
|
|
|
|
_INTEGRATION_CLAIM_RE = re.compile(
|
|
r"(?:master\s+(?:was\s+)?(?:merged|rebased|integrated)|"
|
|
r"merged?\s+master\s+into|master[- ]integration|rebased\s+onto\s+master)",
|
|
re.IGNORECASE,
|
|
)
|
|
_INTEGRATION_EVIDENCE_RE = re.compile(
|
|
r"(?:git\s+merge(?:\s+--no-commit|\s+--no-ff)?|git\s+rebase|git\s+merge-tree)",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
_CLEANUP_CLAIM_RE = re.compile(
|
|
r"worktrees?\s+(?:were\s+)?(?:removed|cleaned|deleted)|"
|
|
r"cleanup\s*:\s*(?!none)\S",
|
|
re.IGNORECASE,
|
|
)
|
|
_CLEANUP_EVIDENCE_RE = re.compile(r"git\s+worktree\s+list", re.IGNORECASE)
|
|
|
|
|
|
def assess_proof_backed_handoff_report(report_text: str) -> dict[str, Any]:
|
|
"""Fail closed on proof-sensitive claims that lack explicit evidence (#395)."""
|
|
text = report_text or ""
|
|
reasons: list[str] = []
|
|
|
|
if _INVENTORY_CLAIM_RE.search(text):
|
|
if _PAGE_SIZE_ASSUMPTION_RE.search(text):
|
|
reasons.append(
|
|
"inventory completeness claimed from a page size assumption; "
|
|
"explicit pagination metadata (has_more=false / is_final_page=true / "
|
|
"inventory_complete=true / total count) is required"
|
|
)
|
|
elif not _PAGINATION_METADATA_RE.search(text):
|
|
reasons.append(
|
|
"inventory completeness claimed without explicit pagination "
|
|
"metadata proof (has_more=false, is_final_page=true, "
|
|
"inventory_complete=true, or total count)"
|
|
)
|
|
|
|
for match in _SKIPPED_PR_RE.finditer(text):
|
|
pr_number = match.group(1)
|
|
window = text[match.start():match.start() + 400]
|
|
if not _SKIP_EVIDENCE_RE.search(window):
|
|
reasons.append(
|
|
f"skipped PR #{pr_number} claim has no explicit command/tool "
|
|
"proof or structured mergeability/blocker evidence"
|
|
)
|
|
|
|
if _BASELINE_CLAIM_RE.search(text):
|
|
missing = [
|
|
name for name, pattern in _BASELINE_REQUIRED if not pattern.search(text)
|
|
]
|
|
if missing:
|
|
reasons.append(
|
|
"baseline validation claim missing required proof fields: "
|
|
+ ", ".join(missing)
|
|
)
|
|
|
|
if _INTEGRATION_CLAIM_RE.search(text) and not _INTEGRATION_EVIDENCE_RE.search(text):
|
|
reasons.append(
|
|
"master-integration claim lacks the exact merge/rebase/simulation "
|
|
"command and result"
|
|
)
|
|
|
|
if _CLEANUP_CLAIM_RE.search(text) and not _CLEANUP_EVIDENCE_RE.search(text):
|
|
reasons.append(
|
|
"cleanup claim lacks final `git worktree list` (or equivalent) proof "
|
|
"that session-owned worktrees were removed"
|
|
)
|
|
|
|
return {
|
|
"proven": not reasons,
|
|
"block": bool(reasons),
|
|
"reasons": reasons,
|
|
"provenance_classes": PROOF_PROVENANCE_CLASSES,
|
|
}
|