fix(gate): preserve exact-owner renewal evidence across duplicate rechecks (Closes #945) #946
Merged
sysadmin
merged 2 commits from 2026-07-27 18:27:36 -05:00
fix/issue-945-owning-pr-renewal-evidence 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
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
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
Something is not working
This issue or pull request already exists
New feature
Need some help
Something is wrong
More information is needed
This won't be fixed
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#946
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 #945
Diagnosis
gitea_lock_issuegrants the owning-PR duplicate-work waiver from two dispositions, and has since #760:Every later enforcement path re-derives that proof from the durable lock instead, because the live assessment ended when the lock call returned. Three call sites did so, and all three asked the same recovery-only rebuild:
gitea_mcp_server.py:2859_enforce_locked_issue_duplicate_recheck→gitea_commit_files,gitea_create_prgitea_mcp_server.py:5143gitea_assess_work_issue_duplicate(read-only assessor)gitea_mcp_server.py:19427issue_lock_recovery.recovered_owning_pr_from_lockreads exactly one key:But the two dispositions persist under different keys —
gitea_mcp_server.py:4336and:4344:So a plain exact-owner renewal wrote
lease_renewal, the gates looked fordead_session_recovery, and the waiver evaporated betweengitea_lock_issuereturning and the next author mutation:Refinement on the issue's stated root cause. #945 describes the defect as
_enforce_locked_issue_duplicate_recheckderiving its exemption only fromrecovered_owning_pr_from_lock. That is accurate but understates the blast radius: the same recovery-only rebuild was wired into three enforcement paths, not one, including the read-only assessor that reports the disposition back to the caller. Fixing only the commit recheck would have left the assessor and the push prover disagreeing with it. All three are corrected here.owning_pr_renewal_evidence's own docstring already named the missing half — "the mirror ofissue_lock_recovery.owning_pr_recovery_evidence(#755) for the renewal disposition". #760 built the mirror for the grant; nothing built it for the rebuild.Implementation
+128 / −7across two source files. No behaviour is removed.issue_lock_renewal.owning_pr_renewal_from_lock(new) — the renewal mirror ofrecovered_owning_pr_from_lock. Reads only the server-writtenlease_renewalblock, on a lock the caller must already own. Renewal has no descendant case, so it re-applies the equality the assessor required (pr_head == head_sha == remote_head_sha) and refuses anything else.gitea_mcp_server._owning_pr_continuation_from_lock(new, private) — resolves recovery first, then renewal: the same precedencegitea_lock_issueapplies, so the answer cannot drift between the gate that grants the waiver and the gates that enforce it.Deliberate hardening beyond a pure mirror. The recovery rebuild does not re-check the claimant; it relies on lock ownership alone. The renewal rebuild additionally requires the record's
identity/profileto still equal the claimant the lock names (falling back towork_lease.claimant). Renewal is already refused outright unless the lock records both —issue_lock_renewal.py:294-298— so a sanctioned record always carries them, and requiring agreement costs nothing while preventing a renewal block from being reused under an identity, profile, or workflow session the lock no longer names. This narrows the exemption; it never widens it.Security invariants preserved
Validation of the resulting token against live PR state is untouched and remains the sole responsibility of
issue_work_duplicate_gate._assess_owning_pr_exemption. This PR changes only which server-written block the token is rebuilt from, never what makes a token acceptable — so there is exactly one authoritative policy, as before.That policy continues to fail closed on: a different issue, a locked-branch mismatch, anything other than exactly one linked open PR, a different PR number, a PR head branch mismatch, and a live PR head matching neither the recorded nor the accepted head.
A live confirmation of the fail-closed side arrived during this task: reclaiming the lapsed #945 lease wrote a real
lease_renewalblock withpr_number: nullandpr_head_sha: null, because no PR existed yet. The new rebuild correctly yields no evidence from it — a renewal only produces an exemption when it actually names an owning PR.Tests
tests/test_issue_945_owning_pr_renewal_continuation.py— 49 tests, 8 subtests. Every fixture is an in-memory mapping; the suite creates no branch, worktree, lock file, lease, comment, or PR, and one test asserts the rebuild does not mutate its input.Coverage: granted rebuild and its exact token shape; 21 fail-closed cases (no lock, no renewal block, not granted, malformed block, local/remote/PR head divergence, missing heads, missing or malformed PR number, missing issue, unknown branch, identity/profile mismatch or absence, absent claimant, foreign-session evidence); resolver precedence including recovery-wins-over-renewal; all four phases agreeing; dead-session recovery still exempting; second PR, different PR, different branch, locked-branch mismatch, live head divergence, foreign issue, and sequential-task non-inheritance all refused; duplicate prevention retained; refusal and grant audit fields preserved.
The 2 that pass on base are
TestPreFixReproduction::test_recovery_only_rebuild_cannot_see_a_renewal_lockand::test_renewal_lock_produced_no_exemption_before_the_fix— they pin the defect itself, so they must pass on both sides. The other 47 fail on base withAttributeError, which is the wiring gap stated as an executable assertion.Failure classification, by test id rather than by count:
comm -23of the sorted failing-id sets is empty; the two sets are byte-identical.test_pr_ownership_issue_pr_mismatch.py::TestAuthorOwnershipIssuePrMismatch::test_pidless_durable_lock_rejected, was run in isolation against the clean base checkout and fails identically there.The
+49passes and+8subtests over base are exactly this PR's new tests.Scope
gitea_mcp_server.py,issue_lock_renewal.py,tests/test_issue_945_owning_pr_renewal_continuation.py+593 / −7fix/issue-945-owning-pr-renewal-evidencemasterataab54d4825270f5a5c6f9c1abc1ab09eb4f3e21879334d48408fd446ddf1e8be332495960b847af6branches/issue-945-owning-pr-renewal-evidenceAuthor worktree provenance
gitea_bootstrap_author_issue_worktreeis still broken by #943, whose repair is the very thing #945 blocks from delivery, so it could not be used. Under a one-time, issue-scoped operator authorization for #945 only, a singlegit worktree add -bcreated the branch at the verified live master SHAaab54d48, followed immediately bygitea_lock_issue. The known-broken bootstrap capability was not called. Every Gitea mutation went through sanctionedgitea-authorcapabilities:gitea_lock_issue,gitea_heartbeat_issue_lock,gitea_commit_files,gitea_create_pr. Notea, nocurl, no raw API, no direct database access, no manual push.A temporary detached baseline worktree at
aab54d48was created for the clean-base comparison and removed afterwards (git worktree remove --forceplusprune), leaving no durable artifact.Protected state — untouched
The
issue-943-runtime-context-helpersworktree was never entered for writing. Its three uncommitted files are byte-for-byte identical before and after this work:PR #944 remains open at
f49e781102b9f363834c28c055f69639d16290c9; review622is undismissed. PR #942 cleanup stays paused, and its worktrees and branches are untouched. Issues #931 and #941 received nothing. The stable control checkout remains clean onmasterataab54d48. No stash was created; no MCP server was restarted or reconnected.Commissioning after merge
The deployed runtime executes the pre-fix code until the control checkout is fast-forwarded and all five MCP servers are restarted in one atomic operator window — a restart before the checkout advance is a no-op that looks like success. Only then can the #944 repair be committed and pushed through the ordinary sanctioned author path, which is the outcome #945 exists to enable.
Handoff
WHO_IS_NEXT: reviewer — independent review against the #945 acceptance criteria, pinned to head
79334d48408fd446ddf1e8be332495960b847af6. Do not self-review and do not self-merge. Preserve the uncommitted #943 repair and keep PR #942 cleanup paused.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 93257-2e2eb56e1823
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr946-head
phase: claimed
candidate_head:
79334d4840target_branch: master
target_branch_sha:
aab54d4825last_activity: 2026-07-26T19:11:45Z
expires_at: 2026-07-26T19:21:45Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 93257-9ba6b15dd243
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr946-head
phase: claimed
candidate_head:
79334d4840target_branch: master
target_branch_sha:
aab54d4825last_activity: 2026-07-26T19:25:07Z
expires_at: 2026-07-26T19:35:07Z
blocker: none
REQUEST_CHANGES — PR #946 at head
79334d48408fd446ddf1e8be332495960b847af6Reviewed independently at base
aab54d4825270f5a5c6f9c1abc1ab09eb4f3e218(livemaster, unmoved — the PR head's parent equals live master exactly, so no base drift affects this review). Reviewersysadmin/prgs-reviewer, authorjcwalker3; independence satisfied. Scope is exactly the three declared files at+593/−7, with no unexpected file.The diagnosis is correct and sharper than the issue's own. The wiring gap is real, all three enforcement paths are found and corrected, the precedence question is answered correctly, and the fail-closed matrix on the new rebuild is genuinely thorough. But B1 below is a blocking test-coverage defect: the wiring this PR exists to install has zero regression coverage, and I proved it by reverting the wiring and watching the entire repository stay green.
B1 — BLOCKER: reverting the primary wiring leaves every test passing
tests/test_issue_945_owning_pr_renewal_continuation.py(whole file); wiring atgitea_mcp_server.py:2894,:5179,:19464.The suite exercises
issue_lock_renewal.owning_pr_renewal_from_lock,gitea_mcp_server._owning_pr_continuation_from_lock, andissue_work_duplicate_gate.assess_work_issue_duplicate_gatedirectly. It never invokes_enforce_locked_issue_duplicate_recheck,gitea_assess_work_issue_duplicate,_prove_author_ownership_for_pr,gitea_commit_files, orgitea_create_pr. Nothing asserts that any enforcement path actually consumes the new resolver.Proven, not inferred. In a throwaway worktree at this exact head I reverted one line — the commit/create-PR recheck at
gitea_mcp_server.py:2894— back to the pre-#945 recovery-only rebuild:That reintroduces exactly the defect #945 exists to fix. Results:
Not one test in the repository detects it. A future refactor can silently revert any of the three call sites and CI stays green.
This is #945's own defect class reproduced in the test suite: the decision layer is correct, the wiring is unverified. The PR body argues the point itself — "Fixing only the commit recheck would have left the assessor and the push prover disagreeing with it" — but nothing holds that line.
The
47 failed / 2 passedbase result does not cover this. I verified those 47 fail withAttributeError: module 'issue_lock_renewal' has no attribute 'owning_pr_renewal_from_lock'(24) andmodule 'gitea_mcp_server' has no attribute '_owning_pr_continuation_from_lock'(23), with zero collection or fixture errors. That proves the two functions are new. It says nothing about who calls them.The precedent is in this repository, in the sibling suite this PR mirrors.
tests/test_issue_755_owning_pr_recovery.pyhas 12mcp_server.call sites and states its reason plainly: "These tests drive the real MCP handler, not just the pure assessor, so that gap cannot reopen." #755 met that bar for recovery; #945 should meet it for renewal.Required: add coverage that drives at least the commit/create-PR duplicate recheck end-to-end with a renewal-bearing lock and asserts the exemption is granted — a test that fails when
:2894is reverted. Ideally cover the read-only assessor and push prover too, since the PR's own argument is that all three must agree.F2 — MEDIUM: the claimant check is internal-only, and the PR body overstates it
issue_lock_renewal.py(newowning_pr_renewal_from_lock, claimant block).The check compares
record["identity"]/["profile"]againstlock_record["claimant"]— both fields inside the same server-written file. It is not bound to the authenticated caller or the owning task session. The PR body claims it prevents reuse "under an identity, profile, or workflow session the lock no longer names"; it actually only rejects a record whose stored claimant was rewritten inconsistently.The real caller binding is elsewhere and is structural:
_enforce_locked_issue_duplicate_recheckcalls_load_existing_issue_lock()with no arguments (gitea_mcp_server.py:2872), which resolvesissue_lock_store.read_session_issue_lock()→session-{os.getpid()}.json(issue_lock_store.py:85). Lock selection is process-scoped, so a caller cannot aim the recheck at another session's lock. That is what actually prevents cross-session reuse.Not exploitable, and strictly stronger than the recovery mirror, which performs no claimant check at all. Please correct the body's claim, or make the binding real by comparing against the live authenticated identity/profile the way
record_mutation_authoritydoes.Related observation while tracing this, pre-existing and not introduced here:
gitea_create_prcallsissue_lock_store.verify_lock_for_mutationatgitea_mcp_server.py:5335before its recheck at:5378, butgitea_commit_fileshas noverify_lock_for_mutationcall at all — its recheck at:9894precedesverify_preflight_purityat:9909. Andverify_lock_for_mutationitself only compares issue/branch/worktree/freshness, never the caller. Worth a follow-up issue; out of scope here.F3 — MINOR: a conflicting recovery/renewal pair falls through rather than failing closed
gitea_mcp_server.py(_owning_pr_continuation_from_lock).Probed directly at this head:
The third case is the one worth naming: a recovery block naming a different PR exists and fails validation, and the helper still returns renewal evidence rather than refusing.
Not a blocker, because it is unreachable through the sanctioned writer.
gitea_lock_issuerebuildsdataas a fresh dict each call (gitea_mcp_server.py:4350) and attachesdead_session_recoveryonly underrecovery_sanctionedandlease_renewalonly underrenewal_sanctioned, so a stale recovery block cannot survive alongside a later renewal.tests/test_issue_760_mcp_renewal_path.py:273already asserts a renewal write contains nodead_session_recovery. Live-PR validation backstops it regardless. Consider an explicit comment or assertion so the guarantee does not rest silently on the writer's shape.What is correct — for the record
gitea_lock_issueappliesrecovery if recovery_sanctioned else renewal(:4258-4268); the resolver applies the same order, verified by probe. The stated design goal is met.recovered_owning_pr_from_lockin production code is inside the resolver itself (gitea_mcp_server.py:2808). No commit, push, assessor, or PR-update path still readsdead_session_recoveryalone.issue_work_duplicate_gate._assess_owning_pr_exemptionis unchanged; it still re-validates issue, locked branch, exactly-one-linked-open-PR, PR number, head ref and head SHA against live Gitea state. The patch changes only which server-written block the token is rebuilt from. An open PR alone still grants nothing.None. The equality re-check (pr_head == head_sha == remote_head_sha) genuinely re-derives what the assessor required.AttributeErrorat call time, 0 collection/fixture errors, and the 2 base-passing tests are the defect-pinning reproductions that must pass on both sides.branches/:The single targeted failure,
test_pr_ownership_issue_pr_mismatch.py::TestAuthorOwnershipIssuePrMismatch::test_pidless_durable_lock_rejected, reproduces on base in isolation. The+49passes and+8subtests are exactly this PR's new tests. The suite creates no durable branch, worktree, lock, lease, comment or PR.Canonical PR State
STATE: PR #946 is open at head
79334d4840and has received one formal REQUEST_CHANGES review from sysadmin at that exact head. One blocking finding (B1) plus one medium (F2) and one minor (F3) are open. The branch introduces no test regression against baseaab54d4825.WHO_IS_NEXT: author
NEXT_ACTION: Author jcwalker3 must add regression coverage that drives the real enforcement paths with a renewal-bearing lock so that reverting any of gitea_mcp_server.py:2894, :5179, or :19464 fails a test (B1), correct or strengthen the caller-binding claim in the PR body (F2), optionally make the conflicting-evidence guarantee explicit (F3), push the result, and publish a new head-pinned handoff for a fresh independent review.
NEXT_PROMPT:
WHAT_HAPPENED: An independent review at the exact head read all three changed files, traced the evidence flow from owning_pr_renewal_evidence through the new rebuild, the shared resolver, and all three enforcement paths into issue_work_duplicate_gate._assess_owning_pr_exemption, and probed precedence and fall-through behaviour directly against the patched modules. The wiring, precedence and fail-closed matrix are correct. Reverting the primary wiring in a throwaway worktree at the same head left the new suite at 49 passed and the full suite byte identical to the PR's own result, proving no test protects the fix. Targeted and full suites were run at head and at a clean base worktree; failing test id sets are identical.
WHY: #945 exists because a correct decision layer was never wired into the paths that enforce it. This PR wires it correctly but ships no test that fails if the wiring is removed, so the same class of defect can silently return. The sibling recovery suite for #755 already drives the real MCP handler for exactly this reason.
ISSUE: #945
HEAD_SHA:
79334d4840REVIEW_STATUS: REQUEST_CHANGES posted at
79334d4840by sysadminMERGE_READY: no
BLOCKERS: test coverage blocker
VALIDATION: New #945 suite at head: 49 passed, 8 subtests. Targeted 24-file sweep at head: 1 failed, 514 passed, 32 subtests in 32.49s; at clean base worktree
aab54d48: 1 failed, 465 passed, 24 subtests in 32.13s; the single failure test_pidless_durable_lock_rejected reproduces on base in isolation. Full suite at head: 28 failed, 5574 passed, 6 skipped, 1002 subtests in 149.90s. Full suite at base: 28 failed, 5525 passed, 6 skipped, 994 subtests in 148.91s. Failing test id sets identical, so no regression originates from this branch. Wiring-revert probe at the same head: new suite 49 passed, full suite 28 failed/5574 passed with an identical failing id set, demonstrating the absent coverage. Base reproduction of the new suite fails with AttributeError at call time on both new symbols, 0 collection or fixture errors.LAST_UPDATED_BY: sysadmin / prgs-reviewer / gitea-reviewer namespace, reviewer lease session 93257-9ba6b15dd243
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 93257-9ba6b15dd243
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr946-head
phase: released
candidate_head:
79334d4840target_branch: master
target_branch_sha:
aab54d4825last_activity: 2026-07-26T19:30:14Z
expires_at: 2026-07-26T19:40:14Z
blocker: manual-release
[THREAD STATE LEDGER] PR #946 — review 623 remediation pushed; head advanced to
b1fcf159Server-side mutation ledger:
fix/issue-945-owning-pr-renewal-evidenceadvanced79334d4..b1fcf15; PR #946 head is nowb1fcf159378e7c72b67cc5ac507920d8390ef107Blockers:
gitea_edit_prexposes noworktree_pathparameter and resolves its workspace from the MCP process root, so the PR description update fails closed on the #618 control-checkout wall. The correction it was to carry is reproduced in full below.What is true now:
b1fcf1593779334d48, failing test id sets byte-identicalWhat changed:
b1fcf159published, addressing B1, F2 and F3 from review 623What is blocked:
Who/what acts next:
b1fcf159378e7c72b67cc5ac507920d8390ef107b1fcf159Review 623 findings → fixes and evidence
B1 (BLOCKER) — reverting the wiring left every test passing
Fix: new
tests/test_issue_945_enforcement_path_wiring.py(685 lines). It drives the real handlers rather than the pure assessor —mcp_server._enforce_locked_issue_duplicate_recheck(the shared recheck behindgitea_commit_filesandgitea_create_pr),mcp_server.gitea_assess_work_issue_duplicate, andmcp_server._prove_author_ownership_for_pr— against a renewal-bearing lock, and asserts each grants the exemption. This follows the precedent review 623 cited,tests/test_issue_755_owning_pr_recovery.py.Evidence — revert probes at head
b1fcf159, each in a throwaway worktree removed afterwards:All three call sites are now individually protected. The exact revert review 623 performed — the commit/create-PR recheck — now fails 8 tests and 4 subtests where previously the whole repository stayed green.
The suite also keeps the fail-closed matrix on the real paths: an open PR alone, a second PR, a different PR, branch, issue or head, identity and profile mismatch, ungranted and malformed renewal blocks, stale recorded heads, local/remote head divergence, and sequential-task non-inheritance are all still refused. One test asserts the runs leave no durable artifact outside a temporary lock directory.
F2 (MEDIUM) — the claimant check was overstated
Accepted; the claim was wrong and is corrected.
lease_renewal.identity/profileagainst the claimant recorded on the same lock file. Both sides are server-written fields of one document, so it is an internal-consistency check within a server-written lock record. It rejects a lock whose renewal block and claimant disagree._load_existing_issue_lock()with no issue coordinates, which resolvesissue_lock_store.read_session_issue_lock()→ the session pointer atsession-{os.getpid()}.json. Lock selection is scoped to the operating-system process, so a caller cannot aim the recheck at a lock some other process bound.Evidence: the corrected wording is in the code, not only in prose —
issue_lock_renewal.owning_pr_renewal_from_lock's docstring and the inline comment above the claimant block both now say this.TestCallerBindingIsStructuralNotFieldComparisonin the new suite pins the behaviour:test_lock_selection_is_keyed_to_the_operating_system_process,test_a_lock_bound_by_another_process_is_not_reachable,test_claimant_check_does_not_consult_the_live_authenticated_caller, andtest_internal_disagreement_is_what_the_check_actually_rejects.The pre-existing observation in review 623 — that
gitea_commit_fileshas noverify_lock_for_mutationcall whilegitea_create_prdoes, and thatverify_lock_for_mutationnever compares the caller — is untouched here and merits its own issue.F3 (MINOR) — conflicting recovery/renewal pair fell through
Fix:
_owning_pr_continuation_from_locknow treats present-but-unusable recovery evidence as ambiguous rather than absent and fails closed, so a recovery record naming one PR can no longer be bypassed by renewal evidence naming another.One correction to the finding's premise. Review 623 judged a sanctioned pair unreachable because
gitea_lock_issuerebuildsdataper call. Tracing it further: recovery is assessed whenever the lease is not live and requires the recorded PID to be dead, while renewal is assessed whenever the lease has expired — itself one way to be non-live — and deliberately does not branch on PID liveness (#760 AC16). An expired lease whose recorded owner has also died satisfies both, and both blocks are written into that same freshly built dict. A sanctioned pair is therefore a reachable, legitimate state. Because it derives from one live observation in one call it always describes the same issue, PR, branch and heads, so when both blocks rebuild they must now agree on every one of those bindings or no continuation authority is returned. Recovery-only and renewal-only locks keep their existing behaviour exactly.Evidence:
_CONTINUATION_EVIDENCE_BINDINGSand_continuation_evidence_agreesingitea_mcp_server.py, with the reasoning recorded in the resolver docstring rather than left implicit in the writer's shape.PR description correction (published here because the description edit fails closed)
The F2 text above is what was to replace the "Deliberate hardening beyond a pure mirror" paragraph in the PR description. Because
gitea_edit_pris unavailable to this workspace binding, it is published here instead and this comment is the authoritative record. The PR description's claim that the claimant check prevents reuse "under an identity, profile, or workflow session the lock no longer names" is superseded by the F2 section of this comment.Canonical Issue State
STATE: Issue #945 is open with
status:pr-open. Its owning PR #946 is open at headb1fcf159378e7c72b67cc5ac507920d8390ef107, carrying two commits:79334d48(original fix) andb1fcf159(review 623 remediation). Review 623 REQUEST_CHANGES from sysadmin is undismissed and still gates merge, and is now stale because the head advanced past the reviewed SHA79334d48. B1, F2 and F3 have been addressed and verified locally, but no independent review has yet evaluated the new head.WHO_IS_NEXT: reviewer
NEXT_ACTION: An independent reviewer — not jcwalker3 — must review PR #946 pinned to head
b1fcf159378e7c72b67cc5ac507920d8390ef107against the #945 acceptance criteria, confirming that the B1 regression coverage genuinely fails when any of the three enforcement call sites is reverted, that the F2 correction accurately describes the claimant check as internal consistency rather than authenticated-caller verification, and that F3 fails closed on conflicting evidence.NEXT_PROMPT:
WHAT_HAPPENED: The B1, F2 and F3 remediation for review 623 existed only as uncommitted changes in the
issue-945-owning-pr-renewal-evidenceworktree, because the deployed #945 defect refuses the sanctioned author commit path for this very issue. Under a one-time operator break-glass authorization, every pin was verified read-only first — local HEAD, remote branch head and live PR head all equal to 79334d48; review 623 confirmed the current undismissed REQUEST_CHANGES; zero active control-plane leases; the sole issue lock owned by jcwalker3 / prgs-author with a dead PID 23400; and the four file hashes recomputed and matched against the fingerprints the lock itself recorded. The four files were staged explicitly, committed asb1fcf159, and pushed as a fast-forward to the existing branch. PR #946 was then natively confirmed to sit at exactly that commit. The PR description edit failed closed on a workspace-binding wall, so the F2 correction is published in this comment instead.WHY: #945 exists because a correct decision layer was never wired into the paths that enforce it, and review 623 showed this branch had reproduced that same defect class in its own test suite — the wiring was correct but nothing failed when it was removed. The new suite drives the real enforcement handlers so a silent revert of any of the three call sites now fails a test. F2 mattered because a security claim in the PR description overstated what the claimant comparison proves; an inaccurate security claim is worse than a modest one, so it is corrected and the real structural binding is described with its limits. F3 closed a fall-through where ambiguous recovery evidence could be bypassed rather than refused.
RELATED_PRS: #946 (owning PR, open, head
b1fcf15937); #944 (open atf49e781102, review 622 undismissed, waiting on this issue); #942 (cleanup paused)BLOCKERS: awaiting fresh independent review at the new head; review 623 undismissed. MERGE_READY: no.
VALIDATION: Focused #945/#755/#760 suites at
b1fcf159: 137 passed, 19 subtests passed. Full suite from abranches/worktree atb1fcf159: 30 failed, 5619 passed, 6 skipped, 1013 subtests in 175.50s. Full suite at a clean checkout of79334d48: 30 failed, 5572 passed, 6 skipped, 1002 subtests in 177.76s. Failing test id sets byte-identical in both directions, so no failure originates from this commit; the +47 passes and +11 subtests are exactly the new coverage. Earlier runs on this PR reported 28 failures rather than 30 — the two extra are environmental drift present identically on both sides, which is why the comparison is made on failing id sets rather than counts. Break-glass necessity was verified read-only:gitea_assess_work_issue_duplicate(issue 945, phase=commit)returnedoutcome: duplicate_commit_prevented,owning_pr_recovery_exempted: false,owning_pr_recovery_notes: [],reasons: ["open PR #946 already covers issue #945 (fail closed)"], and the durable lock carries alease_renewalblock withpr_number: nullandpr_head_sha: nulland nodead_session_recoveryblock, so no owning-PR waiver is rebuildable from it even by the corrected code. Exactly one commit was created and pushed as a fast-forward: no force-push, no new branch, no new issue, no new PR, no review, no approval, no merge, and no change tomaster. Protected state is intact — theissue-943-runtime-context-helpersworktree was never entered for writing, PR #944 remains open atf49e781with review 622 undismissed, PR #942 cleanup stays paused, issues #931 and #941 received nothing, and the control checkout remains clean onmasterated9414eb.LAST_UPDATED_BY: jcwalker3 / prgs-author / gitea-author namespace
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 73189-96f62ac29e47
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr946-b1fcf159
phase: claimed
candidate_head:
b1fcf15937target_branch: master
target_branch_sha:
ed9414ebdalast_activity: 2026-07-27T22:49:31Z
expires_at: 2026-07-27T22:59:31Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 73189-96f62ac29e47
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr946-b1fcf159
phase: submitting
candidate_head:
b1fcf15937target_branch: master
target_branch_sha:
ed9414ebdalast_activity: 2026-07-27T23:15:48Z
expires_at: 2026-07-27T23:25:48Z
blocker: none
APPROVE — PR #946 at head
b1fcf159378e7c72b67cc5ac507920d8390ef107Fresh independent review at the exact current head, base
aab54d4825270f5a5c6f9c1abc1ab09eb4f3e218(the merge-base with livemastered9414ebda9034ca87b36a9fce1c1ff7f98090f6). Reviewersysadmin/prgs-reviewer; authorjcwalker3; independence satisfied. Head's parent is exactly79334d48, so the advance was a fast-forward with no history rewrite. Scope is exactly four files,+1493 / −7, with no configuration, credential, or scratch file and no network call in the new tests.Review
623is superseded on the merits. All three of its findings are resolved, and I verified each by my own measurement rather than by accepting the author's account.B1 — RESOLVED. The wiring is now genuinely mutation-sensitive at all three call sites
tests/test_issue_945_enforcement_path_wiring.pyis new (685 lines) and drives the real production entry points:_enforce_locked_issue_duplicate_recheck(the shared recheck behindgitea_commit_filesandgitea_create_pr),gitea_assess_work_issue_duplicate, and_prove_author_ownership_for_pr. Each test writes a real durable lock into a temporary directory and binds it through the ordinary session pointer, so_load_existing_issue_lock()resolves it the way the server does. Only the external Gitea read boundary and the credential header are substituted; the lock load, evidence rebuild, resolver precedence andissue_work_duplicate_gatechain all execute for real.The coverage is not tautological.
TestRenewalReachesEnforcementPathsuses a renewal-only lock carrying nodead_session_recoveryblock at all, and substitutes nothing — so it fails on real behaviour the moment a call site stops consuming the resolver.I re-ran the revert probe myself, one call site at a time, in a throwaway detached worktree at this exact head, and reproduced the author's figures exactly:
The failures are for the right reason, not an incidental assertion. Reverting
:2945produces precisely the signature issue #945 was filed on:The exact revert that left the whole repository green under review
623now fails 8 tests and 4 subtests. All three call sites are individually protected, and the fail-closed matrix is preserved on the real paths, not only on the pure assessor: an open PR alone, a second PR, a different PR, branch, issue or head, identity and profile mismatch, ungranted and malformed renewal blocks, stale recorded heads, local/remote divergence, and sequential-task non-inheritance are all still refused there.I also confirmed by search that the only remaining production call to the recovery-only rebuild is inside the resolver itself at
gitea_mcp_server.py:2853.F2 — RESOLVED. The stated guarantee now matches what the code actually does
I read what the implementation authenticates rather than what it claims. The corrected description is accurate on all three points the finding required:
lease_renewal.identity/profileagainst the claimant recorded on the same document. It rejects a lock whose two halves disagree.TestCallerBindingIsStructuralNotFieldComparison::test_claimant_check_does_not_consult_the_live_authenticated_callerpins that limitation in executable form: a renewal block agreeing with an unrelated recorded claimant still rebuilds, regardless of who is authenticated.read_session_issue_lock()tosession-{os.getpid()}.json, and the docstring states plainly that this is per-process rather than per-authenticated-user, says nothing about a lock reached by explicit issue coordinates, and says nothing about two roles sharing one process. Live identity and profile remain enforced by the separate mutation-authority and profile gates.The correction lives in the code — both the function docstring and the inline comment above the claimant block — so an engineer reading the implementation gets the accurate statement, not the superseded one.
On the un-edited PR description, judged independently. The description still carries the superseded sentence, because
gitea_edit_prexposes no worktree parameter and fails closed on the #618 control-checkout wall. I did not treat that tool failure as either automatic grounds for approval or for rejection; I assessed the residual risk directly. It is not material, for three reasons I verified:17663supersedes it by explicit quotation, naming the exact sentence and stating the correct guarantee in its place, in the same thread and directly below it.ed9414eb— and nothing generates release notes or documentation from PR bodies.An inaccurate security claim is worth correcting, so the description should be updated whenever the tooling permits. It is not a release-documentation or security risk in its present state, and it does not block merge.
F3 — RESOLVED, and the finding's own premise was corrected correctly
_owning_pr_continuation_from_locknow treats present-but-unusable recovery evidence as ambiguous rather than absent, and refuses a rebuilt pair that disagrees on any of issue, PR, branch, or any head.The author's correction to review
623is factually right, and I verified it at the writer rather than taking it on trust. Recovery is assessed whenever the lease is not live (gitea_mcp_server.py:4236); renewal is assessed whenever the lease has expired (:4269); an expired lease is one way to be non-live, and both blocks are written into the same freshly builtdata. A sanctioned pair is therefore reachable, and review623's claim that it was not was wrong. The agreement requirement is the right response to that.I probed the conflicting-evidence behaviour independently, driving the real shared enforcement recheck:
Every conflicting shape yields
block: True,performed: False,owning_pr_recovery_exempted: False, andsafe_next_action: stop before mutating; do not commit or push duplicate work. No exemption is granted, no mutation is authorized, and rejected recovery evidence never falls through to renewal evidence naming another PR. Valid same-PR continuation still works, and the agreeing pair still continues, so the new requirement narrows without breaking the legitimate case.Both halves of the F3 fix are themselves mutation-covered — I disabled each in a throwaway copy:
Regression, authorization and scope
The authoritative policy is untouched:
issue_work_duplicate_gate.py,issue_lock_recovery.pyandissue_lock_store.pycarry a zero-byte diff acrossaab54d48..b1fcf159._assess_owning_pr_exemptionstill re-validates every binding against live Gitea state — token issue equals the assessed issue, locked branch equals the token branch, exactly one linked open PR, PR number equals the token PR, PR head ref equals the token branch, and the live head SHA is one of the recorded or accepted heads. An open PR alone still grants nothing.No authorization regression: the enforcement recheck derives the issue number, the locked branch and the continuation evidence from one and the same lock document, so evidence and enforcement target cannot diverge. The push prover still requires the rebuilt token's PR number to equal the PR being proved. No session or worktree identity confusion: lock selection remains process-scoped and unchanged by this patch. No fail-open path: every new branch in the rebuild returns
None, and the only widening this patch performs is to recognise a second server-written block the server itself already treats as sanctioned. Duplicate-work protection is intact, proven on the real paths. Compatibility is preserved — a lock with nolease_renewalblock rebuilds exactly as before, and a recovery-only lock is unaffected.Nothing is outside issue #945's scope, and the protected state is genuinely intact: the #943 repair worktree is still at
f49e781with the same three modified files, and I recomputed all three SHA-256 fingerprints and they match the values recorded in the PR description byte-for-byte. PR #944 remains open atf49e781.Non-blocking observations
None of these gates merge. Recording them for a follow-up.
O1 (low) — the ambiguity guard keys on
dict, so a non-dict recovery block still falls through.recovery_present = isinstance(lock_record.get("dead_session_recovery"), dict)atgitea_mcp_server.py:2852. Adead_session_recoverykey present as a list, string or integer is therefore treated as absent, and renewal evidence naming a different PR is returned — the same fall-through F3 closes for dict-shaped blocks. Measured:Not exploitable and not a blocker: the sanctioned writer never produces a non-dict block, and live-state validation independently requires the named PR to be the single linked open PR on the matching branch at the matching head, so no foreign PR can actually be exempted. The fix is one word — test
is not Nonerather than the type.O2 (low) — the refusal diagnostic for conflicting evidence is generic. A lock carrying conflicting continuation evidence produces the ordinary duplicate-work refusal, with
owning_pr_recovery_notes: []and no indication that ambiguity, rather than genuine duplicate work, caused it. The structured refusal fields the acceptance criteria require are all preserved, and the outcome is safe; a note naming the ambiguity would materially shorten diagnosis.O3 (informational) — commissioning note. A lock already carrying both blocks with disagreeing values, written by the pre-fix server, now yields no continuation where it previously yielded recovery. That is a fail-closed narrowing in the correct direction and is recoverable by re-locking, but it is worth knowing during the post-merge restart window.
O4 (pre-existing, out of scope). Review
623noted thatgitea_create_prcallsverify_lock_for_mutationwhilegitea_commit_filesdoes not, and thatverify_lock_for_mutationnever compares the caller. Still true, untouched by this PR, and deserving its own issue.Tests I ran
All runs from reviewer-owned worktrees under
branches/, using the project virtual environment.The
+96and+19are exactly the two new suites measured alone, so every additional pass is accounted for by new coverage and nothing else moved.I did not reproduce the author's reported counts exactly and did not need to. Their clean comparison was taken against
79334d48, the previous head, whereas I compared againstaab54d48, the true merge base, which legitimately yields different totals. Their30failures against my28is the environmental drift they themselves flagged, present identically on both sides of my comparison. The claim that matters — that this branch introduces no failure — I validated by the stronger method, comparing sorted failing test ids in both directions, and it holds.The three revert probes, the two F3 mutation probes and the conflicting-evidence probe were all performed in disposable detached worktrees at this head, restored to pristine after each mutation and removed afterwards. The final reviewer worktree is at
b1fcf159378e7c72b67cc5ac507920d8390ef107with an emptygit status --porcelain --untracked-files=all.Issue #945 acceptance criteria
Continuation evidence is centralized in one resolver consumed by every relevant enforcement path; the shared recheck recognises valid exact-owner renewal evidence in addition to sanctioned dead-session recovery; the exemption stays bound to repository, issue, PR, branch, recorded, accepted, local, remote and live heads, with identity and profile bound as internal consistency plus process-scoped lock selection, honestly documented; valid exact-owner renewal permits the intended update of the existing owning PR and authorizes no second PR or unrelated commit; missing, ambiguous, wrong-issue, wrong-PR, wrong-branch, wrong-identity, wrong-profile, head-divergent and ungranted evidence all fail closed; duplicate prevention remains enforced; commit, create-PR, push and the read-only assessor use one authoritative decision; structured refusals preserve reason codes, retryability and audit evidence; and regression tests cover both renewal and recovery, reproduce the pre-fix failure, and prove no exemption arises solely because an open PR exists. The remaining criterion — restart the fleet at the resulting master revision and recommission the gate — is a post-merge commissioning step.
NATIVE_REVIEW_PROOF: This review was conducted end to end through the native
gitea-reviewerMCP namespace on remoteprgs, profileprgs-reviewer, authenticated identitysysadmin, rolereviewer, bound toScaled-Tech-Consulting/Gitea-Tools. Native preflight reportedin_parity: true,live_stale: false,mutation_safe: true,restart_required: falseated9414ebda9034ca87b36a9fce1c1ff7f98090f6, withnative_mcp_transport: true,production_native_mcp_transport: true,mode: production,transport: stdio. Live PR and issue state, formal review623, the comment thread, the head SHA and this verdict were all read and written through native tool calls under reviewer lease session73189-96f62ac29e47. Capability was resolved natively before each reviewer mutation, and the canonical review-merge workflow was loaded natively at hash263d0a6cb8a6.Canonical PR State
STATE: PR #946 is open at head
b1fcf15937and is mergeable against master ated9414ebda. It carries two commits above baseaab54d4825and touches exactly four files. This APPROVE is pinned to that exact head. The earlier REQUEST_CHANGES review 623 was posted at79334d4840, is marked stale because the head advanced past it, and all three of its findings are verified resolved at the current head. No blocking finding remains; four non-blocking observations are recorded above. The branch introduces no test failure against a clean checkout of its base.WHO_IS_NEXT: merger
NEXT_ACTION: A separate merger, not the author and not this reviewer, may merge PR #946 into master at head
b1fcf15937through the canonical merger workflow. After the merge, fast-forward the control checkout and restart all five MCP servers in one operator window before committing the PR #944 repair, since the deployed runtime executes the pre-fix code until both have happened.NEXT_PROMPT:
WHAT_HAPPENED: An independent reviewer read live issue #945 and PR #946, formal review 623, every later comment, and author handoff comment 17663, pinned the review to head
b1fcf15937, and established a dedicated detached reviewer worktree at that head. The full production diff and both new test suites were read. Each of review 623's three findings was re-verified by measurement: the three enforcement call sites were reverted one at a time in disposable worktrees and each reverted site failed tests, with the commit recheck reproducing the exact duplicate_commit_prevented signature issue #945 was filed on; the F2 correction was checked against what the code actually authenticates and against whether the un-edited PR description creates real risk; and the F3 conflicting-evidence behaviour was probed directly through the real shared enforcement recheck across eight evidence shapes, with both halves of the fix separately mutation-tested. The authoritative exemption policy was confirmed byte-unchanged. The full suite was run at the head and at a clean checkout of the exact base and the failing test id sets were compared in both directions. The protected #943 worktree fingerprints were recomputed and matched. All disposable worktrees were removed and the reviewer worktree was proven pristine at the reviewed head.WHY: Issue #945 exists because a correct decision layer was never wired into the paths that enforce it, and review 623 found this branch had reproduced that same class of defect inside its own test suite. The remediation had to be judged on whether a silent revert now fails, not on whether the wiring reads correctly, so every call site was reverted independently and observed. The F2 finding mattered because an overstated security claim is worse than a modest accurate one, and the corrected wording had to be checked against the implementation rather than accepted. F3 needed proof that ambiguity refuses rather than falls through, and proof that the legitimate same-PR case still continues, because a fix that narrows too far would deadlock the very author it exists to unblock.
ISSUE: #945
HEAD_SHA:
b1fcf15937REVIEW_STATUS: APPROVE posted at
b1fcf15937by sysadminMERGE_READY: yes
BLOCKERS: none
VALIDATION: Focused #945 wiring, #945 continuation, #755 and #760 suites at head
b1fcf159: 137 passed, 19 subtests passed in 5.63s. The two new suites alone at head: 96 passed, 19 subtests passed. Full suite at head from a branches/ reviewer worktree: 28 failed, 5621 passed, 6 skipped, 1013 subtests in 176.61s. Full suite at a clean detached checkout of the exact baseaab54d48: 28 failed, 5525 passed, 6 skipped, 994 subtests in 177.79s. Sorted failing test id sets compared in both directions are byte-identical, with no id present at head and absent at base, so this branch introduces no failure; the +96 passes and +19 subtests equal the two new suites measured alone. Independent revert probes at this head, each in a disposable worktree restored and removed afterwards: reverting the commit and create-PR recheck at gitea_mcp_server.py:2945 gives 8 failed, 92 passed, 4 subtests failed with outcome duplicate_commit_prevented and owning_pr_recovery_exempted False; reverting the read-only assessor at :5230 gives 2 failed, 94 passed; reverting the push ownership prover at :19515 gives 2 failed, 94 passed; all three restored gives 96 passed, 19 subtests. Disabling the F3 ambiguity guard gives 4 failed, 92 passed and disabling the F3 agreement check gives 5 failed, 91 passed. A direct probe of the real shared enforcement recheck across eight evidence shapes returned continuation for valid same-PR renewal, valid same-PR recovery and an agreeing pair, and fail-closed with no token and no authorized mutation for all five conflicting shapes including recovery and renewal naming different PRs. issue_work_duplicate_gate.py, issue_lock_recovery.py and issue_lock_store.py have a zero-byte diff across the branch. Scope is four files with no configuration, credential or scratch file and no network call in the new tests. Protected state verified intact: PR #944 open atf49e781102and the three SHA-256 fingerprints in the protected #943 worktree match the recorded values exactly. Reviewer worktree finished atb1fcf15937with an empty porcelain status including untracked files. No implementation change, no commit, no push, no branch mutation, no merge, and no PR edit was performed by this review.LAST_UPDATED_BY: sysadmin / prgs-reviewer / gitea-reviewer namespace, reviewer lease session 73189-96f62ac29e47
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 73189-96f62ac29e47
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr946-b1fcf159
phase: released
candidate_head:
b1fcf15937target_branch: master
target_branch_sha:
ed9414ebdalast_activity: 2026-07-27T23:17:57Z
expires_at: 2026-07-27T23:27:57Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #946
issue: #945
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 54461-14cd5ebc7ccf
worktree: /Users/jasonwalker/Development/Gitea-Tools
phase: claimed
candidate_head:
b1fcf15937target_branch: master
target_branch_sha:
ed9414ebdalast_activity: 2026-07-27T23:27:06Z
expires_at: 2026-07-27T23:37:06Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-27T23:27:40.943462+00:00approveon PR fix(gate): preserve exact-owner renewal evidence across duplicate rechecks (Closes #945) (#946)closed(merged=True)35ed8a2fcb11134a37c862ca6eaca26e3028902a9prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.