fix(auth): route delete_branch capability to the reconciler role (Closes #729)

Remote post-merge branch deletion was blocked because the capability
resolver classified delete_branch as an author-role task while
gitea.branch.delete is granted only to prgs-reconciler. No configured
profile could satisfy both the permission gate and the role gate, so
every deletion failed closed with performed=false.

Re-home delete_branch from the author role to the reconciler role in
both single-source-of-truth maps:
  - task_capability_map.TASK_CAPABILITY_MAP["delete_branch"].role
  - role_session_router: TASK_REQUIRED_ROLE + AUTHOR_TASKS/RECONCILER_TASKS

resolve_task_capability("delete_branch") now resolves to prgs-reconciler.
In gitea_delete_branch, the reconciler is now the delete-capable role and
performs raw deletion through the existing reconciliation-cleanup-phase
authorization gate (operator approval + safe_to_delete proof) plus the
preservation/protected guards; the prior reconciler->cleanup redirect is
removed as it would orphan that guarded flow. Author, reviewer, and
merger stay denied by the permission gate and the required-role gate.
gitea_cleanup_merged_pr_branch remains a separate reconciler-owned path
with independent merged/ancestry/ownership verification.

Tests: reconciler resolves + performs eligible deletion; author/reviewer/
merger denied even when holding the permission; protected/preservation
branches remain fail-closed; both role maps asserted in agreement.
Updated pre-existing tests that encoded the superseded author-delete /
#687 reconciler-redirect contract. Full suite: 3190 passed, 6 skipped,
1 pre-existing unrelated failure (test_issue_702 create_pr stale-binding).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01UracxyRHQw49rZBaexHdft
This commit is contained in:
2026-07-17 19:42:52 -04:00
co-authored by Claude Opus 4.8
parent 3e761206e5
commit 7eb4884658
7 changed files with 206 additions and 75 deletions
+6 -2
View File
@@ -76,7 +76,6 @@ AUTHOR_TASKS = frozenset({
"create_pr",
"comment_pr",
"address_pr_change_requests",
"delete_branch",
"work_issue",
"work-issue",
"reconcile_landed_pr",
@@ -84,6 +83,10 @@ AUTHOR_TASKS = frozenset({
RECONCILER_TASKS = frozenset({
"cleanup_merged_pr_branch",
# #729: delete_branch is reconciler-owned (gitea.branch.delete is granted
# only to the reconciler profile). Raw gitea_delete_branch redirects here to
# the guarded gitea_cleanup_merged_pr_branch path (#514/#687).
"delete_branch",
"reconcile_already_landed_pr",
"reconcile_already_landed",
"reconcile-landed-pr",
@@ -99,7 +102,8 @@ TASK_REQUIRED_ROLE = {
"create_pr": "author",
"comment_pr": "author",
"address_pr_change_requests": "author",
"delete_branch": "author",
# #729: reconciler-owned; see RECONCILER_TASKS and task_capability_map.
"delete_branch": "reconciler",
"review_pr": "reviewer",
"merge_pr": "merger",
"blind_pr_queue_review": "reviewer",