fix: preserve actual reconciler role for #274/#475 role exemptions during comment_issue preflight (Closes #540)
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]>
This commit is contained in:
+17
-2
@@ -58,15 +58,30 @@ def assess_root_checkout_guard(
|
||||
porcelain_status: str,
|
||||
remote_master_sha: str | None,
|
||||
resolved_role: str | None = None,
|
||||
actual_role: str | None = None,
|
||||
) -> dict:
|
||||
"""Fail closed when the control checkout is not clean master/prgs/master."""
|
||||
"""Fail closed when the control checkout is not clean master/prgs/master.
|
||||
|
||||
``resolved_role`` is the preflight-resolved *task* role and ``actual_role``
|
||||
is the *active profile* role (#540). The reconciler exemption honours either
|
||||
signal so a ``comment_issue`` preflight (which stamps the task role as
|
||||
``author``) cannot strip a genuine reconciler of its exemption. An actual
|
||||
author profile classifies as ``author`` in both signals, so author blocking
|
||||
on a contaminated control checkout is preserved.
|
||||
|
||||
Merger *strictness* (a merger must not be auto-exempted by working from a
|
||||
``branches/`` worktree) stays keyed on the resolved task role: merge
|
||||
operations resolve their own task role, and widening the merger test with
|
||||
``actual_role`` would wrongly subject a merger operating from its clean
|
||||
workspace under a non-merge task to full control-checkout checks.
|
||||
"""
|
||||
reasons: list[str] = []
|
||||
root = os.path.realpath(canonical_repo_root)
|
||||
workspace = os.path.realpath(workspace_path)
|
||||
branch = (current_branch or "").strip()
|
||||
dirty_files = parse_dirty_tracked_files(porcelain_status)
|
||||
|
||||
if resolved_role == "reconciler":
|
||||
if resolved_role == "reconciler" or actual_role == "reconciler":
|
||||
return _assessment(True, [], root, workspace, branch, head_sha, dirty_files)
|
||||
|
||||
if resolved_role != "merger" and is_path_under_branches(workspace, root):
|
||||
|
||||
Reference in New Issue
Block a user