Files
Gitea-Tools/reviewer_proof_backed_claims.py
T
sysadminandClaude Opus 4.8 e1e76f0420 feat: require proof-backed PR review handoff claims (Closes #395)
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]>
2026-07-07 10:30:32 -04:00

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,
}