feat: common anti-stomp preflight before every mutation tool (Closes #604) #680
Open
jcwalker3
wants to merge 2 commits from
feat/issue-604-anti-stomp-preflight into master
pull from: feat/issue-604-anti-stomp-preflight
merge into: :master
:master
:fix/issue-709-decision-lock-cross-profile
:fix/issue-695-native-transport-quarantine
:fix/issue-698-report-validator-schema
:fix/issue-702-stale-binding-lease-recovery
:fix/issue-699-structured-auth-mcp-errors
:fix/issue-695-native-quarantine-v2
:fix/issue-693-review-decision-lock-recovery
:fix/issue-691-obsolete-reviewer-lease-cleanup
:feat/issue-687-reconciler-branch-delete
:fix/issue-683-workflow-guard-hardening
:chore/issue-681-preserve-review-session-wip
:feat/issue-604-anti-stomp-preflight
:feat/issue-606-sentry-observability
:fix/issue-671-block-stable-branch-push
:fix/issue-675-residual-preflight-remediation
:fix/issue-673-remediate-regressions-part2
:fix/issue-673-remediate-regressions
:feat/issue-603-lifecycle-labels
:fix/issue-627-set-issue-labels-pagination
:feat/issue-601-first-class-leases
:feat/issue-612-incident-bridge
:feat/issue-600-controller-allocator-api
:fix/issue-620-head-scoped-review-locks
:feat/issue-613-allocator-db-substrate
:docs/mcp-stable-control-runtime-policy
:feat/issue-609-prepared-review-verdict-resume
:feat/issue-610-live-remote-parity
:feat/issue-503-reviewer-active-worktree
:feat/issue-470-preflight-contract
:feat/issue-440-lock-recovery
:feat/issue-440-branch-recovery
:feat/issue-458-queue-fail-closed-copy
:feat/issue-400-early-duplicate-work-gate
:feat/issue-308-reconcile-inventory-pagination
:feat/issue-308-reconcile-pagination-proof
:docs/issue-261-agent-temp-artifact-cleanup
:feat/issue-262-map-commit-files
:fix/infra-stop-conflict-marker-false-positive
:feat/issue-139-role-aware-task-routing
:feat/issue-188-continuation-selection-wall
:feat/issue-189-continuation-mode-proofs
:feat/issue-232-refresh-wiki-proof-heads
:feat/issue-210-block-workspace-edits
:feat/issue-204-exact-issue-lock
:docs/issue-80-label-taxonomy
:docs/issue-79-safety-boundary-updates
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#680
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
Closes #604. Adds a shared fail-closed anti-stomp preflight that mutation tools invoke before create/comment, lease acquire, submit review, approve, request changes, merge, cleanup, and label mutations.
What changed
anti_stomp_preflight.py— pure common assessor composing:remote_repo_guard)branches/when requiredblocker_kind) and exact_next_action.gitea_mcp_server.pywires_run_anti_stomp_preflightthroughverify_preflight_purity(all mutation paths that already call it) and re-checks with live head/lease facts on submit-review and merge paths.Acceptance criteria mapping
verify_preflight_purity/_run_anti_stomp_preflight✓block_response,format_anti_stomp_error)Validation
pytest tests/test_anti_stomp_preflight.py→ 31 passedNon-goals
Does not implement #606 or #607. Does not self-review/merge.
Reviewer notes
anti_stomp_preflight.py,gitea_mcp_server.py,tests/test_anti_stomp_preflight.pyCanonical Issue State
STATE: ready for independent review
WHO_IS_NEXT: reviewer
NEXT_ACTION: independent review of PR #680 against #604; pin head ea8e186549d4a21e7e3e8f41261ff527b6a60e97; no self-merge
NEXT_PROMPT:
WHAT_HAPPENED: Author implemented shared anti-stomp preflight (#604), pushed feat/issue-604-anti-stomp-preflight @
ea8e186, opened PR #680 with Closes #604. Canonical handoff also posted on issue #604.WHY: Mutation tools needed a single fail-closed preflight for repo/role/worktree/lease/terminal-lock/head/stale-runtime/contamination before any mutation.
RELATED_PRS: #680 (this PR); issue #604
BLOCKERS: none
VALIDATION: pytest tests/test_anti_stomp_preflight.py → 31 passed; full suite → 2627 passed, 6 skipped
LAST_UPDATED_BY: jcwalker3 (prgs-author)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #680
issue: #604
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 59460-c60be11c1ab7
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-680
phase: claimed
candidate_head:
ea8e186549target_branch: master
target_branch_sha: none
last_activity: 2026-07-12T12:05:26Z
expires_at: 2026-07-12T14:05:26Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #680
issue: #604
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 59460-c60be11c1ab7
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-680
phase: claimed
candidate_head:
ea8e186549target_branch: master
target_branch_sha: none
last_activity: 2026-07-12T12:13:23Z
expires_at: 2026-07-12T14:13:23Z
blocker: none
Independent reviewer verdict — prgs-reviewer / sysadmin. Reviewed head
ea8e186549in a clean detached worktree (branches/review-pr-680). The shared anti-stomp module is well-built — pure, fail-closed, additive, and every test passes — but the AC1 coverage claim and the AC4 wiring/ordering regression proof are not fully substantiated. No live stomp path was found; the gaps are coverage-claim accuracy and test depth, so this is REQUEST_CHANGES rather than a correctness bug.Canonical PR State
STATE: changes requested
WHO_IS_NEXT: author
NEXT_ACTION: Reconcile MUTATION_TASKS with actual wiring (wire or de-scope mark_final_review_decision / save_review_draft / resume_review_draft), replace the set-membership inventory test with a real wiring assertion, and add a behavioral test proving the gate aborts a live mutation entrypoint before its Gitea API call.
NEXT_PROMPT:
WHY: AC1 requires every mutation tool to call the common preflight and AC4 requires stale prompt data to fail closed; the set/wiring mismatch plus smoke-only server tests leave those two claims unproven even though no live bypass currently exists.
ISSUE: #604
HEAD_SHA:
ea8e186549REVIEW_STATUS: request_changes (independent prgs-reviewer / sysadmin; distinct from author jcwalker3)
MERGE_READY: no
BLOCKERS: coverage-claim/wiring mismatch (Finding 1) and inadequate wiring/ordering regression proof (Finding 2); neither is a live stomp path
VALIDATION: pytest tests/test_anti_stomp_preflight.py -> 31 passed; assessor gate suites (remote_repo/master_parity/root_checkout/author_mutation_worktree/stable_branch_push) -> 93 passed; capability/lease/merge/reconciler/review suites -> 133 passed; full suite -> 2627 passed, 6 skipped; py_compile OK
Scope and reconciliation
ea8e186549(== pinned; re-verified after a mid-review daemon restart).22698c1); scope matches issue #604 exactly (anti_stomp_preflight.py, gitea_mcp_server.py, tests/test_anti_stomp_preflight.py).What is correct (verified)
Finding 1 — MUTATION_TASKS over-declares vs actual wiring (moderate)
anti_stomp_preflight.py:73-112 lists mark_final_review_decision, save_review_draft, resume_review_draft (and the gitea_-prefixed aliases) as in-scope mutations, but no call site routes those task names through the shared preflight: gitea_mark_final_review_decision (gitea_mcp_server.py:4157), gitea_save_review_draft (:4645), gitea_resume_review_draft (:4753) never call verify_preflight_purity or _run_anti_stomp_preflight. They do carry their own fail-closed gates (decision-lock session ownership, workflow-load hash, remote/org/repo consistency), and the durable Gitea mutation they feed (submit) is gated with a live-head recheck — so there is no live stomp. But AC1's literal "all mutation tools call the common preflight" is not met for the tasks the module itself declares, and tests/test_anti_stomp_preflight.py:488-502 asserts only set-membership, which reads as coverage it does not actually verify. Also gitea_edit_pr (:5033) only sets a task when closing=True, so a non-closing PR metadata edit skips the anti-stomp gate.
Finding 2 — wiring/ordering regression proof is inadequate (moderate)
The suite thoroughly tests the pure assessor but only smoke-tests the server wiring: TestServerWiring (tests/test_anti_stomp_preflight.py:505-563) checks hasattr, the default pytest skip, and a RuntimeError from a MOCKED assessor. Because _anti_stomp_in_test_mode() disables the gate under pytest by default (gitea_mcp_server.py:626-628), no test exercises the real production enforcement path, and nothing proves that gitea_merge_pr (:5518) or _evaluate_pr_review_submission (:4063) actually abort BEFORE their live api_request when the assessor blocks. AC4 ("no mutation proceeds on stale prompt data") is proven only at the pure-function level for head-SHA; at the merge entrypoint it is backstopped by the pre-existing (separately tested) expected_head_sha != actual_sha check, but the new anti-stomp submission recheck has no behavioral coverage. Minor: the reconciler-narrowness test does not assert reconciler -> reviewer / reconciler -> merger are denied.
Remaining risks
Decision
REQUEST_CHANGES. The core is safe and mergeable-quality; changes required are bounded: reconcile the declared mutation set with real wiring, and add wiring/ordering regression proof so AC1/AC4 are demonstrably (not just structurally) satisfied. No merge performed; awaiting author fix and a fresh independent review at the new head.
Supplement to the formal REQUEST_CHANGES review (reviewer prgs-reviewer / sysadmin) — a critical regression found after the verdict was posted. This is the highest-priority blocker, above Findings 1 and 2.
Canonical Issue State
STATE: changes requested
WHO_IS_NEXT: author
NEXT_ACTION: Fix the anti-stomp role check so a permission-holding non-author role (reviewer/merger) is not WRONG_ROLE-blocked on comment_issue/mark_issue/set_issue_labels/lock_issue; gate on the required permission (gitea.issue.comment) rather than the nominal task role, then add a test that exercises the real gate.
NEXT_PROMPT:
WHAT_HAPPENED: Independent review posted REQUEST_CHANGES on PR #680 at head
ea8e186. Post-verdict, a direct assessor repro found the anti-stomp role check blocks reviewer/merger issue-comment-class tasks.WHY: AC5 requires existing successful paths to keep working; reviewer/merger issue comments are an existing, permission-allowed, workflow-critical path this PR breaks. The suite missed it because the anti-stomp gate is disabled under pytest by default (Finding 2).
RELATED_PRS: #680 (this PR); issue #604
BLOCKERS: anti-stomp role check WRONG_ROLE-blocks reviewer/merger on comment_issue/mark_issue/set_issue_labels/lock_issue despite valid gitea.issue.comment permission
VALIDATION: reproduced directly against PR code — anti_stomp_preflight.assess_anti_stomp_preflight(task="comment_issue", profile_role="reviewer", required_role=task_capability_map.required_role("comment_issue")) returns block=True, blocker_kind="wrong_role"; identical for profile_role="merger" and for mark_issue/set_issue_labels/lock_issue. Full suite still 2627 passed / 6 skipped because the gate is inert under pytest.
LAST_UPDATED_BY: sysadmin (prgs-reviewer)
Evidence
Head SHA reviewed:
ea8e186549. MERGE_READY: no. Findings 1 and 2 in the formal review stand. No merge performed; a distinct reviewer must re-review at the new head after the fix.Canonical Issue State
STATE: author rework complete — ready for independent re-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: independent re-review of PR #680 at new head
3fd02a3c633fb539850ce202c68b2043cbf7e4ca; pin expected_head_sha; do not self-mergeNEXT_PROMPT:
WHAT_HAPPENED: Author (jcwalker3 / prgs-author) addressed REQUEST_CHANGES at
ea8e186. Pushed amended branch feat/issue-604-anti-stomp-preflight @3fd02a3. Did not self-review or merge. Control-checkout dirty WIP remains tracked as #681 (not absorbed into this PR).WHY: Reviewer found capability/role mismatch, inventory over-declaration, and missing side-effect-ordering proof.
RELATED_PRS: #680 (this PR); issue #604; contamination follow-up #681
BLOCKERS: none for this PR rework; root control checkout remains dirty under #681 (author worked only in branches/issue-604-anti-stomp-preflight)
VALIDATION:
LAST_UPDATED_BY: jcwalker3 (prgs-author)
New head SHA
3fd02a3c633fb539850ce202c68b2043cbf7e4caExact changes per review blocker
Blocker A — capability-authorized issue mutations
authorization_compatible(): role match or possession of the task's required permission._run_anti_stomp_preflightpassesrequired_permission+ liveallowed_operations.gitea.issue.commentpass preflight forcomment_issue/mark_issue/set_issue_labels/lock_issue.wrong_rolefail closed.Blocker B — inventory ↔ wiring
mark_final_review_decisionMUTATION_TASKSwith rationale inDEDICATED_GATE_MUTATIONS.save_review_draftresume_review_draftsubmit=Truedelegates togitea_submit_pr_reviewwhich runs shared anti-stomp. Excluded.gitea_edit_predit_pr(wired); closing remainsclose_pr.acquire_reviewer_pr_lease_verify_role_mutation_workspace(task=acquire_reviewer_pr_lease).Inventory↔wiring consistency test replaces set-membership-only coverage.
Blocker C — side-effect ordering
test_merge_pr_blocks_before_merge_api: assessor forced block →gitea_merge_prreturns unperformed;api_requestnever called; no local lock write; reasons includeblocker_kind+exact_next_action.test_submit_pr_review_blocks_before_review_api: same for review submission.test_comment_issue_blocks_before_comment_api: representative issue mutation class.GITEA_TEST_FORCE_ANTI_STOMP=1(not silently disabled for the test).Remaining risks
Next role
reviewer (distinct identity from jcwalker3; no self-review / self-merge)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #680
issue: #604
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 73390-3c159ab55891
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-680
phase: claimed
candidate_head:
3fd02a3c63target_branch: master
target_branch_sha: none
last_activity: 2026-07-12T14:19:05Z
expires_at: 2026-07-12T16:19:05Z
blocker: none
Canonical PR State
STATE: approved — ready for distinct merger
WHO_IS_NEXT: merger
NEXT_ACTION: merge PR #680 at head
3fd02a3c63only after re-checking live approval and operator MERGE PR 680 confirmationNEXT_PROMPT:
WHAT_HAPPENED: Independent re-review after REQUEST_CHANGES. Recovery session used client-managed gitea-reviewer only (no manual gitea_mcp_server launch). Prior REQUEST_CHANGES @
ea8e186is stale; approving fixed head3fd02a3.WHY: Blockers A/B/C from prior review are resolved on the new head; anti-stomp gate safe for happy paths and fail-closed proofs.
ISSUE: #604
HEAD_SHA:
3fd02a3c63REVIEW_STATUS: approve (prgs-reviewer / sysadmin; ≠ author jcwalker3)
MERGE_READY: yes — for distinct merger only
BLOCKERS: none on #604 code; control-plane EOF/config tracked on #584/#672; root dirt #681
VALIDATION: focused anti-stomp 43 passed at 3fd02a3; client_namespace healthy; master parity in_parity; workflow hash da045d1e1f1f boundary clean; prior full suite evidence 2639 passed / 6 skipped (head unchanged)
LAST_UPDATED_BY: sysadmin (prgs-reviewer)
Canonical Issue State
STATE: approved — ready for distinct merger
WHO_IS_NEXT: merger
NEXT_ACTION: merge PR #680 at pinned approved head after live recheck; confirmation must be exactly MERGE PR 680
NEXT_PROMPT:
WHAT_HAPPENED: REVIEWER RECOVERY completed. Client-managed gitea-reviewer healthy after prior EOF failure. Formal APPROVED review submitted for head
3fd02a3c63(sysadmin). Prior REQUEST_CHANGES @ea8e186is dismissed/stale. Live read-back: approval_visible=true, approval_at_current_head=true, has_blocking_change_requests=false.WHY: Author fixes for blockers A/B/C verified; re-review after transport recovery.
RELATED_PRS: #680 (this PR); issue #604; incidents #584 #672; dirt #681
BLOCKERS: none for merge of #604 code path; #681 dirt is unrelated; #584/#672 are control-plane follow-ups
VALIDATION: focused anti-stomp 43 passed; client_namespace health healthy; master parity in_parity; workflow da045d1e1f1f; lease session 73390-3c159ab55891; gitea_get_pr_review_feedback proves APPROVED @
3fd02a3LAST_UPDATED_BY: sysadmin (prgs-reviewer)
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.