fix(author): gate bootstrap-lock recovery on the namespace mutation wall (#953)
Review 632 F1. `gitea_recover_incomplete_bootstrap_lock` writes the same durable author issue lock as `gitea_recover_dirty_orphaned_issue_worktree` but gated only on the reviewer-stop router check and the profile permission block. Every other author state-creating mutation carries a third gate, `_namespace_mutation_block`, and this tool was the outlier. The surviving gates did not cover the gap: the task's required permission is `gitea.issue.comment`, which every configured role holds, and `_ensure_matching_profile` returns a profile name both call sites discard, so it refuses nothing. A reviewer-bound session reached the exact-owner claimant comparison inside `assess_bootstrap_lock_recovery` and was refused there — one layer too late, with no namespace evaluation and no BLOCKED audit record of the attempt. Adding the call alone would have been inert. `check_author_mutation_namespace` routes through `role_session_router.required_role_for_task`, which reads `TASK_REQUIRED_ROLE` — not `task_capability_map` — and that table had no entry for this task, so the check short-circuited to "allowed" for every caller. The task is therefore registered in `TASK_REQUIRED_ROLE` and `AUTHOR_TASKS`, matching the role the capability map already records. The reviewer-namespace check alone is also insufficient for a task gated on `gitea.issue.comment`, since merger, controller, and reconciler profiles hold it too. `role_namespace_gate.check_author_role_kind` is added as an opt-in, additive wall requiring the active profile's derived role kind to be exactly `author`; `mixed` is refused rather than admitted. `_namespace_mutation_block` gains a keyword-only `author_role_exclusive` flag, default off, so the six pre-existing call sites are byte-for-byte unchanged. Refusals produce the standard structured denial (`namespace_block: true`, `mcp_namespace`) and the standard BLOCKED audit record, and no lock, lease, generation, branch, worktree, issue, or PR is touched. Valid `prgs-author` execution is unaffected. No reviewer permission is broadened and no existing role wall is weakened. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
+33
-2
@@ -5150,6 +5150,21 @@ def gitea_recover_incomplete_bootstrap_lock(
|
||||
if not ok:
|
||||
return _author_mutation_block(block_reasons)
|
||||
|
||||
# #953 F1: the namespace/session wall every author state-creating mutation
|
||||
# carries, and which this tool — the structural neighbour of
|
||||
# gitea_recover_dirty_orphaned_issue_worktree, writing the same durable
|
||||
# lock — was the only one to omit. Exact-owner claimant matching inside
|
||||
# assess_bootstrap_lock_recovery is a later layer, not a substitute: it
|
||||
# refuses one commit too late and leaves no BLOCKED audit record of the
|
||||
# attempt. author_role_exclusive is required here because this task is gated
|
||||
# on gitea.issue.comment, which merger, controller, and reconciler profiles
|
||||
# also hold.
|
||||
blocked = _namespace_mutation_block(
|
||||
task, remote=remote, author_role_exclusive=True
|
||||
)
|
||||
if blocked:
|
||||
return blocked
|
||||
|
||||
blocked = _profile_permission_block(
|
||||
task_capability_map.required_permission(task),
|
||||
issue_number=issue_number,
|
||||
@@ -15490,8 +15505,21 @@ def _profile_permission_block(required_operation: str, **extra_fields) -> dict |
|
||||
)
|
||||
|
||||
|
||||
def _namespace_mutation_block(mutation_task: str, **extra_fields) -> dict | None:
|
||||
"""Reviewer/author namespace alignment gate (#209)."""
|
||||
def _namespace_mutation_block(
|
||||
mutation_task: str,
|
||||
*,
|
||||
author_role_exclusive: bool = False,
|
||||
**extra_fields,
|
||||
) -> dict | None:
|
||||
"""Reviewer/author namespace alignment gate (#209).
|
||||
|
||||
``author_role_exclusive`` additionally requires the active profile's derived
|
||||
role kind to be exactly ``author`` (#953 F1). Off by default, so the six
|
||||
pre-existing call sites are unchanged. Tools whose required permission is
|
||||
``gitea.issue.comment`` — which every configured role holds — opt in, since
|
||||
the reviewer-namespace check alone would let a merger, controller, or
|
||||
reconciler session through to a durable author lock write.
|
||||
"""
|
||||
required_permission = task_capability_map.required_permission(mutation_task)
|
||||
required_role = task_capability_map.required_role(mutation_task)
|
||||
# #714: evaluate active profile only — never auto-switch.
|
||||
@@ -15513,6 +15541,9 @@ def _namespace_mutation_block(mutation_task: str, **extra_fields) -> dict | None
|
||||
}
|
||||
ok, reasons = role_namespace_gate.check_author_mutation_namespace(
|
||||
mutation_task, profile)
|
||||
if ok and author_role_exclusive:
|
||||
ok, reasons = role_namespace_gate.check_author_role_kind(
|
||||
mutation_task, profile)
|
||||
if ok:
|
||||
return None
|
||||
blocked = {
|
||||
|
||||
Reference in New Issue
Block a user