fix(mcp): enforce role capability invariants #766
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#766
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
task_capability_map.ROLE_EXCLUSIVE_TASKSworkspace_role_bindingfailures from review submission and merger-lease adoptionrole_filteredandpermission_onlyviewsCloses #723
Files changed
task_capability_map.pygitea_mcp_server.pytests/test_issue_723_role_stamp_and_submission.pytests/test_task_capability_role_invariants.pytests/test_resolve_task_capability.pyValidation
git diff --check: passedpython3 -m py_compilefor every changed Python file: passedfcf6981b1ba4d8d5072357ba13be0fdb6baa07ffKnown baseline failures
TestF1RecoveryBeforeTerminalProbe.test_removed_worktree_recovers_before_probeTestReconcilerSupersessionMcpTool.test_tool_posts_comment_and_closes_superseded_pr_issueBoth signatures match the pristine detached base and are outside the #723 diff.
Risk
The changes affect capability preflight state and role-filtered reporting. Coverage pins allowed/denied stamp behavior, unknown-task and lease aliases, structured verifier failures, profile-role matching, current-master role-exclusive completeness, and adjacent workspace/preflight invariants.
Worktree:
/Users/jasonwalker/Development/Gitea-Tools/branches/fix-issue-723-role-stamp-invariantsBranch:
fix/issue-723-role-stamp-invariantsCommit:
4b8a9219d85bc99b6abeebb4dc9db3e344bc27e5repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #766
issue: #723
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 9625-79d38f65031d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-766-4b8a921
phase: claimed
candidate_head:
4b8a9219d8target_branch: master
target_branch_sha:
fcf6981b1blast_activity: 2026-07-20T04:17:24Z
expires_at: 2026-07-20T04:27:24Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #766
issue: #723
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 9625-79d38f65031d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-766-4b8a921
phase: claimed
candidate_head:
4b8a9219d8target_branch: master
target_branch_sha:
fcf6981b1blast_activity: 2026-07-20T04:28:54Z
expires_at: 2026-07-20T04:38:54Z
blocker: none
Independent reviewer verdict: APPROVE at head
4b8a9219d85bc99b6abeebb4dc9db3e344bc27e5.Reviewer
sysadmin/prgs-revieweris distinct from PR authorjcwalker3. Review performed in a detached reviewer worktree at the exact PR head (branches/review-pr-766-4b8a921), independent of the author worktree.Acceptance criteria (issue #723)
TestRoleExclusiveSetIntegrity.test_every_role_exclusive_task_has_a_satisfying_profilederives the required role per task and asserts the canonical role-profile shape satisfies both permission and role.test_every_role_exclusive_task_exists_in_capability_mappins the set againstTASK_CAPABILITY_MAP. Verified independently: both are derived checks, not literal comparisons.REVIEWER_TASKS/MERGER_TASKSrole-agreement tests remain, andtest_formal_review_tasks_are_role_exclusiveadds the containment invariant._clear_resolved_capability_stamp()runs at resolver entry before any fallible work; the mid-resolver call is nowrecord_preflight_check("capability")with no role/task, and the authority stamp is recorded only in the finalelsebranch when neitherstop_requirednorrestart_requiredholds. I traced lines 17386-17843: every return between the entry clear and the final stamp is a denial path, and the allow path has a single exit. Confirmedrecord_preflight_checkonly assigns_preflight_resolved_role/_taskwhen the arguments are truthy, so the entry clear is what actually removes stale authority. Confirmed_effective_workspace_role()falls back to the permission-derived role when the stamp isNone, so clearing degrades to the true session role rather than to an unguarded state, andpreflight_task_matches(None, ...)returnsFalse._evaluate_pr_review_submissionandgitea_adopt_merger_pr_leasenow catchRuntimeErrorfrom_verify_role_mutation_workspaceand returnblocker_kind="workspace_role_binding"with redacted reasons. I enumerated the raise sites in_verify_role_mutation_workspaceplus_enforce_canonical_repository_root: all areRuntimeError, so the guarded blocks are fully covered. The early-return result shape is identical to the existingworkflow_blockersearly return (performed=False,would_perform=False, non-emptyreasons), and both callers return it unchanged, so no caller reinterprets it as success._redact(...)is applied to the exception text._build_runtime_task_capabilitiesnow takesactive_role_kindfrom_profile_role_kind(profile)— the same source the resolver uses — and appliesrole_exclusive and active_role_kind == required_role_kind, which is semantically identical to the resolver'srole_matches_current_session. Entries carryrole_exclusiveand an explicitcapability_viewofrole_filteredorpermission_only, so a caller that omits the role gets a labeled weaker view rather than an implied stronger authority.Independent validation
All commands run by the reviewer in
branches/review-pr-766-4b8a921at4b8a9219d85bc99b6abeebb4dc9db3e344bc27e5; worktree verified clean before and after.gitea_mcp_server.pyandtask_capability_map.pyrestored fromfcf6981b1ba4d8d5072357ba13be0fdb6baa07ffinto the reviewer worktree:tests/test_task_capability_role_invariants.pyfails collection withImportError: cannot import name 'ROLE_EXCLUSIVE_TASKS', andtests/test_issue_723_role_stamp_and_submission.pygives 7 failed / 4 passed. This reproduces the author's stated red proof exactly. Reviewer worktree restored to the PR head afterward and re-verified clean.tests/test_issue_723_role_stamp_and_submission.py tests/test_task_capability_role_invariants.py tests/test_resolve_task_capability.pygives 48 passed, 78 subtests passed.-k "runtime_context or preflight or capability or role or submit or submission or merger_lease or reviewer_lease or profile"gives 769 passed, 241 subtests passed, 0 failed.TestF1RecoveryBeforeTerminalProbe.test_removed_worktree_recovers_before_probeandTestReconcilerSupersessionMcpTool.test_tool_posts_comment_and_closes_superseded_pr_issue— were re-run in a separate detached worktree at pristine basefcf6981b1ba4d8d5072357ba13be0fdb6baa07ffand fail identically there. They are pre-existing on master and outside this diff. That temporary baseline worktree was removed after use.Sync state at verdict time: head
4b8a9219d85bc99b6abeebb4dc9db3e344bc27e5, base headfcf6981b1ba4d8d5072357ba13be0fdb6baa07ff,commits_behind=0,mergeable=true,has_conflicts=false, checks not required by live branch protection.Non-blocking observations (no change requested)
test_complete_current_role_exclusive_setcomparesROLE_EXCLUSIVE_TASKSagainst an identical literal in the test file. It is a change detector rather than a derived invariant, so it cannot detect a task that should be role-exclusive but was never in either copy. The derived tests alongside it carry the real invariant weight, so this is acceptable as an intentional pin against silent shrinkage.role_filteredlabel claims role filtering only and does not claim resolver equivalence, so this is honest; a future change that markets the runtime view as resolver-equivalent would need those blocks too.RuntimeErrorbroadly in the two submission paths could also classify an unrelated internalRuntimeErrorasworkspace_role_binding. It still fails closed and includes the original message, which is strictly better than the previous opaque failure mode, andtest_unexpected_verifier_failure_remains_fail_closedpins that non-RuntimeErrorexceptions still propagate.No blocking defects found. Scope is confined to issue #723; no unrelated behavior changes appear in the diff.
Canonical PR State
STATE:
approved
WHO_IS_NEXT:
merger
NEXT_ACTION:
An independent merger session using prgs-merger/sysadmin should acquire a merger PR lease, re-verify this approval at head
4b8a9219d8, and merge PR 766.NEXT_PROMPT:
Run one bounded merger phase for PR #766 in Scaled-Tech-Consulting/Gitea-Tools using prgs-merger/sysadmin. Verify identity, repository binding, namespace health, and master/runtime parity. Acquire the merger PR lease, pin head
4b8a9219d8, confirm the APPROVE review at that exact head is visible and undismissed with no blocking change requests, re-assess checks, commits behind, conflicts, and mergeability, then merge only through gitea_merge_pr with confirmation "MERGE PR 766". Post the canonical merger handoff and stop. Do not review, do not edit the branch, and do not delete branches or worktrees.WHAT_HAPPENED:
An independent reviewer validated PR 766 against the acceptance criteria of issue #723, reproduced the master red proof, ran focused, adjacent, and full-suite validation, confirmed the two remaining suite failures are pre-existing on the base commit, and posted an APPROVE verdict at the exact current head.
WHY:
All five acceptance criteria of issue #723 are implemented and covered by derived regression tests; the capability stamp is cleared at resolver entry and recorded only on the final allow decision, guarded workspace/role rejections now return structured fail-closed results instead of raising, and the runtime capability view applies the same role filter as the resolver or labels itself permission-only.
ISSUE:
#723
BASE:
master
HEAD:
fix/issue-723-role-stamp-invariants
HEAD_SHA:
4b8a9219d8REVIEW_STATUS:
approved
VALIDATION:
Red proof reproduced on master sources (7 failures plus a collection ImportError); focused 48 passed with 78 subtests; adjacent 769 passed with 241 subtests; full suite 3711 passed, 6 skipped, 491 subtests, 2 failures proven pre-existing at base
fcf6981b1b.BLOCKERS:
none
SUPERSEDES:
none
SUPERSEDED_BY:
none
MERGE_READY:
yes — commits_behind is 0, mergeable is true, no conflicts, checks are not required by live branch protection, and this APPROVE is recorded at the exact current head.
LAST_UPDATED_BY:
sysadmin / prgs-reviewer / 2026-07-20
NATIVE_REVIEW_PROOF: transport=native_mcp; tool=gitea_submit_pr_review; namespace=gitea-reviewer; profile=prgs-reviewer; identity=sysadmin; pinned_head=4b8a9219d85bc99b6abeebb4dc9db3e344bc27e5; reviewer_lease_comment_id=13068
[THREAD STATE LEDGER]
What is true now:
PR 766 is open at head
4b8a9219d8, base master atfcf6981b1b, 0 commits behind, mergeable with no conflicts, and status checks are not required by live branch protection. A reviewer PR lease is held by session 9625-79d38f65031d (current lease comment 13068; the first lease comment 13066 expired under the 10-minute sliding TTL while the full suite ran and was re-acquired by the same session). Server-side decision state: no server-side review decision existed on this PR before this submission; this comment is the approved review posted to Gitea. Local verdict/state: approve verdict prepared locally from an independent reviewer worktree at the pinned head, then submitted natively.What changed:
Server-side mutation ledger: two reviewer PR lease comments by the same reviewer session (ids 13066 and 13068) and one native approved review posted to Gitea at head
4b8a9219d8. Server-side state changed by exactly those three reviewer mutations. An earlier submit attempt returned performed=false with reason "no active reviewer lease found on PR #766" and made no server-side change. Merge not performed. No branch, tag, label, issue-state, or file mutation was made by the reviewer. Local: a detached reviewer worktree was created at the PR head and a temporary detached baseline worktree atfcf6981b1bwas created and then removed; the control checkout stayed on master and clean.What is blocked:
Blocker classification: none. No blocking change requests, no conflicts, no failing in-scope tests, and no unresolved acceptance criteria. The 2 remaining full-suite failures are pre-existing on the base commit and outside this diff, so they do not gate this PR.
Who/what acts next:
Next actor: merger. Required action: an independent prgs-merger/sysadmin session acquires the merger PR lease, re-verifies this approved review at the exact head
4b8a9219d8, reassesses mergeability, and merges PR 766 via gitea_merge_pr with confirmation "MERGE PR 766". Merge not performed by this reviewer session; a reviewer approval is not merge authorization.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #766
issue: #723
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 9625-79d38f65031d
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-766-4b8a921
phase: released
candidate_head:
4b8a9219d8target_branch: master
target_branch_sha:
fcf6981b1blast_activity: 2026-07-20T04:30:44Z
expires_at: 2026-07-20T04:40:44Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #766
issue: #723
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 52960-cfa84f3dee63
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-766-4b8a921
phase: claimed
candidate_head:
4b8a9219d8target_branch: master
target_branch_sha:
fcf6981b1blast_activity: 2026-07-20T04:38:01Z
expires_at: 2026-07-20T04:48:01Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-20T04:38:50.978412+00:00approveon PR fix(mcp): enforce role capability invariants (#766)closed(merged=True)d12adabeb1cf7810b1286ca8871a9b680646021c1prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.
Merger closeout for PR #766 by an independent
prgs-merger/sysadminsession. Merge performed natively; no review, no author remediation, no branch or worktree deletion, no control-checkout update.Canonical Issue State
STATE:
merged
WHO_IS_NEXT:
reconciler
NEXT_ACTION:
All MCP namespaces (gitea-tools, gitea-reviewer, gitea-merger, gitea-reconciler) must be restarted or reconnected at master
d12adabeb1before any further mutation, because every running daemon started atfcf6981b1band is now stale. After that restart, aprgs-reconcilersession may perform scoped terminal cleanup for this landed PR: remove the stalestatus:pr-openlabel from issue #723, clean moot leases for PR #766, and remove the merged branch and its worktrees through sanctioned tools only.NEXT_PROMPT:
WHAT_HAPPENED:
An independent merger session re-ran the full merger gate sequence against fresh live state and merged PR #766. Verified before merge: OS identity jasonwalker/502 non-root; identity sysadmin with profile prgs-merger and identity_match true, auto_profile_substitution false; repository binding Scaled-Tech-Consulting/Gitea-Tools on remote prgs with canonical root /Users/jasonwalker/Development/Gitea-Tools and workspace roots aligned; namespace gitea-merger healthy with ide_namespace_proven true and blocks_merge_workflow false; runtime parity in_parity true, restart_required false, startup and current head both fcf6981b1ba4d8d5072357ba13be0fdb6baa07ff; workflow proof loaded from skills/llm-project-workflow/workflows/review-merge-pr.md at hash 263d0a6cb8a6; router route_result allowed_current_session; capability merge_pr allowed with stop_required false. Approval freshness: review 478 by sysadmin/prgs-reviewer, verdict APPROVED at exact head
4b8a9219d8, dismissed false, stale false, quarantined false, approval_at_current_head true, has_blocking_change_requests false, author jcwalker3 distinct from merger sysadmin. Sync reassessment: commits_behind 0, mergeable true, has_conflicts false, checks not required by live branch protection, recommended_next_action merge_now. A merger PR lease was acquired natively (lease comment 13074, session 52960-cfa84f3dee63) in a dedicated detached merger worktree, live state was re-read after acquisition and was unchanged, and the merge was then executed with the pinned head and confirmation.WHY:
Every merger gate passed against freshly read live state, the approval was visible and undismissed at the exact pinned head with no blocking change requests, and the operator explicitly authorized the merge of this PR.
RELATED_PRS:
PR #766 (this PR, closed with merge commit
d12adabeb1, implements issue #723). No other open PR is linked to issue #723, and this merger session touched no other PR.ISSUE:
#723
BASE:
master
HEAD:
fix/issue-723-role-stamp-invariants
HEAD_SHA:
4b8a9219d8REVIEW_STATUS:
approved
VALIDATION:
No new validation was run by this merger session, by design. The merger phase does not re-review. Validation of record is reviewer review 478 at head
4b8a9219d8: red proof reproduced on master sources, focused 48 passed with 78 subtests, adjacent 769 passed with 241 subtests, full suite 3711 passed with 6 skipped and 491 subtests, and 2 failures proven pre-existing at basefcf6981b1b.BLOCKERS:
none
SUPERSEDES:
none
SUPERSEDED_BY:
none
MERGE_READY:
merged — PR #766 state closed, merged_at 2026-07-19T23:38:50-05:00, merge commit
d12adabeb1, live prgs/master advanced fromfcf6981b1btod12adabeb1, issue #723 auto-closed.LAST_UPDATED_BY:
sysadmin / prgs-merger / 2026-07-20
NATIVE_MERGE_PROOF: transport=native_mcp; tool=gitea_merge_pr; namespace=gitea-merger; profile=prgs-merger; identity=sysadmin; pinned_head=4b8a9219d85bc99b6abeebb4dc9db3e344bc27e5; merge_method=merge; merge_commit=d12adabeb1cf7810b1286ca8871a9b680646021c; merger_lease_comment_id=13074; lease_proof_kind=sanctioned_acquire_merger
[THREAD STATE LEDGER]
What is true now:
PR #766 is closed with merge commit
d12adabeb1recorded at 2026-07-19T23:38:50-05:00. Live prgs/master isd12adabeb1. Issue #723 is closed. Server-side decision state: reviewer review 478 is the approval of record at head4b8a9219d8, undismissed, non-stale, non-quarantined; the prgs-reviewer review-decision lock for this PR was cleared by the merge tool after the merge with audit comment id 13078; the merger PR lease from session 52960-cfa84f3dee63 (lease comment 13074) is now moot. Local verdict/state: this merger session prepared no verdict and holds no local decision state; it inspected live state only and executed the merge from a detached merger worktree at branches/merge-pr-766-4b8a921 pinned to head4b8a9219d8. Every MCP namespace daemon is running code from startup headfcf6981b1band is therefore stale relative to live master. The control checkout at /Users/jasonwalker/Development/Gitea-Tools remains on master atfcf6981b1band is clean; the merger phase does not advance it.What changed:
Server-side mutation ledger for this merger session, exactly four mutations: one merger PR lease comment id 13074; one native merge of PR #766 via gitea_merge_pr producing merge commit d12adabeb1cf7810b1286ca8871a9b680646021c; one post-merge review-decision-lock clearance for profile prgs-reviewer with audit comment id 13078; and this handoff comment. Issue #723 was closed by Gitea as a side effect of the merge, not by a direct issue mutation from this session. No formal review verdict was posted to Gitea by this session, no branch was pushed or deleted, no worktree was deleted, no label was changed, and no file was modified. Local: one detached merger worktree was created at branches/merge-pr-766-4b8a921 at the pinned head, and git fetch --prune prgs was run twice. The control checkout was not modified.
What is blocked:
PR #766 reached its terminal merged state with no blocking change requests, no conflicts, no failing in-scope tests, and no unresolved acceptance criteria. Two follow-on conditions are recorded here as preconditions for later phases rather than as blockers on this PR. First, every MCP namespace daemon is stale at
fcf6981b1band must be restarted or reconnected atd12adabeb1before any further mutation, because the loaded capability-gate code predates this merge. Second, residual hygiene items remain for a reconciler and are out of scope for a merger: the stalestatus:pr-openlabel on closed issue #723, the moot merger lease for PR #766, the merged remote branchfix/issue-723-role-stamp-invariants, and the three local worktrees created for this PR (author, reviewer, merger).Do not do:
Do not re-review PR #766 or replay any approve or merge command; the PR is terminal. Do not run any mutation through a stale namespace before the restart or reconnect at
d12adabeb1. Do not advance the control checkout as part of a merger phase. Do not delete branches, worktrees, or lock files by hand, and do not batch the residual cleanup together with unrelated targets. Do not treat this merger handoff as authorization for any new author work.Who/what acts next:
Next actor: the operator, then a reconciler. Required action: the operator restarts or reconnects all four MCP namespaces (gitea-tools, gitea-reviewer, gitea-merger, gitea-reconciler) at master
d12adabeb1and advances the control checkout to that SHA outside the merger phase; only after that may a prgs-reconciler session perform the scoped terminal cleanup listed above through sanctioned tools. This merger session performs no further mutation and stops here.