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