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

Merged
sysadmin merged 2 commits from feat/issue-309-reconciler-close-capability into master 2026-07-07 08:57:05 -05:00
Owner

Summary

Closes #309.

Already-landed open PRs block the review queue: the already-landed gate correctly stops approval/merge (PR #278 case), but no task path could close the redundant PR, so every review run re-encountered it and stopped. This PR adds the dedicated reconciler close path: exact close capabilities under a new reconciler role, wrong-role routing, and a fail-closed close gate.

Changes

  • task_capability_map.py — new tasks reconcile_close_landed_prgitea.pr.close and reconcile_close_landed_issuegitea.issue.close, both under a dedicated reconciler role. The role grants no review/approve/request-changes/merge authority, and normal reviewer profiles keep no broad PR-close authority.

  • role_session_router.py — reconciler tasks route wrong_role_stop in author or reviewer sessions with a dedicated recovery message (WRONG_ROLE_RECONCILER_MSG); matching reconciler sessions route allowed_current_session. Reviewer/author routing untouched.

  • review_proofs.py — new assess_reconciler_close_gate:

    • CLOSE_ALLOWED only when every proof passes: PR live-verified open, candidate head SHA pinned (full 40-hex) and matching the live head, target branch freshly fetched with full SHA, head an ancestor of the target, exact gitea.pr.close proven.
    • NOT_LANDED_CLOSE_BLOCKED — non-landed PRs can never be closed through this path.
    • RECOVERY_HANDOFF_REQUIRED — ancestor proof complete but gitea.pr.close missing.
    • GATE_NOT_PROVEN — any missing/stale proof (closed PR state, changed head, missing target SHA, unchecked ancestry) fails closed.
    • Linked-issue close: skipped when already closed; allowed only for an open issue resolved by the landed commits with exact gitea.issue.close capability.
    • Every outcome returns review/approve/request_changes/merge allowed: False and the required final-report field list (identity/profile, close capability proof, PR live state, candidate head SHA, target branch SHA, ancestor proof, linked issue status, PR close result, issue close result, no review/merge confirmation).
  • tests/test_reconciler_close_gate.py — 17 tests: capability map entries, review-permission denial, wrong-role routing, and all gate paths (landed close, non-landed block, stale target, closed PR state, changed head, missing close capability, linked-issue already closed / open with and without capability, review-mutation denial across outcomes).

Acceptance criteria mapping (#309)

  • Already-landed PRs closable through the reconciler path after proof.
  • Non-landed PRs cannot be closed through the reconciler path.
  • Missing gitea.pr.close produces a recovery handoff.
  • Normal reviewer profile keeps no broad PR-close authority (separate role + task keys).
  • Tests cover already-landed PR, not-landed PR, stale target branch, missing close capability, linked issue already closed, linked issue open, and successful close path.

Scope note

Profile provisioning for a concrete prgs-reconciler profile (config/keychain) is #310/#304 territory and intentionally not included; this PR provides the capability map, routing, and gate those issues plug into. #306 duplicates this issue.

Validation

python -m pytest tests/ in the issue worktree (base c20a936): 1271 passed, 2 failed, 6 skipped, 20 subtests passed.

Both failures are pre-existing on the clean base with this change stashed: the #359 artifact-warning test (fix PR #364 open) and test_final_report_validator.py::TestReviewPrRules::test_clean_reviewer_report_passes, which currently fails on live master without this diff.

🤖 Generated with Claude Code

## Summary Closes #309. Already-landed open PRs block the review queue: the already-landed gate correctly stops approval/merge (PR #278 case), but no task path could close the redundant PR, so every review run re-encountered it and stopped. This PR adds the dedicated reconciler close path: exact close capabilities under a new `reconciler` role, wrong-role routing, and a fail-closed close gate. ## Changes - `task_capability_map.py` — new tasks `reconcile_close_landed_pr` → `gitea.pr.close` and `reconcile_close_landed_issue` → `gitea.issue.close`, both under a dedicated `reconciler` role. The role grants no review/approve/request-changes/merge authority, and normal reviewer profiles keep no broad PR-close authority. - `role_session_router.py` — reconciler tasks route `wrong_role_stop` in author or reviewer sessions with a dedicated recovery message (`WRONG_ROLE_RECONCILER_MSG`); matching reconciler sessions route `allowed_current_session`. Reviewer/author routing untouched. - `review_proofs.py` — new `assess_reconciler_close_gate`: - `CLOSE_ALLOWED` only when every proof passes: PR live-verified open, candidate head SHA pinned (full 40-hex) and matching the live head, target branch freshly fetched with full SHA, head an ancestor of the target, exact `gitea.pr.close` proven. - `NOT_LANDED_CLOSE_BLOCKED` — non-landed PRs can never be closed through this path. - `RECOVERY_HANDOFF_REQUIRED` — ancestor proof complete but `gitea.pr.close` missing. - `GATE_NOT_PROVEN` — any missing/stale proof (closed PR state, changed head, missing target SHA, unchecked ancestry) fails closed. - Linked-issue close: skipped when already closed; allowed only for an open issue resolved by the landed commits with exact `gitea.issue.close` capability. - Every outcome returns `review/approve/request_changes/merge allowed: False` and the required final-report field list (identity/profile, close capability proof, PR live state, candidate head SHA, target branch SHA, ancestor proof, linked issue status, PR close result, issue close result, no review/merge confirmation). - `tests/test_reconciler_close_gate.py` — 17 tests: capability map entries, review-permission denial, wrong-role routing, and all gate paths (landed close, non-landed block, stale target, closed PR state, changed head, missing close capability, linked-issue already closed / open with and without capability, review-mutation denial across outcomes). ## Acceptance criteria mapping (#309) - [x] Already-landed PRs closable through the reconciler path after proof. - [x] Non-landed PRs cannot be closed through the reconciler path. - [x] Missing `gitea.pr.close` produces a recovery handoff. - [x] Normal reviewer profile keeps no broad PR-close authority (separate role + task keys). - [x] Tests cover already-landed PR, not-landed PR, stale target branch, missing close capability, linked issue already closed, linked issue open, and successful close path. ## Scope note Profile provisioning for a concrete `prgs-reconciler` profile (config/keychain) is #310/#304 territory and intentionally not included; this PR provides the capability map, routing, and gate those issues plug into. #306 duplicates this issue. ## Validation `python -m pytest tests/` in the issue worktree (base c20a936): **1271 passed, 2 failed, 6 skipped, 20 subtests passed**. Both failures are pre-existing on the clean base with this change stashed: the #359 artifact-warning test (fix PR #364 open) and `test_final_report_validator.py::TestReviewPrRules::test_clean_reviewer_report_passes`, which currently fails on live master without this diff. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jcwalker3 added 1 commit 2026-07-07 08:16:06 -05:00
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]>
jcwalker3 added 1 commit 2026-07-07 08:38:32 -05:00
sysadmin approved these changes 2026-07-07 08:56:51 -05:00
sysadmin left a comment
Owner

All tests pass successfully after merging master (1405 passed, 1 failed [pre-existing]). Reviewed the implementation of assess_reconciler_close_gate in review_proofs.py, task/role session mappings in task_capability_map.py and role_session_router.py, and the test suite in tests/test_reconciler_close_gate.py. It correctly introduces the dedicated reconciler role tasks to close already-landed open PRs and issues securely without broad reviewer permissions. Verified that the remaining failure is pre-existing on master.

All tests pass successfully after merging master (1405 passed, 1 failed [pre-existing]). Reviewed the implementation of `assess_reconciler_close_gate` in `review_proofs.py`, task/role session mappings in `task_capability_map.py` and `role_session_router.py`, and the test suite in `tests/test_reconciler_close_gate.py`. It correctly introduces the dedicated reconciler role tasks to close already-landed open PRs and issues securely without broad reviewer permissions. Verified that the remaining failure is pre-existing on master.
sysadmin merged commit 0bb6cae95a into master 2026-07-07 08:57:05 -05:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#379