feat: enforce stable-control vs dev runtime modes for Gitea MCP (Closes #615) #770
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#770
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.
Closes #615
Implements the AC6–AC11 stable-control-runtime enforcement layer.
Head:
9bf3acfValidation:
established the policy but had no runtime enforcement: a daemon relaunched from a
feature worktree still holds production credentials and will happily mutate real
issues. This adds the enforcement layer (acceptance criteria 6-11).
stable_control_runtime.py:
process root and checkout branch, with an explicit GITEA_MCP_RUNTIME_MODE
declaration for packaged layouts that have no git checkout.
root, active workspace, repo binding, profile, identity, dirty files,
alignment, and real_mutations_allowed.
unknown runtime, dirty stable checkout, dev-worktree launch, and unsafe
process-root/workspace alignment.
namespace never implies reviewer, merger, or reconciler (#584).
plus health, identity, profile, workspace, capability, and rollback proof.
Server wiring:
gate. gitea.read is never blocked, so an operator can still diagnose a sick
runtime.
the same reason parity uses a startup baseline: the runtime a process serves
from is fixed when it loads its code. Enforcement is decided from how the
process was loaded, so per-test production simulation cannot switch it on.
stable_control_runtime and points at the promotion runbook when blocked.
Docs and tooling:
required record fields, per-namespace re-proving, rollback.
promotion record; it never restarts anything.
recovered, namespace not yet re-proven, promotion completed, rollback required.
Validation: 47 new tests in tests/test_stable_control_runtime.py. Full suite
3827 passed / 6 skipped / 2 failed; both failures are pre-existing on master
059ee77(verified in a clean baseline worktree: identical 2 failures,3780 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) [email protected]
Description
[Summary of changes and issue number closed.]
Closes #[Issue Number]
Checklist
git diff --checkis clean.Documentation and Wiki
docs/wiki/been updated accordingly?scripts/sync-gitea-wiki.sh, see Runbooks).docs/wiki/. If stale or empty, record the required sync as a follow-up before approval.docs/wiki/, sync-helper code, or policy docs alone are not sufficient to close a wiki issue.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #770
issue: #615
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 99553-fed66ecfe767
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-770
phase: claimed
candidate_head:
9bf3acfef6target_branch: master
target_branch_sha:
059ee77c1flast_activity: 2026-07-20T15:47:02Z
expires_at: 2026-07-20T15:57:02Z
blocker: none
Canonical PR State
STATE: open — independent reviewer verdict REQUEST_CHANGES recorded at head
9bf3acfef6WHO_IS_NEXT: author
NEXT_ACTION: Remediate findings F1, F2, and F3 on branch feat/issue-615-runtime-mode-enforcement, push to PR #770, and request a fresh independent review
NEXT_PROMPT:
WHAT_HAPPENED: An independent reviewer inspected all 8 changed files at the pinned head, re-ran the focused suite, reproduced a blocking fail-closed defect in the server wiring directly against the PR code, and posted exactly one native REQUEST_CHANGES verdict.
WHY: The pure module is sound, but the acceptance criterion 7 alignment blocker, as derived by the server, inverts its intent and would refuse every correctly bound author, reviewer, and merger session the moment the enforcement layer is promoted, leaving gitea.read as the only surviving operation.
ISSUE: #615
HEAD_SHA:
9bf3acfef6REVIEW_STATUS: REQUEST_CHANGES
MERGE_READY: no
BLOCKERS: code-defect blocker — F1 fail-closed alignment derivation and F2 process-lifetime snapshot of session-scoped facts, both in gitea_mcp_server.py; test-coverage blocker — F3, acceptance criterion 9's healthy-runtime case is proven only against a fixture
VALIDATION: Reviewer re-ran tests/test_stable_control_runtime.py in worktree branches/review-pr-770 at head
9bf3acf: 47 passed. git diff --check clean against base059ee77c1f. Diff independently confirmed as 8 files, 1714 insertions, 6 deletions, with no unrelated or generated files and no secrets. F1 reproduced by executing the PR's own build_runtime_report with a stable-control runtime and a branches/ worktree workspace: real_mutations_allowed False, blocker unsafe_process_root_workspace_alignment. The author's two full-suite failures were not re-run this session; they are recorded as pre-existing at this identical master SHA and neither touches a file in this diff.LAST_UPDATED_BY: prgs-reviewer (sysadmin)
NATIVE_REVIEW_PROOF: native gitea_submit_pr_review through the prgs-reviewer MCP namespace, workflow skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6, head pinned
9bf3acfef6, reviewer lease comment 13272.REQUEST_CHANGES — independent native review of PR #770 (Closes #615, acceptance criteria 6-11). Reviewer identity sysadmin (prgs-reviewer) is distinct from PR author jcwalker3. Base master at
059ee77c1f, commits behind 0, no conflicts, checks not required by live branch protection, no prior review at this head.Scope confirmed
The diff matches the author's record exactly:
stable_control_runtime.py,tests/test_stable_control_runtime.py,thread_state_ledger_examples.py,scripts/promote-stable-runtime,docs/stable-runtime-promotion-runbook.md,gitea_mcp_server.py,docs/architecture/mcp-stable-control-runtime-policy-adr.md,docs/llm-workflow-runbooks.md. Acceptance criteria 1-5 are correctly out of scope.Independently verified as correct
classify_runtime_modeandbuild_runtime_reportreport every required field. The operatorGITEA_MCP_RUNTIME_MODEdeclaration is validated against the mode allow-list, so a typo falls back to inference rather than being trusted.stable-controldeclaration cannot mask a dev-worktree launch:dev_worktree_launchedis derived from the process root independently of the declaration, and the dev-worktree blocker reads that derived fact. This is a genuinely good design decision.scripts/promote-stable-runtimeare present, and the helper is read-only — it never fetches, merges, restarts, reloads, or kills.[THREAD STATE LEDGER]examples are present.gitea.readis never blocked, so a sick runtime stays diagnosable.F1 (blocking) — the alignment derivation blocks every correctly-bound session
gitea_mcp_server.py:11391-11397derives the gate input as:This redefines
workspace_roots_aligned. The pre-existing meaning of that field, atgitea_mcp_server.py:573, isctx["roots_aligned"], which iscanonical_repo_root == process_project_root— a repository-level question: does this namespace target the repository the process is installed in. The new derivation instead requires the active task workspace to equal the process root.Under the global-worktree rule, all task work happens in a
branches/worktree, so the task workspace is required to differ from the process root. The new derivation therefore reportsaligned=Falsefor exactly the sessions that are configured correctly, andassess_runtime_mutation_gateturns that intounsafe_process_root_workspace_alignment, refusing every non-read operation.Reproduced against the PR's own code, with a healthy promoted runtime — clean control checkout, on
master,runtime_mode=stable-control:Failure scenario, concretely:
_resolve_namespace_mutation_context(None)resolves the workspace fromGITEA_ACTIVE_WORKTREE/GITEA_AUTHOR_WORKTREE(namespace_workspace_binding.py:77-97). Binding the author daemon to its session worktree is the documented fix for the publication gap recorded in issue #615 comment 13253. The moment that binding is set, every author mutation is refused by this gate.namespace_workspace_binding.py:83-84, wired atgitea_mcp_server.py:455-457). Holding a reviewer lease — the required precondition for submitting a verdict — sets the workspace to the lease worktree, so verdict submission itself is refused. This review, submitted while holding a lease bound tobranches/review-pr-770, would be blocked by the code it is reviewing once that code is promoted.The gate is currently inert only because it is not yet the running runtime and because the author daemon presently has no worktree binding at all. Promotion is what makes it live, and promotion is precisely when the workflow would deadlock, with
gitea.readthe only surviving operation.Suggested direction: feed the gate
ctx["roots_aligned"], preserving the established repository-level meaning, or express the check as worktree membership of the canonical repository root rather than path equality with the process root. Abranches/worktree of the same repository is the sanctioned configuration and must not be classified as unsafe.F2 (blocking) — a per-session fact is frozen into a process-lifetime snapshot
_current_runtime_mode_reportcaches its first result into_STARTUP_RUNTIME_MODEand reuses it for every later gate evaluation. The docstring calls this a startup snapshot, but nothing captures it at startup: the module-level_STARTUP_RUNTIME_MODEis initialised toNone, unlike_STARTUP_PARITY, which is genuinely captured at import. The read-only reporting path_current_runtime_mode_report(refresh=True)also seeds the cache when it is still unset, so whichever call happens to run first — possibly a read-onlygitea_get_runtime_contextfrom an arbitrary session — fixes the snapshot for the life of the process.The startup-baseline argument is sound for the facts it was written about: process root, checkout branch, runtime SHA. It does not hold for the two session-scoped fields the report also captures.
active_task_workspaceand its derived alignment change per session and per namespace, anddirty_fileschanges whenever the checkout does. Consequences:Suggested direction: capture the immutable process facts once at import, as the parity gate does, and evaluate the session-scoped fields per call.
F3 (major) — acceptance criterion 9's first case is not actually tested
tests/test_stable_control_runtime.pycovers the pure functions thoroughly, and the intent behind the AC9 list is clearly met at that layer. But every server-wiring test that asserts a healthy runtime permits mutations patches_current_runtime_mode_reportwith a fixture whoseworkspace_roots_aligned=True(stable_report, line 40;test_stable_runtime_allows_mutations_when_guards_forced, lines 447-453). The only test that calls the real derivation,test_live_report_describes_this_checkout(lines 465-468), asserts just the mode and the process root, and nothing aboutreal_mutations_allowed.So the derivation in F1 is executed by no test, which is why 47 passing tests coexist with a gate that would refuse every correctly-bound session. AC9 case "stable healthy runtime allows real mutations" is proven against a fixture rather than against the code that runs in production.
Non-blocking observations, no change required for approval
assess_namespace_reproofcomparesstr(proof_at) < str(flap_at)lexicographically. Correct for identical ISO-8601 UTC strings, silently wrong for any mixed format or offset. Consider parsing, or documenting the format contract at the recording functions._RUNTIME_MODE_GATE_UNDER_TESTis computed at import from"pytest" in sys.modules or "unittest" in sys.modules. Verified today that the production import chain pulls in neither, so this is not a live defect. It is fragile by construction: any future dependency importingunittest.mockat module scope would silently disable the entire gate in production. An explicit environment opt-in would fail safe instead.observe_runtimeparses porcelain withline[3:], so a rename is reported asold -> new. Cosmetic only; it feeds a count and a message.Verdict rationale
The design of the pure module is careful, and the module-level logic for acceptance criteria 6, 8, 10, and 11 is correct and well tested. The blocking problem is in the wiring: acceptance criterion 7's alignment blocker, as derived by the server, inverts its intent and would fail-close the entire mutation workflow at exactly the moment the enforcement layer goes live. F1 and F2 are in the same wiring seam and F3 is the coverage gap that allowed F1 through, so they should be addressed together.
No merge authorization is implied or granted by this review.
[THREAD STATE LEDGER] PR #770 — REQUEST_CHANGES posted to Gitea
What is true now:
9bf3acfef6What changed:
What is blocked:
Who/what acts next:
9bf3acfef6repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #770
issue: #615
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 5311-9cbb43114b9e
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-770
phase: claimed
candidate_head:
ab34280f90target_branch: master
target_branch_sha:
059ee77c1flast_activity: 2026-07-20T16:51:41Z
expires_at: 2026-07-20T17:01:41Z
blocker: none
Canonical PR State
STATE: open — independent reviewer verdict APPROVE recorded at head
ab34280f90CANONICAL_ITEM: this APPROVE verdict on PR #770 at head
ab34280f90SUPERSEDED_ITEM: review 483 (REQUEST_CHANGES) on PR #770 at the superseded head
9bf3acfef6, stale at the current head and undismissedWHO_IS_NEXT: merger
NEXT_ACTION: A prgs-merger session reassesses PR #770 at head
ab34280f90and decides the merge under its own gates; this review grants no merge authorizationNEXT_PROMPT:
WHAT_HAPPENED: A fresh independent reviewer pinned the current head, reproduced findings F1 and F2 directly against the PR code with the real workspace-root derivation and the real namespace resolver rather than test fixtures, ran the same reproduction against the superseded head as a control to prove the harness detects the defect, confirmed the five new F3 tests fail against the pre-fix code and pass against this head, ran focused and affected integration validation, independently reproduced the two claimed pre-existing failures on the base, and posted exactly one native APPROVE verdict.
WHY: All three findings from review 483 are remediated at this head. F1 now feeds the gate the established repository-level
roots_alignedcontract, so a correctly boundbranches/session is permitted rather than refused. F2 captures only immutable process facts at import and recomputes dirty state, workspace binding, and alignment per call, so the acceptance-criterion-7 dirty blocker keeps applying and a read-only call can no longer seed a permissive cache. F3 adds coverage that executes the real derivation and genuinely fails against the pre-fix code.ISSUE: #615
HEAD_SHA:
ab34280f90REVIEW_STATUS: APPROVE
MERGE_READY: yes
BLOCKERS: no blocker
VALIDATION: Focused suite tests/test_stable_control_runtime.py 52 passed at the pinned head in worktree branches/review-pr-770. Affected integration set — stable_control_runtime, namespace_workspace_binding, workspace_guard_alignment, master_parity_gate, mcp_stale_runtime, role_namespace_gate, runtime_clarity, workspace_mutation_consistency, stable_control_runtime_policy_docs, create_issue_workspace_guard, issue_comment_workspace_guard, preflight_workspace_repo_forwarding — 153 passed, 20 subtests passed. F1 and F2 reproduced with an independent harness that drives the real
_current_runtime_mode_reportand the real_resolve_namespace_mutation_contextagainst a realbranches/worktree. The five new F3 tests fail 5/5 against the pre-fix head and pass 5/5 against this head. The two author-claimed pre-existing failures reproduce identically on base059ee77. git diff --check clean against base; remediation scope 3 files, 189 insertions, 30 deletions; no secrets and no unrelated or generated files.LAST_UPDATED_BY: prgs-reviewer (sysadmin)
NATIVE_REVIEW_PROOF: native gitea_submit_pr_review through the prgs-reviewer MCP namespace, workflow skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6, head pinned
ab34280f90, reviewer lease comment 13292.APPROVE — fresh independent native review of PR #770 (Closes #615, acceptance criteria 6-11) at head
ab34280f9025406d5c392d4e07c9d34d3ecd47fe. Reviewer identity sysadmin (prgs-reviewer) is distinct from PR author jcwalker3. Base master059ee77c1fd483da385d6ccb4d842e808fb54630, commits behind 0, no conflicts, mergeable, checks not required by live branch protection.This verdict explicitly supersedes review 483, which recorded REQUEST_CHANGES at
9bf3acfef681f9766ae1d93680b03996a93f98a1. That review remains undismissed but is stale at this head, and it was not reused as the current verdict.Method
The author's handoff asked that the remediation be judged against the real derivation rather than the pass count. It was. Findings F1 and F2 were re-tested with an independent harness written for this review, which patches only
PROJECT_ROOTand the import-time facts — and derives those facts by calling the PR's ownobserve_runtime()against the live control checkout, so they are observed rather than invented._resolve_namespace_mutation_contextran unpatched against the realbranches/issue-615-runtime-mode-enforcementworktree. The same harness was then run against9bf3acfas a control, because a reproduction that passes at both heads proves nothing.F1 — remediated, confirmed by controlled experiment
The gate input is now
ctx.get("roots_aligned")(gitea_mcp_server.py:11414), restoring the established repository-level meaning. That key is real and carries the expected semantics:namespace_workspace_binding.py:156defines it ascanonical_root == process_root, which is the same contract already consumed atgitea_mcp_server.py:573. The fix consumes an existing contract rather than inventing a parallel one.Identical harness, identical real binding (
workspace_path= thebranches/task worktree,canonical_repo_root=process_project_root= the stable root,roots_aligned= True), differing only in the code under test:The correctly bound session that the previous head refused is now permitted, and the deadlock-on-promotion scenario described in review 483 no longer reproduces.
Misalignment still fails closed: with
roots_alignedFalse the gate raises the alignment blocker, so the fix narrowed the check to its intended meaning without removing it.F2 — remediated, confirmed by dynamic state change
_STARTUP_RUNTIME_FACTSis now genuinely captured at import (gitea_mcp_server.py:2027), matching the_STARTUP_PARITYbaseline it was previously only claimed to match. The result cache is gone entirely, andrefreshno longer selects between cache and live state, so the read-only seeding path is structurally eliminated rather than merely discouraged.Observed clean, then changed dirty state, then re-evaluated the gate:
The same sequence at
9bf3acfreporteddirty=[]at step 2 — the injected change was never observed at all, because the first report had been frozen. That is the F2 defect reproduced directly, and step 2 above is it not reproducing.The read-only path was checked separately: a permissive
refresh=Truecall followed by a mutation gate evaluation with a newly dirty checkout returns blocked, so a read-only call can no longer weaken a later mutation decision.observe_dirty_files()is a clean seam for this — it isolates the one runtime fact that legitimately changes during a process lifetime, andobserve_runtime()now delegates to it rather than duplicating the parsing.F3 — remediated, and the coverage is real
TestServerWiringRealDerivationpatches only the derivation's inputs — the import-time facts, the dirty-file read, and the resolved binding — and lets_current_runtime_mode_reportand_runtime_mode_blockexecute for real. That is the distinction the finding asked for, and it holds.Verified rather than assumed: the new test file was extracted and run against the pre-fix server code, where all five fail, and against this head, where all five pass. The pre-fix failure mode is the absent
_STARTUP_RUNTIME_FACTSsymbol rather than an alignment assertion, which is a weaker signal than a behavioural failure would be — noted, but the harness above supplies the behavioural proof independently, and the tests are a genuine forward regression guard: reverting the F1 derivation to path equality would maketest_clean_stable_checkout_with_bound_task_worktree_permits_mutationfail, since its fixture workspace is deliberately abranches/path distinct from the process root.The fifth test is a good addition beyond what was asked: an unresolvable binding must report alignment as unknown rather than as proof, and only definite misalignment blocks. Failing an unresolved binding closed would have reintroduced F1 through a different door.
Scope, base, and pre-existing failures
Remediation commit scope is
gitea_mcp_server.py,stable_control_runtime.py, andtests/test_stable_control_runtime.py— 3 files, 189 insertions, 30 deletions, matching the author's record exactly. No unrelated, generated, or credential-bearing files; secret scan across the full PR diff is clean;git diff --checkclean against base. Acceptance criteria 6, 8, 10, and 11 are untouched by this commit and were verified as correct in review 483.The two full-suite failures were independently reproduced on pristine base
059ee77, in the reviewer's own worktree rather than taken on trust:tests/test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probetests/test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issueBoth fail at the base with the branch absent, so neither is caused by this PR. They remain open pre-existing defects on master and are out of scope here.
Non-blocking observations, carried forward
The three observations from review 483 were deliberately left out of scope to keep the remediation diff minimal, which is the right call. They are not approval blockers and are restated only so they are not lost:
assess_namespace_reproofcompares timestamps lexicographically; correct for identical ISO-8601 UTC strings, silently wrong for mixed formats or offsets._RUNTIME_MODE_GATE_UNDER_TESTis inferred fromsys.modulesat import. Not a live defect, but fragile: a future dependency importingunittest.mockat module scope would silently disable the gate in production. An explicit environment opt-in would fail safe.observe_runtimeporcelain parsing renders a rename asold -> new; cosmetic, it feeds a count and a message.One further observation from this review, also non-blocking: capturing
_STARTUP_RUNTIME_FACTSat import moves severalgitsubprocess calls into module import. That is a small, one-time startup cost and is the correct trade for a trustworthy baseline, but it is worth knowing that importing the server module now shells out. Thedirty_fileskey inside_STARTUP_RUNTIME_FACTSis now dead — the gate always reads live dirt instead — and could be dropped from the captured dict for clarity.Verdict rationale
All three findings are remediated at the seam where they were raised, and each remediation was confirmed against the real code path rather than against a fixture or a pass count. F1 restores an existing contract instead of inventing a new one, F2 removes the caching hazard structurally rather than patching around it, and F3 closes the coverage gap that let F1 through. The enforcement layer now permits the sanctioned configuration and still fails closed on genuine misalignment, dirty stable checkouts, dev-worktree launches, and unknown runtimes.
No merge authorization is implied or granted by this review. Merge is a separate role decision under its own gates.
[THREAD STATE LEDGER] PR #770 — APPROVE posted to Gitea at head
ab34280fWhat is true now:
ab34280f90, superseding review 483 which sits at the superseded head9bf3acfef6059ee77What changed:
ab34280fWhat is blocked:
Who/what acts next:
ab34280f90under its own gates and decides the mergeab34280f90repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #770
issue: #615
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 51581-b2b2bfb0df07
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-770
phase: claimed
candidate_head:
ab34280f90target_branch: master
target_branch_sha:
059ee77c1flast_activity: 2026-07-20T17:36:13Z
expires_at: 2026-07-20T17:46:13Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-20T17:37:11.343400+00:00approveon PR feat: enforce stable-control vs dev runtime modes for Gitea MCP (Closes #615) (#770)closed(merged=True)0568f44cb2d87e78fd394a27a670e33c84f7842f2prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.
Canonical Issue State
STATE: closed — PR #770 landed on master via merge commit
0568f44cb2at 2026-07-20T12:36:59-05:00; linked issue #615 is closedCANONICAL_ITEM: this merger handoff for PR #770 at landed head
ab34280f90SUPERSEDED_ITEM: none — review 484 (APPROVE) was consumed as the merge precondition and is not superseded; review 483 (REQUEST_CHANGES) was already dismissed and stale at the landed head
WHO_IS_NEXT: reconciler
NEXT_ACTION: A prgs-reconciler session performs post-merge reconciliation for PR #770 and issue #615 under its own gates
NEXT_PROMPT:
WHAT_HAPPENED: A prgs-merger session pinned the candidate head, confirmed review 484 was visible, undismissed, non-stale and at that exact head, reassessed branch protection, checks, mergeability, conflicts, commits-behind, identity separation, runtime parity and namespace health, acquired a native merger lease, completed the merge through the sanctioned MCP merge tool with explicit operator authorization, and verified the result by read-after-write.
WHY: All merge gates passed at the pinned head with no blocking change requests, no conflicts, zero commits behind, and merger identity distinct from the PR author.
RELATED_PRS: #770 (this PR, closed, landed on master as merge commit
0568f44cb2, closing issue #615). No other PR is in scope for this merger phase; no PR was superseded, reopened, or otherwise affected by this merge.ISSUE: #615
HEAD_SHA:
ab34280f90MERGE_COMMIT_SHA:
0568f44cb2REVIEW_STATUS: APPROVE consumed (review 484, undismissed, non-stale, at the landed head)
MERGE_READY: consumed — merge completed and verified
BLOCKERS: no blocker
VALIDATION: No new code validation was run by the merger session; validation is the reviewer's recorded evidence at this head (focused suite 52 passed; affected integration set 153 passed with 20 subtests; five F3 tests fail 5/5 pre-fix and pass 5/5 at this head). Merger-side gate evidence: gitea_check_pr_eligibility eligible true with all checks passed; gitea_assess_pr_sync_status recommended_next_action merge_now, commits_behind 0, has_conflicts false, mergeable true, checks_required false, approval_valid_for_merge true, stale_approval false; gitea_get_pr_review_feedback approval_visible true, approval_at_current_head true, has_blocking_change_requests false, quarantined_approvals_at_current_head 0; runtime parity in_parity true, restart_required false, startup_head equals current_head 059ee77c1fd4; namespace gitea-merger ide_namespace_proven true with blocks_merge_workflow false; workflow proof skills/llm-project-workflow/workflows/review-merge-pr.md hash 263d0a6cb8a6.
LAST_UPDATED_BY: prgs-merger (sysadmin)
NATIVE_MERGE_PROOF: native gitea_merge_pr through the prgs-merger MCP namespace, merge method 'merge', explicit operator authorization "MERGE PR 770", expected_head_sha pinned
ab34280f90, merger lease comment 13298, lease_proof_kind sanctioned_acquire_merger, lease_proof_sanctioned true, post-merge decision-lock audit comment 13304.MERGE COMPLETED — PR #770 (Closes #615, acceptance criteria 6-11) landed on master.
Merge gates, reassessed live at the pinned head
Merger identity sysadmin (prgs-merger) is distinct from PR author jcwalker3, so the self-merge block does not apply. Every gate below was evaluated against live state in this session, not carried over from the reviewer handoff.
dismissed=false,stale=false,reviewed_head_shaequal to the pinned candidate head.approval_visible=true,approval_at_current_head=true,latest_approved_head_shaequal to the landed head.has_blocking_change_requests=false. Review 483 (REQUEST_CHANGES) sits at the superseded head9bf3acfeand is recordeddismissed=true,stale=true, so it did not gate the merge.quarantined_review_idsempty,quarantined_approvals_at_current_head=0.not_required, combined state pending with zero contexts) does not gate the merge.branch_protection_requires_current_base=false.mergeable=true,has_conflicts=false.059ee77c1f.in_parity=true,stale=false,restart_required=false, startup head equal to current head at059ee77c1f, mutation gate enforced.client_namespaceproof,ide_namespace_proven=true,blocks_merge_workflow=false.gitea_acquire_merger_pr_leasein worktreebranches/merge-pr-770pinned to the candidate head, comment 13298. No foreign or adopted lease was involved;lease_adopted_from_session_idis null.Live state was re-read after lease acquisition and immediately before the merge call. The head had not moved.
Read-after-write verification
The merge tool returned
performed=truewith a nullmerge_commitfield, so completion was not claimed on that response alone. Verified independently:state=closed, merge timestamp 2026-07-20T12:36:59-05:00,merge_commit_sha=0568f44cb2d87e78fd394a27a670e33c84f7842f,closed_at=2026-07-20T12:36:59-05:00.state=closed. Thestatus:in-progresslabel is no longer present on the issue.Post-merge side effects recorded by the merge tool
prgs-author-62865-d5cc892b, owner pid 62865, reportedstale_dead_process) was released by the tool's own cleanup path:cleanup_status {"615": "released"}. It was not edited or released manually.recovery_required=false.Deliberately not performed
Per the bounded merger phase scope: no review or verdict, no author remediation, no branch amendment, no branch deletion, no worktree deletion, no control-checkout advance, no namespace restart, and no selection of a follow-on task.
Residual state later cycles must not retry
059ee77c1fand is now behind live master. This is expected and intentional. Advancing it is a separate phase; do not treat it as a merge defect.branches/merge-pr-770was created for this phase at the pinned head and still exists. It is reconciler scope.assess_namespace_reproof;_RUNTIME_MODE_GATE_UNDER_TESTinferred fromsys.modulesrather than an explicit environment opt-in;observe_runtimeporcelain parsing rendering a rename asold -> new; and the now-deaddirty_fileskey inside_STARTUP_RUNTIME_FACTS.059ee77and were independently reproduced there by the reviewer. They are not caused by this PR and remain open defects on master:tests/test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probeandtests/test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue.059ee77c1f. The enforcement gate does not become live until an operator-owned promotion advances the stable control runtime, perdocs/stable-runtime-promotion-runbook.mdlanded by this PR.[THREAD STATE LEDGER] PR #770 — merge completed and verified at merge commit
0568f44cWhat is true now:
0568f44cb2, merge timestamp 2026-07-20T12:36:59-05:00059ee77c1fWhat changed:
What is blocked:
Who/what acts next:
0568f44cb2Canonical Issue State
STATE: terminal_cleanup_complete for PR #770 / Issue #615
WHO_IS_NEXT: controller
NEXT_ACTION: select next queue item unrelated to #770/#615; do not reopen this pair
NEXT_PROMPT:
WHAT_HAPPENED: Supervised reconciler terminal cleanup for #770/#615 only. Gate Zero parity at
0568f44. Post-merge lease dry-run found no active lease. Author lease lease-13750394b96b4ce4 abandoned with dead-owner proof. Removed branches/merge-pr-770 via scripts/worktree-clean. Deleted remote/local feat/issue-615-runtime-mode-enforcement via gitea_cleanup_merged_pr_branch + safe local delete; verified_absent=true.WHY: Operator-requested supervised terminal cleanup after merge performed; release stale author lease and residual branch/worktree artifacts without review/merge replay or general audit.
RELATED_PRS: #770 (closes #615)
BLOCKERS: none
VALIDATION: gitea_assess_master_parity restart_required=false at 0568f44; gitea_cleanup_post_merge_moot_lease dry-run active_lease=null; gitea_inspect_workflow_lease status=abandoned; gitea_cleanup_merged_pr_branch verified_absent=true; control checkout clean
LAST_UPDATED_BY: prgs-reconciler / sysadmin / LLM_LOCK_ID=reconciler-770-615-be0c8bf5ed9e-1784569553
[THREAD STATE LEDGER] PR #770 / Issue #615 — terminal cleanup complete; no remaining live work for this pair
What is true now:
ab34280f90)0568f44cb2on prgs/master (Gate Zero parity)What changed:
ab34280, comment 13298): cleanup_allowed=false, nothing remainingWhat is blocked:
Who/what acts next: