fix(capability-map): restore reviewer role on 10 review tasks (break-glass, incident #722)

EXCEPTIONAL OPERATOR-AUTHORIZED BREAK-GLASS RECOVERY - incident #722.

Commit 970e68b ("fix: adopt_merger_pr_lease requires merger role") was an
unreviewed direct push that flipped 11 task_capability_map.py entries to
role="merger" instead of the intended 1. Merger profiles forbid the review
permissions, so no configured profile could resolve review_pr / approve_pr /
request_changes_pr (matching_configured_profile was empty repository-wide)
and no fix PR could be formally reviewed. The operator resolved the catch-22
with a one-time break-glass authorization recorded on issue #722
(comment 11918); this commit is the authorized minimum and nothing more.

Changes:
- task_capability_map.py restored to blob 02826af (the preserved intended
  correction, local commit c071f8a1): the 10 regressive entries return to
  role="reviewer"; adopt_merger_pr_lease keeps role="merger"; merge_pr is
  untouched.
- tests/test_task_capability_role_invariants.py added (#723 AC1/AC2):
  profile-coverage invariant for formal review tasks, map/router agreement,
  merger-only adopt_merger_pr_lease and merge_pr, merger profiles cannot
  hold review permissions.

Validation: focused role-mapping tests 72 passed (+22 subtests); full suite
2900 passed, 6 skipped, 1 known warning, 195 subtests.

Recovery step 1 for incident #722; code-defect follow-ups tracked in #723.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EnHNSVQvJ8nCk7KZ9kL4Ym
This commit is contained in:
2026-07-16 18:38:17 -04:00
co-authored by Claude Opus 4.8
parent 293808b42d
commit ba7915452e
2 changed files with 215 additions and 10 deletions
+10 -10
View File
@@ -66,7 +66,7 @@ TASK_CAPABILITY_MAP: dict[str, dict[str, str]] = {
},
"review_pr": {
"permission": "gitea.pr.review",
"role": "merger",
"role": "reviewer",
},
"merge_pr": {
"permission": "gitea.pr.merge",
@@ -90,42 +90,42 @@ TASK_CAPABILITY_MAP: dict[str, dict[str, str]] = {
# Apply path posts lease release + audit comments (gitea.pr.comment).
"cleanup_obsolete_reviewer_comment_lease": {
"permission": "gitea.pr.comment",
"role": "merger",
"role": "reviewer",
},
"gitea_cleanup_obsolete_reviewer_comment_lease": {
"permission": "gitea.pr.comment",
"role": "merger",
"role": "reviewer",
},
"blind_pr_queue_review": {
"permission": "gitea.pr.review",
"role": "merger",
"role": "reviewer",
},
"pr_queue_cleanup": {
"permission": "gitea.pr.review",
"role": "merger",
"role": "reviewer",
},
"pr-queue-cleanup": {
"permission": "gitea.pr.review",
"role": "merger",
"role": "reviewer",
},
"request_changes_pr": {
"permission": "gitea.pr.request_changes",
"role": "merger",
"role": "reviewer",
},
"approve_pr": {
"permission": "gitea.pr.approve",
"role": "merger",
"role": "reviewer",
},
# #594: clear durable #332 decision lock only when last terminal PR is
# already merged/closed (moot). Apply path requires reviewer review
# permission; assessment itself uses gitea.read inside the tool.
"cleanup_stale_review_decision_lock": {
"permission": "gitea.pr.review",
"role": "merger",
"role": "reviewer",
},
"gitea_cleanup_stale_review_decision_lock": {
"permission": "gitea.pr.review",
"role": "merger",
"role": "reviewer",
},
# #709: truthful absence-of-proof recovery (server-side auth + record + consume).
# Dedicated mutation capability — gitea.read is insufficient (review 434 F1).