fix(lock): allow exact-owner renewal of expired author issue locks (Closes #760) #791
Merged
sysadmin
merged 2 commits from 2026-07-22 01:02:40 -05:00
fix/issue-760-exact-owner-renewal into master
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#791
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 #760
Problem
An expired author issue lease could not be renewed by the exact session that already owned it.
issue_lock_store.assess_same_issue_lease_conflictcomputed same-owner evidence and then returned on the expired branch before ever consulting it, so the same-owner allowance below was unreachable for every expired lease.assess_expired_lock_reclaimdid not help either: it permits takeover only on a dead PID or a missing worktree.Because the recorded PID is the long-lived MCP daemon rather than the authoring task, a lease that expires while its daemon is still up is the ordinary case for any author task that outlives the four-hour TTL. In that case an owner's own lock became permanently unmodifiable through sanctioned tools — request-changes remediation could not proceed, and hand-editing the lock JSON or restarting a daemon became a tempting way to launder false evidence through a guard.
Ownership and renewal semantics
New pure assessor
issue_lock_renewal.assess_exact_owner_lease_renewal, mirroring theissue_lock_recovery(#753) pattern: no mutation, no network I/O, and no caller assertion is trusted. Its disposition is evaluated before the expired foreign-takeover return.Renewal is granted only when every one of these holds:
Any missing or contradictory evidence fails closed and names the exact element that was absent.
Deliberate boundaries:
gitea_lock_issuegains no parameter, and both store entry points default to no waiver (AC14).A sanctioned renewal records prior PID, prior expiry, replacement PID, new expiry, claimant, and its supporting proof under a durable
lease_renewalblock, and reuses the #772 compare-and-swap so two sessions observing one expired lease cannot both win.Files changed
issue_lock_renewal.pyissue_lock_store.pyrenewal_sanctionedkeyword onassess_same_issue_lease_conflictandbind_session_lock; exact-owner disposition ordered ahead of the expired-takeover returngitea_mcp_server.py_evaluate_issue_lock_renewalevidence gatherer; conflict check moved after evidence collection; renewal recorded, surfaced, and covered by the compare-and-swaptests/test_issue_760_exact_owner_lease_renewal.pytests/test_issue_772_unpublished_claim_recovery.py213 insertions, 20 deletions.
Tests and results
venv/bin/python -m pytest, run from the allocated worktree.tests/test_issue_760_exact_owner_lease_renewal.py— 40 passed. Covers the positive path; every near-match refusal (branch, worktree, claimant, profile, remote/org/repo, dirty worktree, missing worktree, wrong branch, head mismatch, unpublished branch, PR-head mismatch, competing lock, competing branch, malformed lock, absent claimant); the non-candidate cases (live foreign lease, unexpired lease, dead PID under an unexpired lease, absent lock, other issue, other operation); the AC16 daemon-liveness regressions; the AC2 gate-ordering regression in both directions; the AC9 renewal record; AC10 downstream mutation ownership; AC14; AC17.test_issue_lock_store,test_lease_lifecycle,test_issue_753_dead_pid_lock_recovery,test_issue_755_owning_pr_recovery,test_issue_768_strict_descendant_recovery,test_issue_772_unpublished_claim_recovery,test_lock_issue_mcp_registration,test_issue_lock_worktree,test_issue_lock_adoption,test_issue_work_duplicate_gate) — 240 passed, 2 subtests passed.All 11 failures reproduce identically on
masterat3d0c13faand none touch lease, lock-conflict, or renewal code. Six intest_commit_payloads.pyand two intest_issue_702_review_findings_f1_f6.pyfail on master with the identical assertions; the remaining three (test_mcp_server.py::TestPreflightVerification::test_declared_clean_task_worktree_ignores_control_checkout_violation,test_post_merge_moot_lease.py::TestAcquireToolRefusesMergedPR::test_acquire_tool_fails_closed_on_merged_pr_without_posting,test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue) also fail on master. They stem from worktree-path assumptions and the #735verify_preflight_purityorg/repo forwarding, both outside this change. Baseline count is unchanged: this branch adds zero new failures.Issue #790 remains deferred
Per the sequencing decision recorded on both issues, #790 is intentionally held behind this work and must be reassessed after this lands. Its sliding heartbeat renewal, shared cross-role lease model, fencing-token design, and broader lease-generation changes are deliberately not implemented here. Once this merges, #790 should be re-read against the renewal behavior delivered by this PR, its remaining requirements identified, and its plan updated before it is allocated. Nothing in #790 was implemented, locked, branched, or otherwise mutated by this work.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #791
issue: #760
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 39910-3285b738dd87
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-fix-issue-760-exact-owner-renewal
phase: claimed
candidate_head:
1a97ced133target_branch: master
target_branch_sha:
3d0c13fa5alast_activity: 2026-07-22T04:55:42Z
expires_at: 2026-07-22T05:05:42Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #791
issue: #760
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 39910-3285b738dd87
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-fix-issue-760-exact-owner-renewal
phase: claimed
candidate_head:
1a97ced133target_branch: master
target_branch_sha:
3d0c13fa5alast_activity: 2026-07-22T04:56:33Z
expires_at: 2026-07-22T05:06:33Z
blocker: none
REQUEST_CHANGES — one blocking defect
Reviewed at head
1a97ced133fbb16a560ea4c3bf71b97b7401950c, basemaster, in a detached reviewer worktree.The design is sound and the assessor is well built: it is genuinely pure, it never trusts a caller assertion, and the refusal matrix is thorough. The blocking problem is that the feature does not work end to end.
F1 (BLOCKING) — renewal is granted, then blocked by the base-equivalence gate
gitea_lock_issuepasses onlyrecovery_sanctionedinto the worktree gate (gitea_mcp_server.py:4113):assess_issue_lock_worktreewaives base-equivalence only forrecovery_sanctioned(issue_lock_worktree.py:318-321). In a real renewal the #753 recovery assessment refuses, because the recorded PID is alive — the very condition this issue exists for — sorecovery_sanctionedisFalse. Andbase_equivalentis computed ashead_sha == base_sha(issue_lock_worktree.py:275), which isFalsefor any branch carrying committed work.Issue #760 states the premise directly: "a branch that already carries commits is ahead of its base by construction." So for every real renewal the new waiver is granted and then discarded one gate later.
Reproduced against this head with a real git repo — expired lease, live daemon PID, exact owner, clean worktree, one commit on the branch, remote and PR heads agreeing:
gitea_lock_issueraises at gate 3. AC13 ("Renewal is available through a native sanctioned tool path; no JSON edit, daemon restart, fabricated worktree, or direct API/CLI mutation is required") is not met, and AC10 is unproven end to end. The dead end #760 describes still exists; only its final gate has changed.Fix: thread the renewal waiver into the worktree gate the same way #753 threads recovery — add a
renewal_sanctionedparameter toassess_issue_lock_worktreeand pass it at thegitea_lock_issuecall site, keeping cleanliness and every other precondition unchanged. The renewal assessor has already proven branch, worktree, claimant, profile, and head agreement at that point, so base-equivalence is the one requirement it can safely stand in for — the argument the #753 docstring already makes.F2 (BLOCKING, coverage) — no MCP-level regression, which is why F1 shipped
Issue #760's validation expectations require "An MCP-level regression through native
gitea_lock_issue, not only against the assessor functions," and "A test asserting line-476-equivalent reachability for the exact-owner expired case, so the ordering defect cannot silently return."All 40 new cases are unit-level against
issue_lock_renewalandissue_lock_store. None invokesgitea_lock_issue, and none composes the renewal decision withassess_issue_lock_worktree.ConflictGateOrderingproves the store-level ordering but stops one gate short of the behavior the issue asks for. A single end-to-end case would have caught F1.Please add a regression that drives
gitea_lock_issuethrough a sanctioned renewal on a branch carrying a commit and asserts the lock is written,lease_renewalis recorded, and the result reports the renewal.F3 (non-blocking) — duplicated evidence gathering, and both waivers can be granted at once
For any expired lock both assessments run:
_evaluate_issue_lock_recoveryis gated onnot is_lease_live(...)and_evaluate_issue_lock_renewalonis_lease_expired(...), and an expired lease satisfies both. Each independently callsapi_get_all(.../branches)and_list_open_pulls(...), so one lock attempt makes two redundant round trips.If the lease is expired and the PID is dead and the owner is exact, both waivers can be granted and the lock records
dead_session_recoveryandlease_renewaltogether, making the provenance of the takeover ambiguous. Consider sharing one evidence gather and making the two dispositions mutually exclusive.F4 (non-blocking) — contention diagnostics regress
Moving the early
assess_same_issue_lease_conflictcall belowverify_preflight_purity, the stacked-base block, and the worktree read means a caller contending with a live foreign lease can surface a preflight or worktree error instead of the precise lease-conflict message, after extra network calls. Fail-closed behavior is preserved —bind_session_lockstill checks under the per-issue flock — so this is a diagnosability regression, not a safety one. The comment left in place of the old call is accurate and helpful.What I verified as correct
os.path.realpathand anos.kill(pid, 0)liveness probe.same_owner.gitea_lock_issuegains no parameter, and both store entry points default toFalse.tests/test_issue_772_unpublished_claim_recovery.pydoubles forward the new keyword faithfully and default toFalse, preserving the #772 compare-and-swap intent.Tests I ran
In the detached reviewer worktree at
1a97ced1:tests/test_issue_760_exact_owner_lease_renewal.py— 40 passedI independently classified the 11 failures rather than accepting the author's classification: I re-ran those exact node IDs against
masterat3d0c13fa5a75and all 11 fail there identically. Confirmed pre-existing; this PR introduces no new failures. The author's accounting is accurate.Separate security defect, not caused by this PR
tests/test_issue_702_review_findings_f1_f6.py::TestF2PreflightPathVerificationasserts onos.environand, on failure, prints the entire environment — which contains live credential values. I ran the classification pass with--tb=nospecifically so nothing was printed. This predates the PR and should be filed and fixed separately; the affected tokens should be treated as exposed.Canonical PR State
STATE: changes-requested
WHO_IS_NEXT: author
NEXT_ACTION: Thread the renewal waiver into assess_issue_lock_worktree and add the MCP-level renewal regression, then request re-review.
NEXT_PROMPT:
WHAT_HAPPENED: Reviewed PR #791 at head
1a97ced133in a detached reviewer worktree. Mapped AC1-AC17 to implementation and tests, reproduced a reachability defect against this head, ran the focused, regression, and full suites, and independently classified the 11 full-suite failures against master.WHY: The renewal waiver is never threaded into the base-equivalence gate, so the feature is unreachable end to end for exactly the scenario Issue #760 describes, and no MCP-level regression exists to catch it.
ISSUE: #760
HEAD_SHA:
1a97ced133REVIEW_STATUS: REQUEST_CHANGES
MERGE_READY: no
RELATED_PRS: #791 (this PR, changes requested at head
1a97ced133)BLOCKERS: F1 renewal unreachable through gitea_lock_issue; F2 missing MCP-level regression required by the issue's validation expectations.
VALIDATION: Reviewed head
1a97ced133confirmed by gitea_get_pr_review_feedback current_head_sha and by git rev-parse in the reviewer worktree; base master, merge-base3d0c13fa5a, single commit on the branch. Reviewer identity sysadmin, profile prgs-reviewer, role reviewer, independent from author jcwalker3. Runtime and master parity in parity at3d0c13fa5a. Focused suite 40 passed; lock and lease regression set 200 passed with 2 subtests passed; full suite 4230 passed, 11 failed, 6 skipped, 493 subtests passed, and those same 11 node IDs fail identically on master at3d0c13fa5a, independently re-run with tracebacks suppressed to avoid printing credentials. Reviewer worktree and stable control checkout both clean; no branch, commit, or Issue #790 mutation performed.LAST_UPDATED_BY: sysadmin / prgs-reviewer (reviewer session)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #791
issue: #760
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 7835-d4bf525741c2
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr791-issue-760
phase: claimed
candidate_head:
a30a3ce4c3target_branch: master
target_branch_sha:
3d0c13fa5alast_activity: 2026-07-22T05:21:10Z
expires_at: 2026-07-22T05:31:10Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #791
issue: #760
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 7835-d4bf525741c2
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr791-issue-760
phase: validated
candidate_head:
a30a3ce4c3target_branch: master
target_branch_sha:
3d0c13fa5alast_activity: 2026-07-22T05:27:14Z
expires_at: 2026-07-22T05:37:14Z
blocker: none
APPROVE — both review #499 blocking findings are remediated
Re-reviewed the complete PR (both commits
1a97ced1anda30a3ce4) at heada30a3ce4c37b2dde725301bef8b9ef8e04160088, basemaster, in a fresh session-owned detached reviewer worktree atbranches/review-pr791-issue-760. Review #499 is stale solely because the author pushed the remediation; it is undismissed and remains in the audit history.F1 — end-to-end renewal path: resolved
assess_issue_lock_worktreenow takesrenewal_sanctioned(issue_lock_worktree.py:288) andgitea_lock_issuepasses it (gitea_mcp_server.py:4125), so the waiver computed atgitea_mcp_server.py:4074survives to the gate that previously discarded it.Verified against this head:
recovery_sanctioned or renewal_sanctioned(issue_lock_worktree.py:331). Both are server-computed; neither is reachable from a tool parameter.parse_dirty_tracked_filesruns atissue_lock_worktree.py:322and appends its reason at:324-329, above the waiver branch.test_dirty_worktree_still_blocks_a_would_be_renewalproves the tool still refuses, and the refusal names the file._assessmentreportsrecovery_sanctionedandrenewal_sanctionedas separate fields (issue_lock_worktree.py:439-440); only the derivedbase_equivalence_waivedis combined, and it is labelled as derived. On the lock record,dead_session_recoveryandlease_renewalremain separate blocks.gitea_lock_issuegains no parameter (signature unchanged).renewal_sanctionedis keyword-only and defaults toFalseonassess_same_issue_lease_conflict,bind_session_lock,_save_issue_lock, andassess_issue_lock_worktree.same_ownerand the server-proven flag (issue_lock_store.py:529);test_non_base_equivalent_branch_still_blocks_without_any_waiverproves the tool-level refusal with no durable lock present.issue_lock_recovery.pyis not in the diff; the recovery assessment, its gate condition (not is_lease_live), andassess_expired_lock_reclaimare untouched.I also re-checked that moving
assess_same_issue_lease_conflictbelow the evidence gather cannot widen a write window: everything between the old and new call sites is read-only (stacked-base assessment,read_worktree_git_state,verify_preflight_purity, the two assessors), and the authoritative check still runs insidebind_session_lockunder the per-issue flock (issue_lock_store.py:219-227).Downstream duplicate-work gate — the second discard point
owning_pr_renewal_evidence(issue_lock_renewal.py:383) is sound:Noneunlessoutcome == RENEWAL_SANCTIONEDandrenewal_sanctionedis truthy, and the call site is guarded byif recovered_owning_pr is None and renewal_sanctioned(gitea_mcp_server.py:4110).pr_numberis set only for an open PR whose head ref equals the locked branch (gitea_mcp_server.py:2500-2506).issue_lock_renewal.py:333-344), and the evidence builder re-checkspr_head != local_head or pr_head != remote_headbefore emitting anything (:420), so a truncated or hand-built evidence map authorizes nothing.pr_number,branch_name, orpr_headreturnsNone(:416); non-integer ids returnNone(:422-426)._assess_owning_pr_exemptionagainst the live PR list: issue match, locked-branch match, exactly one linked open PR, PR-number match, head-ref match, and head-SHA match (issue_work_duplicate_gate.py:83-143). A second linked open PR, or any element disagreeing, drops the exemption. The branch-duplicate and claim-inventory blockers are evaluated independently and are untouched.issue_lock_recovery.owning_pr_recovery_evidenceis not in the diff and keeps priority at the call site; the renewal token is consulted only when recovery produced none.F2 — native MCP regression: satisfied
tests/test_issue_760_mcp_renewal_path.pydrives the realmcp_server.gitea_lock_issueagainst a real git repository (realgit init, a seed commit onmaster, a second commit on the work branch) and a real durable lock file written throughissue_lock_store.save_lock_file, withbase_equivalent=False— the exact condition that defeated the previous head. Coverage confirmed case by case:test_expired_lease_live_pid_exact_owner_renews_through_the_tool(assertsis_lease_expiredandis_process_aliveon the prior lock first)_init_worktreecommitswork.txt;_git_statepinsbase_equivalent=Falseresult["lease_renewal"]["renewed"]and"Renewed the expired"inresult["message"]test_renewed_lock_records_prior_and_replacement_evidence;replacement_pidadditionally asserted in the unit suite (test_evidence_records_both_sides_of_the_transition,test_record_captures_prior_and_replacement_state)lock_generation(written) == prior_generation + 1verify_lock_for_mutationtest_renewed_lock_is_live_and_satisfies_mutation_ownershiptest_recovery_record_is_not_written_for_a_live_owner_renewaltest_foreign_claimant_cannot_use_the_waiver,test_foreign_profile_cannot_use_the_waivertest_unpublished_branch_cannot_use_the_waiver,test_pr_head_mismatch_cannot_use_the_waivertest_dirty_worktree_still_blocks_a_would_be_renewaltest_non_base_equivalent_branch_still_blocks_without_any_waiverI confirmed the generation assertion is a real single advance:
bind_session_lockwritescurrent_generation + 1once (issue_lock_store.py:241), and renewal now suppliesexpected_generation, so the #772 compare-and-swap covers a renewal exactly as it covers a recovery.Acceptance criteria AC1-AC17
All satisfied at this head. AC1/AC2 — the renewal disposition is evaluated ahead of the expired foreign-takeover return (
issue_lock_store.py:520-540), withConflictGateOrderingcovering both directions. AC3 — remote, org, repo, issue, operation, branch, realpath worktree, claimant username, and claimant profile each compared with an independent refusal test. AC4 — existence, on-branch, and cleanliness. AC5/AC6 — local/remote/PR head agreement. AC7 — competing live lock, competing branch marker, other owning PR. AC8 — any missing evidence returnsREFUSEDnaming the exact element. AC9 —build_renewal_recordcaptures both sides of the transition. AC10 — proven end to end throughverify_lock_for_mutation. AC11/AC12 — only an expired lease is a candidate, so live foreign leases stay non-recoverable and dead-PID takeover keeps its existing conditions. AC13 — native path, no JSON edit or daemon restart, and now reachable. AC14 — no caller-controlled flag anywhere. AC15/AC16 — the refusal matrix and the daemon-liveness regressions. AC17 — no repository issue or PR number is special-cased, with a source scan asserting it.Changed callers and test doubles forward faithfully with behavior-preserving defaults:
_save_issue_lockandbind_session_lockboth defaultrenewal_sanctioned=False, and the twotests/test_issue_772_unpublished_claim_recovery.pydoubles forward the keyword verbatim so the compare-and-swap race still exercises real code.No Issue #790 behavior appears: no heartbeat renewal, no sliding expiration, no fencing token, no shared cross-role lease lifecycle, and no broader generation semantics — the only mention is a docstring stating those are out of scope. Absolute wall-clock expiry is preserved. Identity, repository binding, branches-only, duplicate-work, adoption, stale-runtime, and anti-stomp protections are intact.
Tests run
Reviewer worktree
branches/review-pr791-issue-760, detached ata30a3ce4c37b2dde725301bef8b9ef8e04160088, clean before and after;venv/bin/python -m pytest(pytest 9.1.1):tests/test_issue_760_mcp_renewal_path.py— 10 passedtests/with--tb=noso no environment values could print — 4240 passed, 11 failed, 6 skipped, 493 subtests passed4240 is exactly 10 more than the 4230 established at the previous head, matching the 10 added cases, with no test removed or weakened. No validation command failed unexpectedly during this session; there is no validation failure history to reconcile.
The 11 failures
Classified independently rather than accepted from the author: I created a clean baseline worktree
branches/baseline-master-pr791detached atmaster3d0c13fa5a756ff94441dc57cf4e74318f45ba41and re-ran those exact 11 node IDs there with tracebacks suppressed — 11 failed, the identical node IDs. Six intest_commit_payloads.py, two intest_issue_702_review_findings_f1_f6.py, plustest_mcp_server.py::TestPreflightVerification::test_declared_clean_task_worktree_ignores_control_checkout_violation,test_post_merge_moot_lease.py::TestAcquireToolRefusesMergedPR::test_acquire_tool_fails_closed_on_merged_pr_without_posting, andtest_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue. Pre-existing on the target branch; this PR adds none and touches no code they exercise.The environment-dumping defect in
test_issue_702_review_findings_f1_f6.py::TestF2PreflightPathVerificationflagged in review #499 is unchanged and unrelated to this PR. It still warrants a separate issue; no credential value was printed in this session.Non-blocking notes, carried forward
dead_session_recoveryandlease_renewaltogether. The remediation did not worsen this — the two flags and the two record blocks stay distinct, and the recovery token keeps priority in the duplicate gate — but sharing one evidence gather and making the dispositions mutually exclusive remains worthwhile.owning_pr_renewal_evidencehas no direct unit tests for its own negatives (truncated, mismatched, or non-sanctioned evidence). Its behavior is protected by the unchanged, #755-covered re-checks in_assess_owning_pr_exemption, and the positive path is exercised end to end by the MCP suite, so this is a coverage gap rather than a defect. Relatedly, the MCP suite injectsread_worktree_git_statewhile using a real repository, lock, and worktree — the gate composition it needed to prove is real, but the git read itself is not exercised.None of these blocks the merge.
Canonical PR State
STATE: approved
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR #791 into master at head
a30a3ce4c3through the sanctioned merger workflow.NEXT_PROMPT:
WHAT_HAPPENED: Re-reviewed PR #791 at head
a30a3ce4c3after the review #499 remediation, in a fresh session-owned detached reviewer worktree. Verified the F1 end-to-end renewal path and the newly added downstream duplicate-work-gate exemption, verified the F2 native MCP regression case by case, mapped AC1-AC17 to production code and tests, ran the new MCP suite, both Issue #760 suites, the lock and lease regression set, and the full suite, and independently re-classified the 11 full-suite failures against a clean master baseline worktree.WHY: Both blocking findings from review #499 are fixed without weakening any precondition, and the remediation additionally closed a second discard point in the duplicate-work gate that the earlier review had not reached.
ISSUE: #760
HEAD_SHA:
a30a3ce4c3REVIEW_STATUS: APPROVED
MERGE_READY: yes
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; mode=production; pid=7835; token_fingerprint=99a234e3f703b03d
RELATED_PRS: #791 (this PR, approved at head a30a3ce4c37b2dde725301bef8b9ef8e04160088; previous head
1a97ced133carried review #499 REQUEST_CHANGES, undismissed and preserved in audit history)BLOCKERS: None. F3, F4, and one new note remain open as non-blocking.
VALIDATION: Head
a30a3ce4c3confirmed by gitea_get_pr_review_feedback current_head_sha, by git ls-remote against prgs, and by git rev-parse in the reviewer worktree, and re-confirmed unchanged immediately before this submission. PR #791 open, base master, mergeable, author jcwalker3. Reviewer identity sysadmin, profile prgs-reviewer, role reviewer, independent from the author; binding Scaled-Tech-Consulting/Gitea-Tools on prgs; namespace health proven through the live client namespace; runtime and master parity in parity at3d0c13fa5a. Reviewer lease session 7835-d4bf525741c2 acquired at the exact candidate head (comment 13921) and heartbeated (comment 13923). Merge-base of the PR head and master is3d0c13fa5aand the head is not an ancestor of master, so the PR is not already landed. Tests from the reviewer worktree ata30a3ce4: new MCP suite 10 passed; both Issue #760 suites 50 passed; lock and lease regression set 200 passed with 2 subtests passed; full suite 4240 passed, 11 failed, 6 skipped, 493 subtests passed, run with tracebacks suppressed so no environment values printed. The same 11 node IDs fail identically in a clean baseline worktree detached at master3d0c13fa5a, re-run in this session. Reviewer worktree and baseline worktree clean before and after validation; the stable control checkout stayed on master at3d0c13fa5aand clean, and no test was run in it. No file was edited by the reviewer; no branch, commit, or push was made. Issue #790 confirmed open, unlocked, unbranched, and unmodified, with no remote ref carrying its number.LAST_UPDATED_BY: sysadmin / prgs-reviewer (reviewer session)
CTH: Reviewer Handoff — PR #791 approved at
a30a3ce4, ready for mergerReview 500 (APPROVED) is posted at head
a30a3ce4c37b2dde725301bef8b9ef8e04160088. Read back after submission:approval_at_current_headtrue,latest_approved_head_shaequals the current head,stalefalse,dismissedfalse,quarantinedfalse,has_blocking_change_requestsfalse,review_feedback_stalefalse.gitea_assess_pr_sync_statusreturnsrecommended_next_action: merge_now,commits_behind: 0,mergeable: true,has_conflicts: false,approval_valid_for_merge: true,stale_approval: false, checksnot_required.Correction to one statement inside review 500
Review 500's body states that review #499 "is undismissed and remains in the audit history." That was accurate when the body was composed and when the submission was sent —
gitea_get_pr_review_feedbackreporteddismissed: falsefor #499 immediately beforehand.Reading back after submission, Gitea now reports review #499 with
dismissed: true. The server dismissed it automatically as a consequence of the superseding APPROVED review by the same reviewer; no dismissal was requested, and no tool was called to dismiss it. Review #499 is fully preserved in the audit history — id 499, verdict REQUEST_CHANGES, reviewed head1a97ced133fbb16a560ea4c3bf71b97b7401950c, submitted 2026-07-21T23:59:25-05:00, complete body intact,quarantined: false. Only the server-sidedismissedflag changed. Recording it here so the audit trail is not contradicted by the review body.Verdict summary
Both #499 blocking findings are remediated. F1:
renewal_sanctionedis threaded intoassess_issue_lock_worktreeand passed fromgitea_lock_issue, base-equivalence is waived only under a proven exact-owner renewal or the existing #753 recovery, worktree cleanliness is evaluated before the waiver and never relaxed, and the two dispositions stay separately reported. The remediation also closed a second discard point the earlier review had not reached —owning_pr_renewal_evidenceexempts the duplicate-work gate for the one PR the renewing lock already owns, re-checking that PR, local, and remote heads all agree, and re-validated against the live PR list by the unchanged #755 exemption logic. F2:tests/test_issue_760_mcp_renewal_path.pydrives nativegitea_lock_issueend to end with 10 cases. AC1-AC17 all map to production code and tests. No caller-controlled bypass, no Issue #790 behavior, and #753 recovery unchanged.Tests from the session-owned reviewer worktree
branches/review-pr791-issue-760ata30a3ce4: new MCP suite 10 passed; both Issue #760 suites 50 passed; lock/lease regression set 200 passed with 2 subtests passed; full suite 4240 passed, 11 failed, 6 skipped, 493 subtests passed with tracebacks suppressed. The same 11 node IDs fail identically in a clean baseline worktree detached at master3d0c13fa5a756ff94441dc57cf4e74318f45ba41, re-run in this session — pre-existing, none introduced by this PR. Non-blocking F3 and F4 remain open, plus one new non-blocking coverage note aboutowning_pr_renewal_evidencenegatives.Mutation ledger for this reviewer run
git worktree add --detach branches/review-pr791-issue-760 a30a3ce4;git worktree add --detach branches/baseline-master-pr791 3d0c13fagit fetch prgsfor master and the PR branchgitea_mark_final_review_decision(approve, head-pinned);gitea_submit_pr_reviewAPPROVED, review 500The stable control checkout stayed on master at
3d0c13fa5a756ff94441dc57cf4e74318f45ba41and clean; no test ran in it. Both session worktrees are clean. Issue #790 is untouched — open, unlocked, unbranched, no remote ref carrying its number.Canonical PR State
STATE: approved
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR #791 into master at head
a30a3ce4c3through the sanctioned merger workflow.NEXT_PROMPT:
WHAT_HAPPENED: Re-reviewed PR #791 at head
a30a3ce4c3after the review #499 remediation and submitted review 500 APPROVED, then read the review back and recorded that Gitea automatically set review #499 dismissed true as a side effect of the superseding approval.WHY: Both blocking findings from review #499 are remediated without weakening any precondition, and the PR is conflict-free with a valid approval at the exact current head.
ISSUE: #760
HEAD_SHA:
a30a3ce4c3REVIEW_STATUS: APPROVED
MERGE_READY: yes
NATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; mode=production; pid=7835; token_fingerprint=99a234e3f703b03d
RELATED_PRS: #791 (this PR, approved by review 500 at head a30a3ce4c37b2dde725301bef8b9ef8e04160088; review #499 REQUEST_CHANGES at previous head
1a97ced133is preserved in audit history and now carries a server-set dismissed flag)BLOCKERS: None. F3, F4, and one new coverage note remain open as non-blocking.
VALIDATION: Review 500 read back through gitea_get_pr_review_feedback: verdict APPROVED, reviewed head
a30a3ce4c3equal to current_head_sha, stale false, dismissed false, quarantined false, approval_at_current_head true, has_blocking_change_requests false. gitea_assess_pr_sync_status returns merge_now with commits_behind 0, mergeable true, has_conflicts false, approval_valid_for_merge true, stale_approval false, checks not_required. Reviewer identity sysadmin, profile prgs-reviewer, role reviewer, independent from author jcwalker3; runtime and master parity in parity at3d0c13fa5a. Reviewer lease session 7835-d4bf525741c2 held at the exact head throughout. Tests from branches/review-pr791-issue-760 ata30a3ce4: new MCP suite 10 passed; both Issue #760 suites 50 passed; lock and lease regression set 200 passed with 2 subtests passed; full suite 4240 passed, 11 failed, 6 skipped, 493 subtests passed with tracebacks suppressed so no environment values printed; the same 11 node IDs fail identically in branches/baseline-master-pr791 detached at master3d0c13fa5a. Both session worktrees clean before and after; stable control checkout clean on master and unused for task work. No file edited, no branch, commit, or push made, and Issue #790 unmodified.LAST_UPDATED_BY: sysadmin / prgs-reviewer (reviewer session)
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #791
issue: #760
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 58066-cf14e86f060e
worktree: branches/review-pr791-issue-760
phase: claimed
candidate_head:
a30a3ce4c3target_branch: master
target_branch_sha:
3d0c13fa5alast_activity: 2026-07-22T06:01:51Z
expires_at: 2026-07-22T06:11:51Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-22T06:02:41.788228+00:00approveon PR fix(lock): allow exact-owner renewal of expired author issue locks (Closes #760) (#791)closed(merged=True)620ed6e9a9550b8da2ceb82d9ab8744e8920490f2prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.