fix: preserve actual reconciler role for #274/#475 role exemptions during comment_issue preflight (Closes #540) #541

Merged
sysadmin merged 1 commits from feat/issue-540-reconciler-comment-role-poison into master 2026-07-08 21:09:34 -05:00
Owner

Closes #540.

Root cause

resolve_task_capability("comment_issue") stamps _preflight_resolved_role = "author" because comment_issue.required_role_kind = author. _effective_workspace_role() prefers that stamp over the active profile role, so a genuine prgs-reconciler session is classified as an author inside:

  • _enforce_branches_only_author_mutation (#274 branch-only mutation guard), and
  • _enforce_root_checkout_guard -> assess_root_checkout_guard (#475 root checkout guard).

Both then treated the reconciler as an author, blocking gitea_create_issue_comment from the control checkout even though the reconciler profile holds gitea.issue.comment.

Fix

Key the role exemptions off the actual active profile role as well as the effective workspace role, so the poisoned author task stamp cannot strip a non-author profile of its exemption:

  • New _actual_profile_role() — derives the workspace role from the active profile alone, never from _preflight_resolved_role.
  • _enforce_branches_only_author_mutation — exempt when either the effective role or the actual profile role is a non-author role.
  • _enforce_root_checkout_guard — passes actual_role; assess_root_checkout_guard gains an actual_role param and exempts a reconciler by resolved or actual role.

Preserved behavior:

  • Author blocking intact — an actual author profile classifies as author in both signals, so it stays blocked on a contaminated control checkout by #274/#475.
  • Merger strictness stays keyed on the resolved task role (not actual_role), so a merger operating from its clean branches/ workspace under a non-merge task is not over-tightened (a regression caught mid-implementation and corrected).

Files changed

  • gitea_mcp_server.py — add _actual_profile_role(); use actual-role exemption in _enforce_branches_only_author_mutation; pass actual_role from _enforce_root_checkout_guard.
  • root_checkout_guard.pyassess_root_checkout_guard gains actual_role; reconciler exemption honours resolved or actual role; merger strictness unchanged.
  • tests/test_issue_540_comment_role_poison.py — new regression suite.

Regression tests added

tests/test_issue_540_comment_role_poison.py:

  1. _actual_profile_role() returns the real role (reconciler/author/reviewer) even when _preflight_resolved_role="author", while _effective_workspace_role() stays poisoned (documents the mechanism).
  2. _enforce_branches_only_author_mutation exempts a reconciler under the poisoned author stamp and does not exempt an actual author.
  3. assess_root_checkout_guard — actual-reconciler exempt despite poisoned resolved author; actual-author still blocked on contaminated root; merger strictness keyed on resolved task role; actual_role="merger" under a non-merge task does not over-tighten; backward compatible without actual_role.
  4. Integration through gitea_create_issue_comment: reconciler comment from the control checkout succeeds; author comment from the control checkout is blocked.

Validation results

  • New suite: tests/test_issue_540_comment_role_poison.py — 12 passed.
  • Guard and preflight suites — pass.
  • Full suite: 1924 passed, 6 skipped, 2 failed. The 2 failures (test_verify_preflight_blocks_control_checkout_with_test_porcelain, test_pr487_style_merge_binds_clean_merger_workspace) are pre-existing on clean master — verified by stashing this change and re-running, they fail identically. They are outside the scope of #540 and are not touched by this PR.
  • git diff --check — clean.

Mutation ledger

  • Gitea: opened this PR (closes #540); locked issue #540 for durable tracking. No merges, no issue/PR comments posted, no labels, no state changes.
  • Local: created git worktree branches/issue-540-reconciler-comment-role-poison off master (cfce823) for edits, plus a master-pinned branches/issue-540-lock-base for the durable lock. Committed + pushed branch feat/issue-540-reconciler-comment-role-poison (head 9438855). Root checkout left clean on master. branches/mcp-author-worktree (foreign staged work) untouched.

Follow-up

The PR #527 / issue #515 canonical reconciler audit comment remains blocked until #540 lands. After this merges, a reconciler session should retry only that single blocked canonical audit comment — no other reconciliation action is outstanding (post-merge moot lease cleanup already proven a no-op).

Closes #540. ## Root cause `resolve_task_capability("comment_issue")` stamps `_preflight_resolved_role = "author"` because `comment_issue.required_role_kind = author`. `_effective_workspace_role()` prefers that stamp over the active profile role, so a genuine `prgs-reconciler` session is classified as an author inside: * `_enforce_branches_only_author_mutation` (#274 branch-only mutation guard), and * `_enforce_root_checkout_guard` -> `assess_root_checkout_guard` (#475 root checkout guard). Both then treated the reconciler as an author, blocking `gitea_create_issue_comment` from the control checkout even though the reconciler profile holds `gitea.issue.comment`. ## Fix Key the role exemptions off the **actual active profile role** as well as the effective workspace role, so the poisoned `author` task stamp cannot strip a non-author profile of its exemption: * New `_actual_profile_role()` — derives the workspace role from the active profile alone, never from `_preflight_resolved_role`. * `_enforce_branches_only_author_mutation` — exempt when **either** the effective role **or** the actual profile role is a non-author role. * `_enforce_root_checkout_guard` — passes `actual_role`; `assess_root_checkout_guard` gains an `actual_role` param and exempts a reconciler by resolved **or** actual role. Preserved behavior: * Author blocking intact — an actual author profile classifies as `author` in both signals, so it stays blocked on a contaminated control checkout by #274/#475. * Merger strictness stays keyed on the resolved **task** role (not `actual_role`), so a merger operating from its clean `branches/` workspace under a non-merge task is not over-tightened (a regression caught mid-implementation and corrected). ## Files changed * `gitea_mcp_server.py` — add `_actual_profile_role()`; use actual-role exemption in `_enforce_branches_only_author_mutation`; pass `actual_role` from `_enforce_root_checkout_guard`. * `root_checkout_guard.py` — `assess_root_checkout_guard` gains `actual_role`; reconciler exemption honours resolved **or** actual role; merger strictness unchanged. * `tests/test_issue_540_comment_role_poison.py` — new regression suite. ## Regression tests added `tests/test_issue_540_comment_role_poison.py`: 1. `_actual_profile_role()` returns the real role (reconciler/author/reviewer) even when `_preflight_resolved_role="author"`, while `_effective_workspace_role()` stays poisoned (documents the mechanism). 2. `_enforce_branches_only_author_mutation` exempts a reconciler under the poisoned author stamp and does **not** exempt an actual author. 3. `assess_root_checkout_guard` — actual-reconciler exempt despite poisoned resolved `author`; actual-author still blocked on contaminated root; merger strictness keyed on resolved task role; `actual_role="merger"` under a non-merge task does not over-tighten; backward compatible without `actual_role`. 4. Integration through `gitea_create_issue_comment`: reconciler comment from the control checkout succeeds; author comment from the control checkout is blocked. ## Validation results * New suite: `tests/test_issue_540_comment_role_poison.py` — 12 passed. * Guard and preflight suites — pass. * Full suite: **1924 passed, 6 skipped, 2 failed**. The 2 failures (`test_verify_preflight_blocks_control_checkout_with_test_porcelain`, `test_pr487_style_merge_binds_clean_merger_workspace`) are **pre-existing on clean master** — verified by stashing this change and re-running, they fail identically. They are outside the scope of #540 and are not touched by this PR. * `git diff --check` — clean. ## Mutation ledger * Gitea: opened this PR (closes #540); locked issue #540 for durable tracking. No merges, no issue/PR comments posted, no labels, no state changes. * Local: created `git worktree` `branches/issue-540-reconciler-comment-role-poison` off `master` (cfce823) for edits, plus a master-pinned `branches/issue-540-lock-base` for the durable lock. Committed + pushed branch `feat/issue-540-reconciler-comment-role-poison` (head 9438855). Root checkout left clean on `master`. `branches/mcp-author-worktree` (foreign staged work) untouched. ## Follow-up The PR #527 / issue #515 canonical **reconciler audit comment remains blocked** until #540 lands. After this merges, a reconciler session should retry **only** that single blocked canonical audit comment — no other reconciliation action is outstanding (post-merge moot lease cleanup already proven a no-op).
jcwalker3 added 1 commit 2026-07-08 11:07:44 -05:00
resolve_task_capability("comment_issue") stamps _preflight_resolved_role
= "author" because comment_issue.required_role_kind = author.
_effective_workspace_role() prefers that stamp, so a genuine
prgs-reconciler session was classified as an author inside the #274
branch-only mutation guard and the #475 root checkout guard, blocking
gitea_create_issue_comment from the control checkout.

Fix keys the role exemptions off the actual active profile role as well
as the effective workspace role:

- Add _actual_profile_role(): derives the workspace role from the active
  profile alone, never from _preflight_resolved_role.
- _enforce_branches_only_author_mutation: exempt when EITHER the
  effective role OR the actual profile role is a non-author role.
- _enforce_root_checkout_guard: pass actual_role; assess_root_checkout_guard
  now exempts a reconciler by resolved OR actual role.

Author blocking is preserved: an actual author profile classifies as
author in both signals, so it stays blocked on a contaminated control
checkout. Merger strictness stays keyed on the resolved task role so a
merger operating from its clean branches/ workspace under a non-merge
task is not over-tightened.

Regression tests (tests/test_issue_540_comment_role_poison.py) cover the
reconciler comment path, author-blocking path, reviewer/merger/reconciler
role classification under a poisoned author stamp, and the root guard
actual_role exemption.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #541
issue: #540
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 71026-a73bae0ae00d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr541
phase: claimed
candidate_head: 9438855fdd
target_branch: master
target_branch_sha: cfce823dd7
last_activity: 2026-07-08T16:50:05Z
expires_at: 2026-07-08T18:50:05Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #541 issue: #540 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 71026-a73bae0ae00d worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr541 phase: claimed candidate_head: 9438855fdd135392b8b8369929efe03b32d9da31 target_branch: master target_branch_sha: cfce823dd7343bbafecbff82d807a27884c60b6c last_activity: 2026-07-08T16:50:05Z expires_at: 2026-07-08T18:50:05Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #541
issue: #540
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 71026-a73bae0ae00d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr541
phase: claimed
candidate_head: 9438855fdd
target_branch: master
target_branch_sha: cfce823dd7
last_activity: 2026-07-08T16:54:05Z
expires_at: 2026-07-08T18:54:05Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #541 issue: #540 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 71026-a73bae0ae00d worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr541 phase: claimed candidate_head: 9438855fdd135392b8b8369929efe03b32d9da31 target_branch: master target_branch_sha: cfce823dd7343bbafecbff82d807a27884c60b6c last_activity: 2026-07-08T16:54:05Z expires_at: 2026-07-08T18:54:05Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #541
issue: #540
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 34817-3c5dd715caff
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr541-215611
phase: claimed
candidate_head: 9438855fdd
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T01:57:03Z
expires_at: 2026-07-09T03:57:03Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #541 issue: #540 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 34817-3c5dd715caff worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr541-215611 phase: claimed candidate_head: 9438855fdd135392b8b8369929efe03b32d9da31 target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T01:57:03Z expires_at: 2026-07-09T03:57:03Z blocker: none
sysadmin approved these changes 2026-07-08 20:57:24 -05:00
sysadmin left a comment
Owner

Independent review: APPROVE (PR #541 / Issue #540)

Scope

Fix role-poison from comment_issue preflight stamping _preflight_resolved_role=author, which defeated reconciler exemptions on #274 branch-only and #475 root-checkout guards.

Files (domain-restricted)

  • gitea_mcp_server.py_actual_profile_role(); #274 exemption OR on actual profile role; pass actual_role into root guard
  • root_checkout_guard.py — reconciler exempt if resolved or actual role is reconciler; merger strictness stays on resolved task role
  • tests/test_issue_540_comment_role_poison.py — new regression suite

Validation (isolated worktree branches/review-pr541-215611)

  • Head pinned: 9438855fdd135392b8b8369929efe03b32d9da31
  • Base: master (live tip at lease time 18e7d7b); Gitea mergeable: true
  • git diff --check prgs/master...HEAD: clean
  • Targeted: test_issue_540_comment_role_poison + test_root_checkout_guard23 passed
  • Full suite: 1924 passed, 6 skipped, 2 failed
  • Failures pre-existing (not introduced by this PR):
    • test_verify_preflight_blocks_control_checkout_with_test_porcelain
    • test_pr487_style_merge_binds_clean_merger_workspace

Design notes reviewed

  • Author profiles still classify as author in both signals → author blocking intact
  • Merger strictness intentionally not widened via actual_role (avoids over-tightening non-merge tasks from merger workspace)

Verdict

APPROVE. Ready for independent merger.

LLM-Role: reviewer
Authenticated-Gitea-User: sysadmin
MCP-Profile: prgs-reviewer
Self-merge: no

## Independent review: APPROVE (PR #541 / Issue #540) ### Scope Fix role-poison from `comment_issue` preflight stamping `_preflight_resolved_role=author`, which defeated reconciler exemptions on #274 branch-only and #475 root-checkout guards. ### Files (domain-restricted) - `gitea_mcp_server.py` — `_actual_profile_role()`; #274 exemption OR on actual profile role; pass `actual_role` into root guard - `root_checkout_guard.py` — reconciler exempt if resolved **or** actual role is reconciler; merger strictness stays on resolved task role - `tests/test_issue_540_comment_role_poison.py` — new regression suite ### Validation (isolated worktree `branches/review-pr541-215611`) - Head pinned: `9438855fdd135392b8b8369929efe03b32d9da31` - Base: `master` (live tip at lease time `18e7d7b`); Gitea mergeable: true - `git diff --check prgs/master...HEAD`: clean - Targeted: `test_issue_540_comment_role_poison` + `test_root_checkout_guard` — **23 passed** - Full suite: **1924 passed, 6 skipped, 2 failed** - Failures pre-existing (not introduced by this PR): - `test_verify_preflight_blocks_control_checkout_with_test_porcelain` - `test_pr487_style_merge_binds_clean_merger_workspace` ### Design notes reviewed - Author profiles still classify as author in both signals → author blocking intact - Merger strictness intentionally **not** widened via `actual_role` (avoids over-tightening non-merge tasks from merger workspace) ### Verdict **APPROVE.** Ready for independent merger. LLM-Role: reviewer Authenticated-Gitea-User: sysadmin MCP-Profile: prgs-reviewer Self-merge: no
Owner

adopted_at: 2026-07-09T02:09:23Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 34817-3c5dd715caff
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 7317
adoption_reason: merger-handoff-approved-head

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #541
issue: #540
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 34819-e70c03aa27d4
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr541
phase: adopted
candidate_head: 9438855fdd
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:09:23Z
expires_at: 2026-07-09T04:09:23Z
blocker: none

<!-- mcp-review-lease-adoption:v1 --> adopted_at: 2026-07-09T02:09:23Z adopted_by_identity: sysadmin adopted_by_profile: prgs-merger adopted_from_session_id: 34817-3c5dd715caff adopted_from_profile: prgs-reviewer adopted_from_reviewer_identity: sysadmin adopted_from_comment_id: 7317 adoption_reason: merger-handoff-approved-head <!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #541 issue: #540 reviewer_identity: sysadmin profile: prgs-merger session_id: 34819-e70c03aa27d4 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr541 phase: adopted candidate_head: 9438855fdd135392b8b8369929efe03b32d9da31 target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:09:23Z expires_at: 2026-07-09T04:09:23Z blocker: none
sysadmin merged commit 8db26c9a15 into master 2026-07-08 21:09:34 -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#541