feat: dedicated reconciler capability for closing landed PRs (Closes #309)

Already-landed open PRs block the review queue: the already-landed gate
correctly stops approval/merge, but no task path could close the
redundant PR. Add the reconciler close path:

- task_capability_map: new reconcile_close_landed_pr (gitea.pr.close)
  and reconcile_close_landed_issue (gitea.issue.close) tasks under a
  dedicated "reconciler" role. Exact close capabilities only — the
  role grants no review, approve, request-changes, or merge authority,
  and normal reviewer profiles keep no broad PR-close authority.
- role_session_router: reconciler tasks route wrong_role_stop in
  author/reviewer sessions with a dedicated recovery message; matching
  reconciler sessions route allowed_current_session.
- review_proofs.assess_reconciler_close_gate: PR close allowed only
  when every proof passes — PR live-verified open, candidate head SHA
  pinned and matching the live head, target branch freshly fetched
  with a full SHA, head an ancestor of the target, exact
  gitea.pr.close proven. Non-landed PRs return
  NOT_LANDED_CLOSE_BLOCKED; missing close capability returns
  RECOVERY_HANDOFF_REQUIRED; incomplete proof fails closed as
  GATE_NOT_PROVEN. Linked-issue close is skipped when the issue is
  already closed and allowed only for an open issue resolved by the
  landed commits with exact gitea.issue.close capability. The gate
  also returns the required final-report field list.

17 new tests cover the capability map, wrong-role routing, and every
gate path (landed close, non-landed block, stale target, changed head,
missing capabilities, linked-issue variants, review-mutation denial).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-07 09:15:13 -04:00
co-authored by Claude Opus 4.8
parent c20a93602d
commit 958d470f98
4 changed files with 389 additions and 0 deletions
+10
View File
@@ -96,6 +96,16 @@ TASK_CAPABILITY_MAP: dict[str, dict[str, str]] = {
"permission": "gitea.read",
"role": "author",
},
# #309: dedicated reconciler path for already-landed open PRs. Exact
# close capabilities only — never review/approve/request_changes/merge.
"reconcile_close_landed_pr": {
"permission": "gitea.pr.close",
"role": "reconciler",
},
"reconcile_close_landed_issue": {
"permission": "gitea.issue.close",
"role": "reconciler",
},
}
# Issue-mutating MCP tools and their resolver task keys.