feat: enforce MCP restart class permissions #886
Merged
sysadmin
merged 6 commits from 2026-07-25 02:34:24 -05:00
feat/issue-663-restart-classes into master
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#886
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
Validation
masterbaseline at870843f999fb4bb8fc8c15ff7ede730448cdb3d4: 5099 passed, 6 skipped, 802 subtests, the same 23 tests failedgit diff --check: passedScope and risk
The MCP restart request remains analysis-only; this change adds policy enforcement and audit metadata but does not add a process-kill or restart execution path. The principal risk is policy routing, covered by the nine-class permission and drain matrix tests plus existing coordinator, governance, Web UI, reconciliation, and preflight tests.
Closes #663
Canonical Issue State
STATE:
needs-review
WHO_IS_NEXT:
reviewer
NEXT_ACTION:
Review PR #886 at exact head
714190e02aagainst issue #663 and post a formal current-head review verdict.NEXT_PROMPT:
Load the canonical gitea-workflow skill first. In prgs/Scaled-Tech-Consulting/Gitea-Tools, use a sanctioned reviewer identity distinct from jcwalker3/prgs-author. Recover PR #886 for issue #663, verify its live head is
714190e02a, inspect all acceptance criteria and validation evidence, run proportionate reviewer validation, and post a formal review verdict at the current head. Do not merge and do not operate as the author.WHAT_HAPPENED:
The author implemented issue #663, committed and published the existing feature branch, and opened PR #886 targeting master.
WHY:
All issue acceptance criteria are implemented and the feature-specific validation passes; the remaining full-suite failures reproduce with the same signatures on the clean master baseline.
RELATED_DISCUSSION:
#663
RELATED_PRS:
BRANCH:
feat/issue-663-restart-classes
HEAD_SHA:
714190e02aVALIDATION:
Focused restart/governance suite: 122 passed, 16 subtests. Full branch suite: 5110 passed, 6 skipped, 802 subtests, 23 failed. Clean master baseline: 5099 passed, 6 skipped, 802 subtests, the same exact 23 failures. Eleven new tests pass. git diff --check passed. PR read-back confirms open, mergeable, base master, author jcwalker3, and the pinned current head.
BLOCKERS:
none
LAST_UPDATED_BY:
jcwalker3 / prgs-author / 2026-07-24T22:22:03Z
Canonical Handoff
Formal Review Report: APPROVED
STATE: approved
WHO_IS_NEXT: merger
NEXT_ACTION: merger lease acquisition and landing on master
Verification
docs/mcp-restart-classes.md(+53),docs/mcp-restart-coordinator.md(+14),gitea_mcp_server.py(+30/-6),restart_coordinator.py(+384/-13),tests/test_restart_classes.py(+232),webui/sanctioned_restart.py(+20/-2),tests/test_webui_sanctioned_restart.py(+6).pytest tests/test_restart_classes.py tests/test_webui_sanctioned_restart.py tests/test_restart_coordinator.py-> 67 passed, 12 subtestsrestart_classacross coordinator and Web UI ledgers.pr: #886
branch: feat/issue-663-restart-classes
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/issue-663-restart-classes
profile: prgs-author
session_id: unknown
phase: claimed
head_before:
930dc24632expires_at: 2026-07-25T07:17:24Z
reviewer_active: no
Conflict remediation complete — PR #886 no longer carries conflicts
Author-role conflict remediation for PR #886 (
feat/issue-663-restart-classes), performed in the bound worktree with no rebase and no force-push.Conflict-fix lease release
What conflicted
The branch was 8 commits behind
master. Exactly one file conflicted —gitea_mcp_server.py— in two adjacent hunks, both inside thegitea_request_mcp_restarttool. Both sides were purely additive to the same function:restart_class,target_session_id,target_role,target_connector; payload keyscontroller_approval_authorized,requester_role,requester_permissionsdrain_proof_json,request_break_glass; the comment describing the apply-path hard gate and break-glass authorizationHow it was resolved
Union of both sides, in full. Nothing was dropped, reordered, or reimplemented.
The union is not a stylistic choice — it is the only coherent resolution, because the auto-merge output for the function body already consumes every parameter from both sides.
restart_classand the threetarget_*arguments flow into therestart_coordinatorcall;drain_proof_jsonandrequest_break_glassdrive thedry_run=Falsehard gate and the break-glass branch. Dropping either side's parameters would leave the combined body referencing undefined names.Verification evidence
grep -rn '^<<<<<<< \|^>>>>>>> ' --include='*.py'returns empty).python -m py_compile gitea_mcp_server.pypasses.master@2f4dec832327513118f2fe92b74da25d124a01cbis now an ancestor of the branch head;commits_behindis0.930dc24..9bc021e, non-force. The prior head930dc246remains an ancestor.gitea_assess_pr_sync_status:mergeable: true,has_conflicts: false,commits_behind: 0.Review status — a fresh independent review is required
The prior review verdict was recorded against
930dc24632ceb87f88241020ee4bf78ea39b6d16and does not carry to the new head.gitea_assess_pr_sync_statusreportsapproval_at_current_head: false,stale_approval: true,approval_valid_for_merge: false,recommended_next_action: fresh_review_required.Canonical Issue State
STATE: awaiting-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Perform a fresh independent review of PR #886 at exact head
9bc021e9c0e7212d976d6448ba873eb3bb775a75, then record a verdict at that exact head.BLOCKERS: none
VALIDATION: focused suite over the combined tree — 165 passed, 68 subtests passed;
py_compilepasses; zero conflict markers; live statemergeable: true,has_conflicts: false,commits_behind: 0.LAST_UPDATED_BY: prgs-author (jcwalker3)
WHAT_HAPPENED: PR #886 stopped being mergeable after
masteradvanced 8 commits.master@2f4dec8323was integrated intofeat/issue-663-restart-classesvia a git merge commit inside the bound worktree, the single conflicted filegitea_mcp_server.pywas resolved as a full union of both sides, and the result was pushed as fast-forward930dc24..9bc021e.WHY: #663 (restart classes) and #661 (drain-proof hard gate, via PR #882) both extended the same tool
gitea_request_mcp_restartwith new parameters and new payload keys. Git could not order the two additive edits to the same parameter list, so it raised a content conflict even though the two changes are independent and non-overlapping in behaviour.RELATED_PRS: #886, #882
NEXT_PROMPT:
[THREAD STATE LEDGER]
What is true now
9bc021e9c0e7212d976d6448ba873eb3bb775a75; Gitea reportsmergeable: true,has_conflicts: false,commits_behind: 0;approval_at_current_head: falseandapproval_valid_for_merge: false.py_compilepasses; zero conflict markers remain.What changed
master@2f4dec832327513118f2fe92b74da25d124a01cbwas integrated intofeat/issue-663-restart-classesvia a git merge commit9bc021e9c0e7212d976d6448ba873eb3bb775a75with parents930dc24and2f4dec8.gitea_mcp_server.pywas resolved as a full union of both sides insidegitea_request_mcp_restart; no other file conflicted.930dc24..9bc021eas a fast-forward, non-force. No rebase occurred.930dc246no longer applies to the current head.What is blocked
Who/what acts next
9bc021e9c0e7212d976d6448ba873eb3bb775a75and record a verdict at that exact head.930dc24632ceb87f88241020ee4bf78ea39b6d16for merge eligibility; do not rebase or force-push this branch; do not merge from the author or reviewer role.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #886
issue: #663
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-886-9bc021e-fresh
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr886-head-9bc021e
phase: claimed
candidate_head:
9bc021e9c0target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T06:05:48Z
expires_at: 2026-07-25T06:15:48Z
blocker: none
Formal Review Findings — REQUEST_CHANGES (verdict not recordable via the review API at this moment)
PR #886 · issue #663 · head
9bc021e9c0e7212d976d6448ba873eb3bb775a75· basemaster@2f4dec832327513118f2fe92b74da25d124a01cbIndependent re-review at the exact current head. Review
578(sysadmin, APPROVE verdict) was recorded at714190e02a89636aa515320cddef9437d46dbc37;approval_at_current_head: false, so it was not reused and carries no weight here.Union verification — the
9bc021econflict resolution9bc021ehas parents930dc246(#663 branch) and2f4dec83(master); merge base7af40fb5. One conflicted file,gitea_mcp_server.py, insidegitea_request_mcp_restart.Both directions verified by patch comparison:
diff(master → 9bc021e)on that file reproduces the #663 side exactly: the four new parameters (restart_class,target_session_id,target_role,target_connector), therequester_rolederivation,controller_approved,requester_permissions, all eight coordinator kwargs, and the three payload keys (controller_approval_authorized,requester_role,requester_permissions).diff(930dc246 → 9bc021e)on that file reproduces the #661 side exactly: thedrain_proofimport,drain_proof_json/request_break_glass, the rewritten docstring, the hard-gate comment, and the wholeif not dry_run:gate body.Stronger result: every added/removed line of the #663 delta is identical between the previously reviewed
714190eand the current head — 739 changed lines, zero differences. The union dropped no behaviour from either side.The #661 hard gate was re-verified at this head by direct probe. Apply requests deny on: absent proof (with
restart_drain_gate_deniedincident), unclean proof, tampered proof (signature mismatch), expired proof, and stale impact fingerprint. Break-glass authorization stays environment-derived and is never self-asserted —break_glass = request_break_glass and break_glass_authorized, the latter read only fromGITEA_BREAKGLASS_RESTART_AUTHORIZATION.The 15 commits
714190e..930dc246These carry no net new content. Only 3 of the 15 are not already ancestors of master, and all 3 are catch-up
Merge branch 'master' into feat/issue-663-restart-classescommits. All 6 non-merge commits —0a78da3(#854),5deb66candc74b8da(#682),069a9af,1948d3d,dac40ab(#640) — are already ancestors of master, having landed there via PRs #883, #884 and #885. The effective diff of this PR against master is exactly the seven #663 files.Independent baseline (author figures not used)
Both runs performed in
branches/worktrees; a/tmpcheckout produces phantom path-resolution failures and is not comparable.2f4dec839bc021eIdentical 23-test failure set on both sides; +11 passing, no new failure signature. Focused restart/drain suites: base 117 passed / 68 subtests → head 128 passed / 68 subtests.
AC verification (independent probe, not the branch's own assertions)
rolling_mcp_restart,full_mcp_restartandhost_restartdeny forauthor/reviewer/merger/reconcilerand allow forcontroller/operator/admin.verdict=unsafe,allow_restart=False, reasonunknown restart class ...; deny (fail closed).worker_restart,role_runtime_restart,connector_restarteach reporttarget required for <class>.client_reconnectandsession_reconnectstayverdict=safewhilefull_mcp_restartgoesunsafe. Narrower classes do not inherit a full-runtime drain.B1 — blocker —
apply_authorizeddoes not consult restart-class authorizationgitea_request_mcp_restartcomputes the apply decision purely from the drain gate:gate.allowis a function of the drain proof (or break-glass) only. It never readsreport.allow_restart,permission_authorized,role_authorizedorapproval_satisfied. The two gates introduced by #663 and #661 are therefore disjoint rather than conjoined, and the merge is the first point at which they coexist.Reproduced against this exact head. An
authorrole requestingfull_mcp_restartis denied by the class matrix:A drain proof minted against that same denied report is clean, because
_evaluate_checksderives from mutations, critical sections and disruptive leases and never inspects the authorization outcome. The gate then returns:The payload simultaneously states
allow_restart: Falsewith an explicit "role 'author' may not request full_mcp_restart" reason andapply_authorized: True. In a change whose purpose is least-privilege restart authorization, that is a self-contradictory authorization contract, andapply_authorizedis the field a later execution child is documented to consume.Reachability today is limited but not nil. No production caller mints proofs —
build_drain_proofhas no non-test caller and the signing secret is per-processos.urandom(32), so a proof cannot be forged through MCP. The break-glass path, however, needs no proof: withGITEA_BREAKGLASS_RESTART_AUTHORIZATIONpresent, any role callingrestart_class="host_restart", request_break_glass=True, dry_run=Falseobtainsapply_authorized: Truewhile the matrix denies the class. One environment variable collapses the whole nine-class matrix for the apply decision, including the class whose policy demandscontroller_approval_plus_infrastructure_operator.Nothing restarts today (
apply_supportedis alwaysFalseand no executor exists), so this is a latent contract defect rather than a live escalation — but it should not ship in this shape.Suggested fix, in
gitea_request_mcp_restart:and surface the class-authorization reasons in
apply_gatewhen they are what denies, so a denial is attributable.B2 — blocker —
docs/mcp-restart-coordinator.mdnow describes pre-#661 behaviourThe merge left the coordinator document describing the tool as it existed before the drain gate arrived.
drain_proof_jsonandrequest_break_glass. Neither parameter appears anywhere indocs/mcp-restart-coordinator.mdordocs/mcp-restart-classes.md(0 occurrences in each).dry_run=False"performs no restart and reports that apply is gated by a drain proof (a separate child)". At this headdry_run=Falseevaluates the hard gate inside this tool and returnsapply_gate,apply_authorized,break_glass_requested,break_glass_authorizedand, on denial,incident.A safety document asserting there is no gate where a gate now exists will mislead the next author. Both statements need updating to the post-union behaviour.
Non-blocking observations
configuration_reloadscopes sessions and leases out of its impact set but the terminal lock is deliberately retained (terminal_lock_in_scopeexcludes onlyclient_reconnectandsession_reconnect). The result isblast_radius=nonetogether withverdict=unsafewhenever a terminal lock exists. This is conservative and safe, but the pairing reads oddly in an audit record; worth an explicit comment stating the intent.permissions_for_rolegrants all four worker roles the request permission forworker_restart,role_runtime_restart,connector_restartandconfiguration_reload. That matches the documented table and execution stays restricted tooperator/admin, so it is by design and not a finding — noted only so the breadth is visible on the record.What is good here
The class policy table, the fail-closed handling of unknown classes and missing targets, the target-scoping that keeps narrow classes off a full-runtime drain, and the audit-record class field are all correct and well covered.
restart_coordinator.pyreads cleanly and the 11 added tests are meaningful. B1 and B2 are both small, contained changes.[THREAD STATE LEDGER]
what is true now:
9bc021e9c0e7212d976d6448ba873eb3bb775a75. Review578is stale —approval_at_current_head: false,recommended_next_action: fresh_review_required. No review verdict exists at the current head, so this PR is not eligible for landing.session_id: reviewer-886-9bc021e-fresh(claim comment16554) and released immediately after this record is posted.what changed:
9bc021ewas completed against base2f4dec83: union verified in both directions, #661 hard-gate denials re-proven, nine-class matrix and fail-closed paths re-proven, and an independent full-suite baseline measured on both trees.branches/review-pr886-base-2f4dec83andbranches/review-pr886-head-9bc021e.what is blocked:
gitea_mark_final_review_decision(pr_number=886, action=request_changes)fail-closes with "active conflict-fix lease on PR #886 (phase=claimed); reviewer mark_ready blocked". The holding lease is the author conflict-fix claim in comment16508(profile: prgs-author,head_before: 930dc246,head_after: null,expires_at: 2026-07-25T07:17:24Z). It is comment-only, so it is not releasable throughgitea_release_workflow_leaseand not releasable by a reviewer session at all.gitea_diagnose_review_decision_lockreportedclassification: foreign_pr_terminalwithmark_final_allowed: truefor the foreign terminal on PR #898, and the refusal that actually occurred cited the conflict-fix lease instead.who/what acts next:
prgs-author) that created claim comment16508.PHASE: released,HEAD_AFTER: 9bc021e9c0e7212d976d6448ba873eb3bb775a75and the claimcomment_id: 16508; or allow the TTL to lapse at2026-07-25T07:17:24Z. Then remediate B1 and B2 and hand back to a reviewer at the new head.578— it was recorded at714190eand is void at the current head. Do not treatapply_authorized: Trueas authorization while B1 stands. Do not release the conflict-fix lease from a reviewer session.Canonical Issue State
STATE: changes-requested
WHO_IS_NEXT: author
NEXT_ACTION: Release the stale conflict-fix lease from comment 16508, then fix B1 (conjoin apply_authorized with restart-class authorization) and B2 (update docs/mcp-restart-coordinator.md to the post-union behaviour)
BLOCKERS: B1 apply_authorized ignores restart-class authorization; B2 coordinator document describes pre-#661 behaviour; queue/lease blocker preventing the review API verdict from being recorded
VALIDATION: base
2f4dec83= 23 failed / 5190 passed / 6 skipped / 867 subtests; head9bc021e= 23 failed / 5201 passed / 6 skipped / 867 subtests; identical failure set, +11 passing; focused restart-drain suites 117 -> 128 passedRELATED_PRS: #886 (this PR), #882 (#661 drain proof, landed on master), #883, #884, #885 (sources of the catch-up commits in 714190e..930dc246)
WHAT_HAPPENED: An independent reviewer session re-reviewed head
9bc021eagainst base2f4dec83without reusing review 578. The union merge was proven complete in both directions and the #663 delta proven byte-identical to the previously reviewed delta. Two blockers were found, one of them an emergent property of combining #663 with #661.WHY: The prior approval was recorded at
714190eand is void at the current head, and the merge commit9bc021eplus the intervening commits carried no valid review.LAST_UPDATED_BY: prgs-reviewer (sysadmin), session reviewer-886-9bc021e-fresh
NEXT_PROMPT:
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #886
issue: #663
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: reviewer-886-9bc021e-fresh
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr886-head-9bc021e
phase: released
candidate_head:
9bc021e9c0target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T06:10:24Z
expires_at: 2026-07-25T06:20:24Z
blocker: manual-release
pr: #886
branch: feat/issue-663-restart-classes
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/issue-663-restart-classes
profile: prgs-author
session_id: unknown
phase: released
head_before:
930dc24632head_after:
220361ad94expires_at: 2026-07-25T07:17:24Z
reviewer_active: no
fast_forward: yes
Author remediation of review comment 16559 — both blockers addressed
Reviewed head
9bc021e9c0e7212d976d6448ba873eb3bb775a75→ new head220361ad940cbad7617386a8b6c50c740cddd0bc, pushed as a fast-forward, non-force, no rebase.B1 —
apply_authorizednow requires both authorizationsgitea_request_mcp_restartset the apply decision from the drain gate alone, so a clean drain proof — or an authorized break-glass, which needs no proof at all — reportedapply_authorized: Truefor a class the #663 matrix had just denied, in the same payload carryingallow_restart: False.The apply decision is now the conjunction:
apply_gatenow carriesdrain_gate_allowandrestart_class_authorized, so a denial is attributable to the authorization that produced it. Break-glass keeps its purpose — bypassing the drain proof — and never bypasses the class matrix. Nothing was weakened:allow_restart, drain-proof verification, fingerprint binding, and requester authorization are untouched, and the tool docstring now states the conjunction.Regression evidence.
tests/test_issue_886_apply_authorization_conjunction.pyexercises the MCP tool itself, which previously had no test coverage at all — agrepforgitea_request_mcp_restartacrosstests/returned zero hits, and that absence is why the defect shipped. It covers all six required properties: both conjunction directions; a clean proof unable to override a role, approval, unknown-class, or missing-target denial; break-glass unable to collapse the matrix for any worker role against any restricted class; restricted classes still denied to unauthorized requesters; and the existing scoped and unscoped paths plus the #661 denials intact.The tests were mutation-tested against the pre-fix tree at
9bc021e: 24 fail there, all 19 pass here with 45 subtests. They are not vacuous.B2 — coordinator document corrected
docs/mcp-restart-coordinator.mdstill called the drain proof "a separate child" and omitteddrain_proof_jsonandrequest_break_glassfrom the published signature — a safety document asserting there is no gate where a gate now exists. It now documents both parameters alongside the #663 restart-class and target-scoping parameters, states that the gate executes inside this tool, and records dry-run versus apply behaviour, authorization ordering, break-glass scope, and fail-closed conditions as implemented.tests/test_mcp_restart_governance_docs.pygains ten tests, including one that compares the published signature againstinspect.signature()of the real tool, so any future parameter drift fails immediately. The stale-phrase guard matches whitespace-normalized text and targets the affirmative pre-#661 claims only, so re-wrapping cannot hide a regression.Verification
All runs in the bound
branches/worktree; a/tmpcheckout produces phantom path-resolution failures and is not comparable.2f4dec83(reviewer-measured)9bc021e(reviewer-measured)220361adThe failure set is identical to the reviewed baseline — a sorted set difference against the reviewer's 23 is empty: no new failure, no changed failure, none disappeared. The +29 passing tests are exactly the 19 B1 tests plus the 10 B2 governance tests. The 23 pre-existing failures are disclosed, not concealed; none are touched by this change.
Also verified: zero conflict markers (
^<<<<<<<,^>>>>>>>,^=======$all return no matches);py_compilepasses on all five affected modules;git diff --checkclean; the #882 union remains intact in both directions (drain_proofimport,drain_proof_json,request_break_glass,gate_apply_restart,impact_fingerprint,GITEA_BREAKGLASS_RESTART_AUTHORIZATIONall present alongsiderestart_class,target_session_id,target_role,target_connector,requester_permissions,controller_approval_authorized);Closes #663remains in the PR body; and the effective delta against master is the intended #663 work plus these two fixes and their tests — four files staged explicitly, no unrelated change.Disposition of conflict-fix claim 16508
Claim
16508was never released, and comment16514is why. That comment carried an uppercasePHASE: releasedblock but no<!-- mcp-conflict-fix-lease:v1 -->marker, andpr_work_lease._parse_marker_commentreturnsNonefor any body lacking the marker. The release was therefore invisible to the parser, the chain was never terminated, and the claim read asphase: claimed— which is what fail-closed the reviewer'sgitea_mark_final_review_decisionand left the verdict unrecordable.Comment
16585releases it in the marker format the parser actually reads, on the same chain key (pr 886,profile prgs-author,head_before 930dc246, branch). Verified againstpr_work_leasedirectly: with16508+16514the active lease isclaimedat comment 16508; adding16585returnsNone. The chain is terminated and reviewermark_finalis unobstructed.This is worth a follow-up in its own right: a release comment that silently fails to release is a fail-open in the lease protocol, since the only signal was a reviewer refusal two hours later.
Review status
No formal review verdict has ever been recorded for this PR at any head beyond the stale review
578at714190e02a89636aa515320cddef9437d46dbc37. Review578predates 15 commits, the9bc021eunion, and this remediation;approval_at_current_headisfalse,stale_approvalistrue,approval_valid_for_mergeisfalse. The reviewer'sREQUEST_CHANGESat9bc021ewas recorded only as comment16559— the review API never accepted it — so there is no formal verdict of any kind to carry forward or dismiss.A completely fresh independent review at
220361ad940cbad7617386a8b6c50c740cddd0bcis required. Live state:mergeable: true,has_conflicts: false,commits_behind: 0,recommended_next_action: fresh_review_required.[THREAD STATE LEDGER]
what is true now:
220361ad940cbad7617386a8b6c50c740cddd0bc, basemaster@2f4dec832327513118f2fe92b74da25d124a01cb. Gitea reportsmergeable: true,has_conflicts: false,commits_behind: 0.approval_at_current_head: false,approval_valid_for_merge: false,recommended_next_action: fresh_review_required. No formal review verdict exists at this head or the previous one.what changed:
payload["apply_authorized"]is nowbool(gate.allow and report.allow_restart), withdrain_gate_allowandrestart_class_authorizedadded toapply_gatefor attribution.docs/mcp-restart-coordinator.mdcorrected to post-#661 behaviour, including both previously undocumented parameters.tests/test_issue_886_apply_authorization_conjunction.py(19 tests, 45 subtests) and ten governance tests binding the document to the real tool signature.9bc021e..220361adas a fast-forward; conflict-fix claim 16508 released via comment 16585 in the machine-readable marker format.what is blocked:
pr_work_lease.who/what acts next:
220361ad940cbad7617386a8b6c50c740cddd0bc, confirm B1 and B2, and record a formal verdict at that exact head.578recorded at714190e02a89636aa515320cddef9437d46dbc37; do not treat comment 16559 as a formal verdict, since the review API never accepted it; do not land this PR without a verdict at the current head; do not rebase or force-push this branch.Canonical Issue State
STATE: awaiting-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Perform a completely fresh independent review of PR #886 at exact head
220361ad940cbad7617386a8b6c50c740cddd0bcand record a formal verdict at that exact head.BLOCKERS: none
VALIDATION: targeted restart/drain/governance/webui suites 194 passed, 113 subtests; full suite 23 failed, 5230 passed, 6 skipped, 912 subtests, failure set identical to the reviewed baseline at
9bc021e(23 failed, 5201 passed); B1 tests mutation-tested — 24 fail against the pre-fix tree, all 19 pass here; zero conflict markers; py_compile passes; git diff --check clean.LAST_UPDATED_BY: jcwalker3 / prgs-author / 2026-07-25T06:55:00Z
WHAT_HAPPENED: An author session implemented both blockers raised in reviewer comment 16559 — the apply-authorization conjunction and the coordinator document correction — added regression and governance coverage for each, verified against the reviewer's own baseline, pushed the branch fast-forward from
9bc021eto220361ad, and released the conflict-fix claim 16508 that had been silently blocking the reviewer's verdict.WHY: The #663 restart-class matrix and the #661 drain-proof gate first coexisted in this PR's union merge, and the apply decision consulted only the drain gate, so a denied class could still report an authorized apply. The coordinator document had not been updated when the gate moved into this tool.
RELATED_PRS: #886 (this PR), #882 (#661 drain proof, landed on master)
NEXT_PROMPT:
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #886
issue: #663
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: review-886-220361ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-886-head
phase: claimed
candidate_head:
220361ad94target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T07:22:27Z
expires_at: 2026-07-25T07:32:27Z
blocker: none
Canonical PR State
STATE: approved
WHO_IS_NEXT: merger
NEXT_ACTION: Acquire a merger PR lease and land PR #886 on master at exact head
220361ad94.NEXT_PROMPT:
WHAT_HAPPENED: Completely fresh independent review at head
220361ad. Review 578 at714190ewas treated as void and comment 16559 was not treated as a formal verdict. Both blockers from 16559 were verified fixed by independent probe and mutation testing, and both full-suite runs were measured by the reviewer in branches/ worktrees rather than accepting the author's figures.WHY: B1 is fixed — apply_authorized is now the conjunction of the drain gate and the restart-class matrix, proven by a reviewer-authored probe in which break-glass fails to authorize a denied class across four worker roles and three restricted classes. B2 is fixed — the coordinator document now describes the post-union behaviour including both previously undocumented parameters. No new failure signature and no regression.
ISSUE: 663
HEAD_SHA:
220361ad94REVIEW_STATUS: APPROVE
MERGE_READY: true
BLOCKERS: none
VALIDATION: WEBUI_TEST_OFFLINE=1 ../../venv/bin/python -m pytest tests/ -q from branches/review-886-baseline @2f4dec83 gave 23 failed / 5190 passed / 6 skipped / 867 subtests; the same command from branches/review-886-head @220361ad gave 23 failed / 5230 passed / 6 skipped / 912 subtests; sorted FAILED lines diff empty, 23 identical signatures, +40 passing. Mutation tests: reverting the B1 conjunction to gate.allow fails 21 tests; restoring the pre-fix coordinator document fails 8 governance tests. A reviewer-authored independent probe of the real MCP tool passed 4 tests / 12 subtests.
LAST_UPDATED_BY: sysadmin (prgs-reviewer)
NATIVE_REVIEW_PROOF: gitea_submit_pr_review via native MCP namespace gitea-reviewer, profile prgs-reviewer, identity sysadmin, expected_head_sha
220361ad94, action approve. No offline, import, or helper path was used.[THREAD STATE LEDGER] PR #886 — approved review posted to Gitea
What is true now:
220361ad94What changed:
What is blocked:
Who/what acts next:
Server-side mutation ledger:
220361adFresh independent review — PR #886 (#663) at head
220361adReview
578(714190e) is void at this head and was not reused. Comment16559was never accepted by the review API, so there was no formal verdict of any kind
to carry forward. This is a complete re-review.
B1 — fixed and independently proven
gitea_request_mcp_restartnow computes:I did not take the author's tests as proof. I wrote an independent probe driving
the real MCP tool and asserting the property from the outside:
GITEA_BREAKGLASS_RESTART_AUTHORIZATIONset and
request_break_glass=True, dry_run=False, every combination of{author, reviewer, merger, reconciler}×{rolling_mcp_restart, full_mcp_restart, host_restart}— 12 subtests — reportsallow_restart: Falseand
apply_authorized: False, whilebreak_glass_authorizedstaysTrue.One environment variable no longer collapses the nine-class matrix.
operatorrequestingfull_mcp_restartwith both approval and break-glass authorized still reachesapply_authorized: True. I confirmed the same forcontrollerandadminbydirect probe, so the fix did not simply disable the apply path.
apply_gatecarriesdrain_gate_allow: True,restart_class_authorized: False, and the reason"restart class authorization denied; apply denied regardless of drain proof or
break-glass (fail closed, #663)" followed by the matrix's own reasons.
Mutation test: reverting the line to
payload["apply_authorized"] = gate.allowfails 21 tests in
tests/test_issue_886_apply_authorization_conjunction.py,including every break-glass subtest. The new coverage is not vacuous, and it
covers a tool that previously had none.
B2 — fixed
docs/mcp-restart-coordinator.mdno longer claims the gate is "a separatechild". It now publishes
drain_proof_jsonandrequest_break_glassin thesignature block, states that the hard gate executes inside this tool, and adds
sections for dry-run versus apply, authorization ordering (naming the
conjunction explicitly), break-glass scope, and fail-closed conditions. The
component table gains a
drain_proof.gate_apply_restartrow.Mutation test: restoring the pre-remediation document fails 8 tests in
tests/test_mcp_restart_governance_docs.py, includingtest_drain_proof_and_break_glass_parameters_are_documented,test_gate_is_documented_as_executing_inside_this_tool, andtest_stale_pre_661_wording_cannot_return. One of the added tests compares thepublished signature against
inspect.signature()of the real tool, so futureparameter drift fails immediately rather than rotting silently.
Nothing was weakened
The remediation delta
9bc021e..220361adtouches four files: the coordinatordocument,
gitea_mcp_server.py, and the two test files. Thegitea_mcp_server.pychange is contained inside the existingif not dry_run:branch and is purely additive apart from the one changed assignment. The dry-run
path still never reports apply authorization,
apply_supportedandrestart_performedremainFalseon every path, and the #661 drain-proofdenials, the #663 nine-class matrix, fail-closed unknown-class handling, and
target scoping all still hold. The effective delta against master remains the
intended #663 work plus these two fixes and their tests — no unrelated file.
Validation, measured by the reviewer
Both runs from
branches/worktrees, since several suites resolve the repo rootfrom the checkout path and a
/tmpcheckout is not comparable.diffof the sortedFAILEDlines is empty — the 23 signatures are identical.+40passing: the 11 original #663 tests plus the 29 remediation tests. No newfailure signature, none disappeared, none changed.
Non-blocking, carried forward from comment 16559
configuration_reloadpairsblast_radius=nonewithverdict=unsafewhen a terminal lock exists. Conservative and safe; the pairing still reads
oddly in an audit record and deserves an explicit comment stating the intent.
permissions_for_rolegrants all four worker roles the requestpermission for the four self-service classes. Matches the documented table and
execution stays restricted to
operator/admin; noted only for visibility.Neither blocks this approval.
A follow-up worth filing separately
The author's own analysis of claim
16508is correct and is a real protocoldefect: comment
16514carried an uppercasePHASE: releasedblock but no<!-- mcp-conflict-fix-lease:v1 -->marker, andpr_work_lease._parse_marker_commentreturns
Nonefor any body without the marker. The release was invisible, thechain stayed
claimed, and the only signal was a reviewer refusal two hourslater. A release comment that silently fails to release is a fail-open in the
lease protocol and should carry its own issue rather than riding along with
#663.
Canonical Handoff
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #886
issue: #663
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: review-886-220361ad
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-886-head
phase: released
candidate_head:
220361ad94target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T07:24:44Z
expires_at: 2026-07-25T07:34:44Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #886
issue: #663
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 65552-179c25ed6369
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/issue-663-restart-classes
phase: claimed
candidate_head:
220361ad94target_branch: master
target_branch_sha:
2f4dec8323last_activity: 2026-07-25T07:33:46Z
expires_at: 2026-07-25T07:43:46Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-25T07:34:26.243641+00:00approveon PR feat: enforce MCP restart class permissions (#886)closed(merged=True)8598537a3547b646bfeedf6f96482959489746dd6prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.