Adds mutation-capability table validation so review_pr cannot authorize merge or delete mutations without their own exact capability rows. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
130 lines
5.0 KiB
Python
130 lines
5.0 KiB
Python
"""Exact per-mutation capability proof verifier for reviewer reports (#405).
|
|
|
|
A reviewer final report may prove ``review_pr`` capability and then also merge
|
|
a PR or delete a remote branch. Merge and branch deletion are separate
|
|
mutations that require their own exact capability proof — a nearby capability
|
|
must never authorize a different operation. This verifier requires a
|
|
mutation-capability table pairing every performed mutation with the exact
|
|
task/permission resolved *before* that mutation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
# Mutations this verifier tracks, with the exact capability tokens that
|
|
# authorize each. A row for the mutation must cite one of its own tokens;
|
|
# tokens from a different mutation (a "nearby capability") never count.
|
|
_REVIEW_TOKENS = ("review_pr", "gitea.pr.review", "gitea.pr.approve",
|
|
"gitea.pr.request_changes", "request_changes_pr", "approve_pr")
|
|
_MERGE_TOKENS = ("merge_pr", "gitea.pr.merge")
|
|
_DELETE_TOKENS = ("delete_branch", "gitea.branch.delete")
|
|
|
|
# Detect that a mutation was actually performed (not merely mentioned as a
|
|
# non-goal or skipped).
|
|
_MERGE_PERFORMED = re.compile(
|
|
r"(?:gitea_merge_pr\b(?![^\n]*\b(?:not called|skipped|blocked)\b)|"
|
|
r"^\s*[-*]?\s*merge result\s*:\s*merged\b|"
|
|
r"\bpr merged\b|\bmerge commit\s*(?:sha)?\s*[:=]?\s*[0-9a-f]{7,})",
|
|
re.IGNORECASE | re.MULTILINE,
|
|
)
|
|
_DELETE_PERFORMED = re.compile(
|
|
r"(?:gitea_delete_branch\b(?![^\n]*\b(?:not called|skipped|blocked)\b)|"
|
|
r"^\s*[-*]?\s*(?:remote )?branch deleted\s*:|"
|
|
r"\bdeleted (?:the )?(?:remote )?branch\b|"
|
|
r"^\s*[-*]?\s*branch deletion\s*:\s*(?!skipped|none|not)\S)",
|
|
re.IGNORECASE | re.MULTILINE,
|
|
)
|
|
_REVIEW_PERFORMED = re.compile(
|
|
r"(?:gitea_submit_pr_review\b|gitea_mark_final_review_decision\b|"
|
|
r"^\s*[-*]?\s*review (?:decision|verdict|mutation)\s*:\s*"
|
|
r"(?:approved|request[_ ]changes)\b|\breview submitted\b)",
|
|
re.IGNORECASE | re.MULTILINE,
|
|
)
|
|
|
|
# Post-hoc proof: capability resolved *after* the mutation is never valid.
|
|
_POST_HOC = re.compile(
|
|
r"capabilit(?:y|ies)\s+(?:resolved|proven|checked)\s+(?:after|post[- ])\s*"
|
|
r"(?:the\s+)?(?:merge|deletion|delete|mutation|review)",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
# The report must carry an explicit mutation-capability table.
|
|
_TABLE_MARKER = re.compile(
|
|
r"mutation[- ]capability(?:\s+table)?|capability[- ]per[- ]mutation",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
|
|
def _tokens_present(text: str, tokens: tuple[str, ...]) -> bool:
|
|
low = text.lower()
|
|
return any(tok.lower() in low for tok in tokens)
|
|
|
|
|
|
def assess_mutation_capability_proof(report_text: str) -> dict:
|
|
"""Validate exact per-mutation capability proof in a reviewer report.
|
|
|
|
Returns ``{proven, block, reasons, safe_next_action}``. A report that
|
|
performs no mutation beyond an ordinary review passes only when its
|
|
review capability is cited; merge/delete each demand their own exact
|
|
capability row. Fail closed on nearby-capability substitution, a
|
|
missing table, missing rows, or post-hoc proof.
|
|
"""
|
|
text = report_text or ""
|
|
reasons: list[str] = []
|
|
|
|
merged = bool(_MERGE_PERFORMED.search(text))
|
|
deleted = bool(_DELETE_PERFORMED.search(text))
|
|
reviewed = bool(_REVIEW_PERFORMED.search(text))
|
|
|
|
extra_mutation = merged or deleted
|
|
|
|
if _POST_HOC.search(text):
|
|
reasons.append(
|
|
"capability proof recorded after the mutation; exact capability "
|
|
"must be resolved before each mutation"
|
|
)
|
|
|
|
# A review-only report needs its review capability cited; no table required.
|
|
if reviewed and not _tokens_present(text, _REVIEW_TOKENS):
|
|
reasons.append(
|
|
"review mutation performed without exact review capability proof "
|
|
"(review_pr / gitea.pr.review)"
|
|
)
|
|
|
|
if extra_mutation and not _TABLE_MARKER.search(text):
|
|
reasons.append(
|
|
"mutation beyond review performed without a mutation-capability "
|
|
"table (mutation, exact task/capability, result, order-before)"
|
|
)
|
|
|
|
if merged:
|
|
if not _tokens_present(text, _MERGE_TOKENS):
|
|
reasons.append(
|
|
"merge performed without exact merge capability proof "
|
|
"(merge_pr / gitea.pr.merge); nearby review_pr does not "
|
|
"authorize merge"
|
|
)
|
|
|
|
if deleted:
|
|
if not _tokens_present(text, _DELETE_TOKENS):
|
|
reasons.append(
|
|
"branch deletion performed without exact delete capability "
|
|
"proof (delete_branch / gitea.branch.delete); nearby "
|
|
"merge_pr does not authorize branch deletion"
|
|
)
|
|
|
|
proven = not reasons
|
|
return {
|
|
"proven": proven,
|
|
"block": not proven,
|
|
"reasons": reasons,
|
|
"safe_next_action": (
|
|
"proceed"
|
|
if proven
|
|
else "add a mutation-capability table with the exact resolved "
|
|
"task/permission and pre-mutation order for every mutation; "
|
|
"skip any mutation whose exact capability is unproven"
|
|
),
|
|
}
|