fix(lock): allow exact-owner renewal of expired author issue locks (Closes #760) #791

Merged
sysadmin merged 2 commits from fix/issue-760-exact-owner-renewal into master 2026-07-22 01:02:40 -05:00
Owner

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_conflict computed 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_reclaim did 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 the issue_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:

  • exact match of remote, org, repo, issue number, operation type, branch, realpath-normalized worktree, claimant username, and claimant profile;
  • the registered worktree exists, sits on the locked branch, and carries no uncommitted tracked changes;
  • local head and remote branch head agree;
  • when an owning PR exists, its head agrees with the local head too;
  • no competing live lock, no competing branch carrying the issue marker, no other owning PR.

Any missing or contradictory evidence fails closed and names the exact element that was absent.

Deliberate boundaries:

  • PID liveness is never authorization. It is recorded as evidence and is neither necessary nor sufficient (AC16). An otherwise-exact owner renews whether the recorded PID is alive or dead; a live PID alone renews nothing.
  • Only an expired lease is ever a candidate. A live foreign lease therefore stays non-recoverable (AC12), and dead-PID takeover under an unexpired lease keeps its existing #601/#753 conditions untouched (AC11).
  • Never caller-declarable. The waiver is computed server-side from durable lock state plus live git and Gitea observation. gitea_lock_issue gains no parameter, and both store entry points default to no waiver (AC14).
  • Absolute wall-clock expiry is preserved. Renewal stamps a fresh absolute expiry.

A sanctioned renewal records prior PID, prior expiry, replacement PID, new expiry, claimant, and its supporting proof under a durable lease_renewal block, and reuses the #772 compare-and-swap so two sessions observing one expired lease cannot both win.

Files changed

File Change
issue_lock_renewal.py new pure assessor, renewal record builder, refusal formatter
issue_lock_store.py renewal_sanctioned keyword on assess_same_issue_lease_conflict and bind_session_lock; exact-owner disposition ordered ahead of the expired-takeover return
gitea_mcp_server.py _evaluate_issue_lock_renewal evidence gatherer; conflict check moved after evidence collection; renewal recorded, surfaced, and covered by the compare-and-swap
tests/test_issue_760_exact_owner_lease_renewal.py new, 40 cases
tests/test_issue_772_unpublished_claim_recovery.py two test doubles forward the new keyword

213 insertions, 20 deletions.

Tests and results

venv/bin/python -m pytest, run from the allocated worktree.

  • New suite tests/test_issue_760_exact_owner_lease_renewal.py40 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.
  • Lock and lease regression set (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.
  • Full suite — 4230 passed, 11 failed, 6 skipped, 493 subtests passed.

All 11 failures reproduce identically on master at 3d0c13fa and none touch lease, lock-conflict, or renewal code. Six in test_commit_payloads.py and two in test_issue_702_review_findings_f1_f6.py fail 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 #735 verify_preflight_purity org/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.

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_conflict` computed 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_reclaim` did 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 the `issue_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: - exact match of remote, org, repo, issue number, operation type, branch, realpath-normalized worktree, claimant username, and claimant profile; - the registered worktree exists, sits on the locked branch, and carries no uncommitted tracked changes; - local head and remote branch head agree; - when an owning PR exists, its head agrees with the local head too; - no competing live lock, no competing branch carrying the issue marker, no other owning PR. Any missing or contradictory evidence fails closed and names the exact element that was absent. Deliberate boundaries: - **PID liveness is never authorization.** It is recorded as evidence and is neither necessary nor sufficient (AC16). An otherwise-exact owner renews whether the recorded PID is alive or dead; a live PID alone renews nothing. - **Only an expired lease is ever a candidate.** A live foreign lease therefore stays non-recoverable (AC12), and dead-PID takeover under an unexpired lease keeps its existing #601/#753 conditions untouched (AC11). - **Never caller-declarable.** The waiver is computed server-side from durable lock state plus live git and Gitea observation. `gitea_lock_issue` gains no parameter, and both store entry points default to no waiver (AC14). - **Absolute wall-clock expiry is preserved.** Renewal stamps a fresh absolute expiry. A sanctioned renewal records prior PID, prior expiry, replacement PID, new expiry, claimant, and its supporting proof under a durable `lease_renewal` block, and reuses the #772 compare-and-swap so two sessions observing one expired lease cannot both win. ## Files changed | File | Change | |---|---| | `issue_lock_renewal.py` | new pure assessor, renewal record builder, refusal formatter | | `issue_lock_store.py` | `renewal_sanctioned` keyword on `assess_same_issue_lease_conflict` and `bind_session_lock`; exact-owner disposition ordered ahead of the expired-takeover return | | `gitea_mcp_server.py` | `_evaluate_issue_lock_renewal` evidence gatherer; conflict check moved after evidence collection; renewal recorded, surfaced, and covered by the compare-and-swap | | `tests/test_issue_760_exact_owner_lease_renewal.py` | new, 40 cases | | `tests/test_issue_772_unpublished_claim_recovery.py` | two test doubles forward the new keyword | 213 insertions, 20 deletions. ## Tests and results `venv/bin/python -m pytest`, run from the allocated worktree. - New suite `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. - Lock and lease regression set (`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**. - Full suite — **4230 passed, 11 failed, 6 skipped, 493 subtests passed**. All 11 failures reproduce identically on `master` at `3d0c13fa` and none touch lease, lock-conflict, or renewal code. Six in `test_commit_payloads.py` and two in `test_issue_702_review_findings_f1_f6.py` fail 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 #735 `verify_preflight_purity` org/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.
jcwalker3 added 1 commit 2026-07-21 23:40:51 -05:00
An expired author issue lease could not be renewed by the exact session that
already owned it. `assess_same_issue_lease_conflict` computed same-owner
evidence and then returned on the expired branch before consulting it, and
`assess_expired_lock_reclaim` only permits takeover 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 under a live daemon is the ordinary case
for any author task outliving the TTL — and in that case the owner's own lock
became permanently unmodifiable through sanctioned tools.

Add `issue_lock_renewal`, a pure evidence assessor for that one case, and
evaluate its disposition before the expired foreign-takeover return.

Renewal requires an exact match of remote, org, repo, issue number, operation
type, branch, realpath-normalized worktree, claimant username, and claimant
profile; a registered worktree that exists, sits on the locked branch, and is
clean; local and remote heads that agree; and, when an owning PR exists, a PR
head that agrees too. No competing live lock, competing branch claim, or other
owning PR may exist. Any missing or contradictory evidence fails closed.

PID liveness is never authorization: it is recorded as evidence and is neither
necessary nor sufficient (AC16). Only an expired lease is ever a candidate, so
a live foreign lease stays non-recoverable (AC12) and dead-PID takeover keeps
its existing #601 conditions (AC11). Renewal is never caller-declarable — the
waiver is server-computed and `gitea_lock_issue` gains no parameter (AC14).

A sanctioned renewal records prior PID, prior expiry, replacement PID, new
expiry, claimant, and its supporting proof under `lease_renewal`, and reuses
the #772 compare-and-swap so two sessions observing the same expired lease
cannot both win.

Absolute wall-clock expiry is preserved. Sliding heartbeat renewal, fencing
tokens, and the shared cross-role lifecycle remain #790's scope and are
deliberately not implemented here; #790 stays sequenced behind this change.

Tests: 40 new cases covering the positive path, every near-match and
foreign-owner refusal, the non-candidate cases, the gate-ordering regression,
the renewal record, downstream mutation ownership, and AC14/AC17. Two #772 test
doubles now forward the new keyword.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ti8deB36iWcjHmE9cuxour
Owner

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: 1a97ced133
target_branch: master
target_branch_sha: 3d0c13fa5a
last_activity: 2026-07-22T04:55:42Z
expires_at: 2026-07-22T05:05:42Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: 1a97ced133fbb16a560ea4c3bf71b97b7401950c target_branch: master target_branch_sha: 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 last_activity: 2026-07-22T04:55:42Z expires_at: 2026-07-22T05:05:42Z blocker: none
Owner

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: 1a97ced133
target_branch: master
target_branch_sha: 3d0c13fa5a
last_activity: 2026-07-22T04:56:33Z
expires_at: 2026-07-22T05:06:33Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: 1a97ced133fbb16a560ea4c3bf71b97b7401950c target_branch: master target_branch_sha: 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 last_activity: 2026-07-22T04:56:33Z expires_at: 2026-07-22T05:06:33Z blocker: none
sysadmin requested changes 2026-07-21 23:59:25 -05:00
Dismissed
sysadmin left a comment
Owner

REQUEST_CHANGES — one blocking defect

Reviewed at head 1a97ced133fbb16a560ea4c3bf71b97b7401950c, base master, 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_issue passes only recovery_sanctioned into the worktree gate (gitea_mcp_server.py:4113):

lock_assessment = issue_lock_worktree.assess_issue_lock_worktree(
    ...
    recovery_sanctioned=recovery_sanctioned,   # renewal_sanctioned is never passed
)

assess_issue_lock_worktree waives base-equivalence only for recovery_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 — so recovery_sanctioned is False. And base_equivalent is computed as head_sha == base_sha (issue_lock_worktree.py:275), which is False for 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:

GATE 1  assess_exact_owner_lease_renewal -> RENEWAL_SANCTIONED   (renewal_sanctioned=True)
GATE 2  assess_same_issue_lease_conflict  -> None                 (allowed)
GATE 3  assess_issue_lock_worktree        -> block=True
        reason: issue lock worktree must be base-equivalent to one of
                dev/main/master before implementation work; inspected
                branch 'fix/issue-9001-demo'

gitea_lock_issue raises 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_sanctioned parameter to assess_issue_lock_worktree and pass it at the gitea_lock_issue call 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_renewal and issue_lock_store. None invokes gitea_lock_issue, and none composes the renewal decision with assess_issue_lock_worktree. ConflictGateOrdering proves 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_issue through a sanctioned renewal on a branch carrying a commit and asserts the lock is written, lease_renewal is 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_recovery is gated on not is_lease_live(...) and _evaluate_issue_lock_renewal on is_lease_expired(...), and an expired lease satisfies both. Each independently calls api_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_recovery and lease_renewal together, 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_conflict call below verify_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_lock still 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

  • The assessor is pure: no writes, no network, no subprocess; only os.path.realpath and an os.kill(pid, 0) liveness probe.
  • AC3 ownership fields are all compared, each with an independent refusal test.
  • AC12 holds structurally — only an expired lease is a candidate, so a live foreign lease can never reach the waiver.
  • AC11 holds — the dead-PID reclaim path and the foreign-takeover return are unchanged, and the waiver is additionally scoped by a locally recomputed same_owner.
  • AC14 holds — the waiver is server-computed, gitea_lock_issue gains no parameter, and both store entry points default to False.
  • AC16 holds — nothing branches on PID liveness; it is evidence only.
  • AC17 holds — no issue or PR number is special-cased.
  • The two tests/test_issue_772_unpublished_claim_recovery.py doubles forward the new keyword faithfully and default to False, preserving the #772 compare-and-swap intent.
  • No #790 behavior appears: no heartbeat renewal, no fencing tokens, no shared cross-role lifecycle, no sliding expiry.
  • No weakening of branches-only, duplicate-work, adoption, stale-runtime, identity, binding, or anti-stomp gates.

Tests I ran

In the detached reviewer worktree at 1a97ced1:

  • tests/test_issue_760_exact_owner_lease_renewal.py — 40 passed
  • Lock/lease regression set (10 files) — 200 passed, 2 subtests passed
  • Full suite — 4230 passed, 11 failed, 6 skipped, 493 subtests passed

I independently classified the 11 failures rather than accepting the author's classification: I re-ran those exact node IDs against master at 3d0c13fa5a75 and 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::TestF2PreflightPathVerification asserts on os.environ and, on failure, prints the entire environment — which contains live credential values. I ran the classification pass with --tb=no specifically 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:

Address review findings on Gitea-Tools PR #791 (Issue #760) in worktree /Users/jasonwalker/Development/Gitea-Tools/branches/fix-issue-760-exact-owner-renewal on branch fix/issue-760-exact-owner-renewal. F1: exact-owner renewal is granted by the assessor and the lease-conflict gate but then blocked by assess_issue_lock_worktree, which waives base-equivalence only for recovery_sanctioned; add a renewal_sanctioned parameter to assess_issue_lock_worktree and pass it from gitea_lock_issue, leaving worktree cleanliness and all other preconditions unchanged. F2: add an MCP-level regression that drives gitea_lock_issue through a sanctioned renewal on a branch carrying a commit and asserts the lock is written, lease_renewal is recorded, and the result reports the renewal. Optionally address F3 by sharing one evidence gather between the recovery and renewal assessments and making the two dispositions mutually exclusive. Do not weaken any existing guard, do not implement Issue #790 behavior, and do not modify Issue #787 artifacts. Re-run the focused suite, the lock and lease regression set, and the full suite, comparing failures against the master baseline at 3d0c13fa which carries 11 pre-existing failures. Push to the same branch and hand back to the reviewer.

WHAT_HAPPENED: Reviewed PR #791 at head 1a97ced133 in 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: 1a97ced133
REVIEW_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 1a97ced133 confirmed by gitea_get_pr_review_feedback current_head_sha and by git rev-parse in the reviewer worktree; base master, merge-base 3d0c13fa5a, single commit on the branch. Reviewer identity sysadmin, profile prgs-reviewer, role reviewer, independent from author jcwalker3. Runtime and master parity in parity at 3d0c13fa5a. 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 at 3d0c13fa5a, 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)

## REQUEST_CHANGES — one blocking defect Reviewed at head `1a97ced133fbb16a560ea4c3bf71b97b7401950c`, base `master`, 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_issue` passes only `recovery_sanctioned` into the worktree gate (`gitea_mcp_server.py:4113`): ```python lock_assessment = issue_lock_worktree.assess_issue_lock_worktree( ... recovery_sanctioned=recovery_sanctioned, # renewal_sanctioned is never passed ) ``` `assess_issue_lock_worktree` waives base-equivalence **only** for `recovery_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 — so `recovery_sanctioned` is `False`. And `base_equivalent` is computed as `head_sha == base_sha` (`issue_lock_worktree.py:275`), which is `False` for 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: ``` GATE 1 assess_exact_owner_lease_renewal -> RENEWAL_SANCTIONED (renewal_sanctioned=True) GATE 2 assess_same_issue_lease_conflict -> None (allowed) GATE 3 assess_issue_lock_worktree -> block=True reason: issue lock worktree must be base-equivalent to one of dev/main/master before implementation work; inspected branch 'fix/issue-9001-demo' ``` `gitea_lock_issue` raises 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_sanctioned` parameter to `assess_issue_lock_worktree` and pass it at the `gitea_lock_issue` call 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_renewal` and `issue_lock_store`. None invokes `gitea_lock_issue`, and none composes the renewal decision with `assess_issue_lock_worktree`. `ConflictGateOrdering` proves 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_issue` through a sanctioned renewal on a branch carrying a commit and asserts the lock is written, `lease_renewal` is 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_recovery` is gated on `not is_lease_live(...)` and `_evaluate_issue_lock_renewal` on `is_lease_expired(...)`, and an expired lease satisfies both. Each independently calls `api_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_recovery` and `lease_renewal` together, 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_conflict` call below `verify_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_lock` still 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 - The assessor is pure: no writes, no network, no subprocess; only `os.path.realpath` and an `os.kill(pid, 0)` liveness probe. - AC3 ownership fields are all compared, each with an independent refusal test. - AC12 holds structurally — only an expired lease is a candidate, so a live foreign lease can never reach the waiver. - AC11 holds — the dead-PID reclaim path and the foreign-takeover return are unchanged, and the waiver is additionally scoped by a locally recomputed `same_owner`. - AC14 holds — the waiver is server-computed, `gitea_lock_issue` gains no parameter, and both store entry points default to `False`. - AC16 holds — nothing branches on PID liveness; it is evidence only. - AC17 holds — no issue or PR number is special-cased. - The two `tests/test_issue_772_unpublished_claim_recovery.py` doubles forward the new keyword faithfully and default to `False`, preserving the #772 compare-and-swap intent. - No #790 behavior appears: no heartbeat renewal, no fencing tokens, no shared cross-role lifecycle, no sliding expiry. - No weakening of branches-only, duplicate-work, adoption, stale-runtime, identity, binding, or anti-stomp gates. ### Tests I ran In the detached reviewer worktree at `1a97ced1`: - `tests/test_issue_760_exact_owner_lease_renewal.py` — 40 passed - Lock/lease regression set (10 files) — 200 passed, 2 subtests passed - Full suite — 4230 passed, 11 failed, 6 skipped, 493 subtests passed I independently classified the 11 failures rather than accepting the author's classification: I re-ran those exact node IDs against `master` at `3d0c13fa5a75` and 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::TestF2PreflightPathVerification` asserts on `os.environ` and, on failure, prints the entire environment — which contains live credential values. I ran the classification pass with `--tb=no` specifically 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: ```text Address review findings on Gitea-Tools PR #791 (Issue #760) in worktree /Users/jasonwalker/Development/Gitea-Tools/branches/fix-issue-760-exact-owner-renewal on branch fix/issue-760-exact-owner-renewal. F1: exact-owner renewal is granted by the assessor and the lease-conflict gate but then blocked by assess_issue_lock_worktree, which waives base-equivalence only for recovery_sanctioned; add a renewal_sanctioned parameter to assess_issue_lock_worktree and pass it from gitea_lock_issue, leaving worktree cleanliness and all other preconditions unchanged. F2: add an MCP-level regression that drives gitea_lock_issue through a sanctioned renewal on a branch carrying a commit and asserts the lock is written, lease_renewal is recorded, and the result reports the renewal. Optionally address F3 by sharing one evidence gather between the recovery and renewal assessments and making the two dispositions mutually exclusive. Do not weaken any existing guard, do not implement Issue #790 behavior, and do not modify Issue #787 artifacts. Re-run the focused suite, the lock and lease regression set, and the full suite, comparing failures against the master baseline at 3d0c13fa which carries 11 pre-existing failures. Push to the same branch and hand back to the reviewer. ``` WHAT_HAPPENED: Reviewed PR #791 at head 1a97ced133fbb16a560ea4c3bf71b97b7401950c in 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: 1a97ced133fbb16a560ea4c3bf71b97b7401950c REVIEW_STATUS: REQUEST_CHANGES MERGE_READY: no RELATED_PRS: #791 (this PR, changes requested at head 1a97ced133fbb16a560ea4c3bf71b97b7401950c) BLOCKERS: F1 renewal unreachable through gitea_lock_issue; F2 missing MCP-level regression required by the issue's validation expectations. VALIDATION: Reviewed head 1a97ced133fbb16a560ea4c3bf71b97b7401950c confirmed by gitea_get_pr_review_feedback current_head_sha and by git rev-parse in the reviewer worktree; base master, merge-base 3d0c13fa5a756ff94441dc57cf4e74318f45ba41, single commit on the branch. Reviewer identity sysadmin, profile prgs-reviewer, role reviewer, independent from author jcwalker3. Runtime and master parity in parity at 3d0c13fa5a75. 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 at 3d0c13fa5a75, 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)
jcwalker3 added 1 commit 2026-07-22 00:10:57 -05:00
Addresses review #499 on PR #791.

F1 — the renewal sanction was computed and then discarded twice.

`assess_issue_lock_worktree` waived base-equivalence only for
`recovery_sanctioned`, and `gitea_lock_issue` never passed the renewal waiver
into it. A branch being renewed always carries committed work, so it is never
base-equivalent, and #753 recovery refuses when the recorded PID is alive —
which is the defining condition of a renewal. Every real renewal was therefore
granted by the assessor and then rejected one gate later.

Thread `renewal_sanctioned` into `assess_issue_lock_worktree` alongside
`recovery_sanctioned`, waiving base-equivalence on the same grounds and nothing
else. Cleanliness is evaluated before the waiver and is never relaxed; the
assessment now reports which of the two waivers applied.

The MCP-level regression then exposed a second discard point: the duplicate-work
gate rejected the renewal with "open PR already covers issue" — the very PR the
lock being renewed already owns. Add `owning_pr_renewal_evidence`, the mirror of
`issue_lock_recovery.owning_pr_recovery_evidence` (#755), and carry it into the
gate only when renewal was granted. It re-checks that the PR, local, and remote
heads agree, so truncated or hand-built evidence cannot authorize an exemption.

Neither waiver is caller-supplied and `gitea_lock_issue` still gains no
parameter. Absolute wall-clock expiry is unchanged. No #790 heartbeat, sliding
expiry, fencing-token, or shared-lifecycle behavior is introduced, and #753
recovery behavior is untouched.

F2 — add tests/test_issue_760_mcp_renewal_path.py, 10 cases driving the native
`gitea_lock_issue` path against a real git repository and a real durable lock:
expired lease, live recorded PID, committed non-base-equivalent branch. Proves
the renewal completes, records prior and replacement lease evidence, advances
the generation exactly once, produces a live lock that satisfies
`verify_lock_for_mutation`, and does not claim dead-session recovery. Negative
companions prove a foreign claimant, foreign profile, unpublished branch, or
mismatched PR head cannot use the waiver, that a dirty worktree still fails
closed, and that base-equivalence still applies with no waiver at all.

Tests: new MCP suite 10 passed; combined #760 suites 50 passed; lock/lease
regression set 200 passed with 2 subtests; full suite 4240 passed, 11 failed,
6 skipped, 493 subtests passed — the same 11 pre-existing failures as the
master baseline at 3d0c13fa, no new failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ti8deB36iWcjHmE9cuxour
Owner

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: a30a3ce4c3
target_branch: master
target_branch_sha: 3d0c13fa5a
last_activity: 2026-07-22T05:21:10Z
expires_at: 2026-07-22T05:31:10Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: a30a3ce4c37b2dde725301bef8b9ef8e04160088 target_branch: master target_branch_sha: 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 last_activity: 2026-07-22T05:21:10Z expires_at: 2026-07-22T05:31:10Z blocker: none
Owner

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: a30a3ce4c3
target_branch: master
target_branch_sha: 3d0c13fa5a
last_activity: 2026-07-22T05:27:14Z
expires_at: 2026-07-22T05:37:14Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: a30a3ce4c37b2dde725301bef8b9ef8e04160088 target_branch: master target_branch_sha: 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 last_activity: 2026-07-22T05:27:14Z expires_at: 2026-07-22T05:37:14Z blocker: none
sysadmin approved these changes 2026-07-22 00:30:33 -05:00
sysadmin left a comment
Owner

APPROVE — both review #499 blocking findings are remediated

Re-reviewed the complete PR (both commits 1a97ced1 and a30a3ce4) at head a30a3ce4c37b2dde725301bef8b9ef8e04160088, base master, in a fresh session-owned detached reviewer worktree at branches/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_worktree now takes renewal_sanctioned (issue_lock_worktree.py:288) and gitea_lock_issue passes it (gitea_mcp_server.py:4125), so the waiver computed at gitea_mcp_server.py:4074 survives to the gate that previously discarded it.

Verified against this head:

  • Waiver is scoped to proven ownership. Base-equivalence is bypassed only under recovery_sanctioned or renewal_sanctioned (issue_lock_worktree.py:331). Both are server-computed; neither is reachable from a tool parameter.
  • Cleanliness is evaluated before the waiver and is never relaxed. parse_dirty_tracked_files runs at issue_lock_worktree.py:322 and appends its reason at :324-329, above the waiver branch. test_dirty_worktree_still_blocks_a_would_be_renewal proves the tool still refuses, and the refusal names the file.
  • The two dispositions stay distinct. _assessment reports recovery_sanctioned and renewal_sanctioned as separate fields (issue_lock_worktree.py:439-440); only the derived base_equivalence_waived is combined, and it is labelled as derived. On the lock record, dead_session_recovery and lease_renewal remain separate blocks.
  • No caller can supply or forge the waiver. gitea_lock_issue gains no parameter (signature unchanged). renewal_sanctioned is keyword-only and defaults to False on assess_same_issue_lease_conflict, bind_session_lock, _save_issue_lock, and assess_issue_lock_worktree.
  • Ordinary unsanctioned non-base-equivalent worktrees still fail closed. The store waiver requires both the locally recomputed same_owner and the server-proven flag (issue_lock_store.py:529); test_non_base_equivalent_branch_still_blocks_without_any_waiver proves the tool-level refusal with no durable lock present.
  • Issue #753 recovery is unchanged. issue_lock_recovery.py is not in the diff; the recovery assessment, its gate condition (not is_lease_live), and assess_expired_lock_reclaim are untouched.

I also re-checked that moving assess_same_issue_lease_conflict below 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 inside bind_session_lock under 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:

  • Applies only after a valid sanction. It returns None unless outcome == RENEWAL_SANCTIONED and renewal_sanctioned is truthy, and the call site is guarded by if recovered_owning_pr is None and renewal_sanctioned (gitea_mcp_server.py:4110).
  • Names only the exact owned PR. pr_number is set only for an open PR whose head ref equals the locked branch (gitea_mcp_server.py:2500-2506).
  • All three heads must agree. The assessor requires local == remote and PR == local (issue_lock_renewal.py:333-344), and the evidence builder re-checks pr_head != local_head or pr_head != remote_head before emitting anything (:420), so a truncated or hand-built evidence map authorizes nothing.
  • Absent / stale / mismatched / foreign evidence fails closed. Missing pr_number, branch_name, or pr_head returns None (:416); non-integer ids return None (:422-426).
  • No duplicate work, no bypass of unrelated open PRs. The token is re-validated by the unchanged _assess_owning_pr_exemption against 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.
  • #755 behavior is unchanged. issue_lock_recovery.owning_pr_recovery_evidence is 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.py drives the real mcp_server.gitea_lock_issue against a real git repository (real git init, a seed commit on master, a second commit on the work branch) and a real durable lock file written through issue_lock_store.save_lock_file, with base_equivalent=False — the exact condition that defeated the previous head. Coverage confirmed case by case:

Requirement Test
Expired exact-owner lease, live recorded PID, renews test_expired_lease_live_pid_exact_owner_renews_through_the_tool (asserts is_lease_expired and is_process_alive on the prior lock first)
Committed, non-base-equivalent branch _init_worktree commits work.txt; _git_state pins base_equivalent=False
Native result reports the renewal same test — result["lease_renewal"]["renewed"] and "Renewed the expired" in result["message"]
Prior PID + prior expiry, replacement PID + new expiry test_renewed_lock_records_prior_and_replacement_evidence; replacement_pid additionally asserted in the unit suite (test_evidence_records_both_sides_of_the_transition, test_record_captures_prior_and_replacement_state)
Generation advances exactly once same test — lock_generation(written) == prior_generation + 1
Renewed lock live and passes verify_lock_for_mutation test_renewed_lock_is_live_and_satisfies_mutation_ownership
Live-PID renewal never claims dead-session recovery test_recovery_record_is_not_written_for_a_live_owner_renewal
Foreign claimant / foreign profile fail closed test_foreign_claimant_cannot_use_the_waiver, test_foreign_profile_cannot_use_the_waiver
Unpublished branch / mismatched PR head fail closed test_unpublished_branch_cannot_use_the_waiver, test_pr_head_mismatch_cannot_use_the_waiver
Dirty worktree protection enforced test_dirty_worktree_still_blocks_a_would_be_renewal
Waiver unavailable without a valid sanction test_non_base_equivalent_branch_still_blocks_without_any_waiver

I confirmed the generation assertion is a real single advance: bind_session_lock writes current_generation + 1 once (issue_lock_store.py:241), and renewal now supplies expected_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), with ConflictGateOrdering covering 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 returns REFUSED naming the exact element. AC9 — build_renewal_record captures both sides of the transition. AC10 — proven end to end through verify_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_lock and bind_session_lock both default renewal_sanctioned=False, and the two tests/test_issue_772_unpublished_claim_recovery.py doubles 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 at a30a3ce4c37b2dde725301bef8b9ef8e04160088, clean before and after; venv/bin/python -m pytest (pytest 9.1.1):

  • tests/test_issue_760_mcp_renewal_path.py10 passed
  • Both Issue #760 suites — 50 passed
  • Lock/lease regression set (10 files) — 200 passed, 2 subtests passed
  • Full suite tests/ with --tb=no so no environment values could print — 4240 passed, 11 failed, 6 skipped, 493 subtests passed

4240 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-pr791 detached at master 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 and re-ran those exact 11 node IDs there with tracebacks suppressed — 11 failed, the identical node IDs. Six in test_commit_payloads.py, two in test_issue_702_review_findings_f1_f6.py, plus 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, and test_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::TestF2PreflightPathVerification flagged 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

  • F3 (unchanged). For an expired lock both assessments still run and each makes its own branch and PR round trips. When 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_recovery and lease_renewal together. 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.
  • F4 (unchanged). Contending with a live foreign lease can still surface a preflight or worktree error before the precise lease-conflict message. Fail-closed behavior is preserved.
  • New, non-blocking. owning_pr_renewal_evidence has 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 injects read_worktree_git_state while 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 a30a3ce4c3 through the sanctioned merger workflow.
NEXT_PROMPT:

Merge Gitea-Tools PR #791 in Scaled-Tech-Consulting/Gitea-Tools on the prgs remote. It closes Issue #760 and is approved at head a30a3ce4c37b2dde725301bef8b9ef8e04160088 against base master. Adopt the reviewer lease from session 7835-d4bf525741c2 with gitea_adopt_merger_pr_lease pinned to that exact head rather than acquiring a new one, from a clean merger worktree under branches/. Confirm approval_at_current_head, that the head has not moved, that the PR is still open and mergeable, and that master is freshly fetched, then merge. Do not update or rebase the author branch. After merging, hand branch and worktree cleanup to a prgs-reconciler session with gitea.branch.delete capability proof, and hand control back to the controller so Issue #790 can be reassessed against the renewal behavior this PR delivers.

WHAT_HAPPENED: Re-reviewed PR #791 at head a30a3ce4c3 after 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: a30a3ce4c3
REVIEW_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 1a97ced133 carried 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 a30a3ce4c3 confirmed 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 at 3d0c13fa5a. 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 is 3d0c13fa5a and the head is not an ancestor of master, so the PR is not already landed. Tests from the reviewer worktree at a30a3ce4: 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 master 3d0c13fa5a, re-run in this session. Reviewer worktree and baseline worktree clean before and after validation; the stable control checkout stayed on master at 3d0c13fa5a and 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)

## APPROVE — both review #499 blocking findings are remediated Re-reviewed the complete PR (both commits `1a97ced1` and `a30a3ce4`) at head `a30a3ce4c37b2dde725301bef8b9ef8e04160088`, base `master`, in a fresh session-owned detached reviewer worktree at `branches/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_worktree` now takes `renewal_sanctioned` (`issue_lock_worktree.py:288`) and `gitea_lock_issue` passes it (`gitea_mcp_server.py:4125`), so the waiver computed at `gitea_mcp_server.py:4074` survives to the gate that previously discarded it. Verified against this head: - **Waiver is scoped to proven ownership.** Base-equivalence is bypassed only under `recovery_sanctioned or renewal_sanctioned` (`issue_lock_worktree.py:331`). Both are server-computed; neither is reachable from a tool parameter. - **Cleanliness is evaluated before the waiver and is never relaxed.** `parse_dirty_tracked_files` runs at `issue_lock_worktree.py:322` and appends its reason at `:324-329`, above the waiver branch. `test_dirty_worktree_still_blocks_a_would_be_renewal` proves the tool still refuses, and the refusal names the file. - **The two dispositions stay distinct.** `_assessment` reports `recovery_sanctioned` and `renewal_sanctioned` as separate fields (`issue_lock_worktree.py:439-440`); only the derived `base_equivalence_waived` is combined, and it is labelled as derived. On the lock record, `dead_session_recovery` and `lease_renewal` remain separate blocks. - **No caller can supply or forge the waiver.** `gitea_lock_issue` gains no parameter (signature unchanged). `renewal_sanctioned` is keyword-only and defaults to `False` on `assess_same_issue_lease_conflict`, `bind_session_lock`, `_save_issue_lock`, and `assess_issue_lock_worktree`. - **Ordinary unsanctioned non-base-equivalent worktrees still fail closed.** The store waiver requires *both* the locally recomputed `same_owner` and the server-proven flag (`issue_lock_store.py:529`); `test_non_base_equivalent_branch_still_blocks_without_any_waiver` proves the tool-level refusal with no durable lock present. - **Issue #753 recovery is unchanged.** `issue_lock_recovery.py` is not in the diff; the recovery assessment, its gate condition (`not is_lease_live`), and `assess_expired_lock_reclaim` are untouched. I also re-checked that moving `assess_same_issue_lease_conflict` below 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 inside `bind_session_lock` under 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: - **Applies only after a valid sanction.** It returns `None` unless `outcome == RENEWAL_SANCTIONED` *and* `renewal_sanctioned` is truthy, and the call site is guarded by `if recovered_owning_pr is None and renewal_sanctioned` (`gitea_mcp_server.py:4110`). - **Names only the exact owned PR.** `pr_number` is set only for an open PR whose head ref equals the locked branch (`gitea_mcp_server.py:2500-2506`). - **All three heads must agree.** The assessor requires local == remote and PR == local (`issue_lock_renewal.py:333-344`), and the evidence builder re-checks `pr_head != local_head or pr_head != remote_head` before emitting anything (`:420`), so a truncated or hand-built evidence map authorizes nothing. - **Absent / stale / mismatched / foreign evidence fails closed.** Missing `pr_number`, `branch_name`, or `pr_head` returns `None` (`:416`); non-integer ids return `None` (`:422-426`). - **No duplicate work, no bypass of unrelated open PRs.** The token is re-validated by the unchanged `_assess_owning_pr_exemption` against 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. - **#755 behavior is unchanged.** `issue_lock_recovery.owning_pr_recovery_evidence` is 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.py` drives the real `mcp_server.gitea_lock_issue` against a real git repository (real `git init`, a seed commit on `master`, a second commit on the work branch) and a real durable lock file written through `issue_lock_store.save_lock_file`, with `base_equivalent=False` — the exact condition that defeated the previous head. Coverage confirmed case by case: | Requirement | Test | |---|---| | Expired exact-owner lease, live recorded PID, renews | `test_expired_lease_live_pid_exact_owner_renews_through_the_tool` (asserts `is_lease_expired` and `is_process_alive` on the prior lock first) | | Committed, non-base-equivalent branch | `_init_worktree` commits `work.txt`; `_git_state` pins `base_equivalent=False` | | Native result reports the renewal | same test — `result["lease_renewal"]["renewed"]` and `"Renewed the expired"` in `result["message"]` | | Prior PID + prior expiry, replacement PID + new expiry | `test_renewed_lock_records_prior_and_replacement_evidence`; `replacement_pid` additionally asserted in the unit suite (`test_evidence_records_both_sides_of_the_transition`, `test_record_captures_prior_and_replacement_state`) | | Generation advances exactly once | same test — `lock_generation(written) == prior_generation + 1` | | Renewed lock live and passes `verify_lock_for_mutation` | `test_renewed_lock_is_live_and_satisfies_mutation_ownership` | | Live-PID renewal never claims dead-session recovery | `test_recovery_record_is_not_written_for_a_live_owner_renewal` | | Foreign claimant / foreign profile fail closed | `test_foreign_claimant_cannot_use_the_waiver`, `test_foreign_profile_cannot_use_the_waiver` | | Unpublished branch / mismatched PR head fail closed | `test_unpublished_branch_cannot_use_the_waiver`, `test_pr_head_mismatch_cannot_use_the_waiver` | | Dirty worktree protection enforced | `test_dirty_worktree_still_blocks_a_would_be_renewal` | | Waiver unavailable without a valid sanction | `test_non_base_equivalent_branch_still_blocks_without_any_waiver` | I confirmed the generation assertion is a real single advance: `bind_session_lock` writes `current_generation + 1` once (`issue_lock_store.py:241`), and renewal now supplies `expected_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`), with `ConflictGateOrdering` covering 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 returns `REFUSED` naming the exact element. AC9 — `build_renewal_record` captures both sides of the transition. AC10 — proven end to end through `verify_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_lock` and `bind_session_lock` both default `renewal_sanctioned=False`, and the two `tests/test_issue_772_unpublished_claim_recovery.py` doubles 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 at `a30a3ce4c37b2dde725301bef8b9ef8e04160088`, clean before and after; `venv/bin/python -m pytest` (pytest 9.1.1): - `tests/test_issue_760_mcp_renewal_path.py` — **10 passed** - Both Issue #760 suites — **50 passed** - Lock/lease regression set (10 files) — **200 passed, 2 subtests passed** - Full suite `tests/` with `--tb=no` so no environment values could print — **4240 passed, 11 failed, 6 skipped, 493 subtests passed** 4240 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-pr791` detached at `master` `3d0c13fa5a756ff94441dc57cf4e74318f45ba41` and re-ran those exact 11 node IDs there with tracebacks suppressed — **11 failed**, the identical node IDs. Six in `test_commit_payloads.py`, two in `test_issue_702_review_findings_f1_f6.py`, plus `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`, and `test_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::TestF2PreflightPathVerification` flagged 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 - **F3 (unchanged).** For an expired lock both assessments still run and each makes its own branch and PR round trips. When 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_recovery` and `lease_renewal` together. 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. - **F4 (unchanged).** Contending with a live foreign lease can still surface a preflight or worktree error before the precise lease-conflict message. Fail-closed behavior is preserved. - **New, non-blocking.** `owning_pr_renewal_evidence` has 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 injects `read_worktree_git_state` while 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 a30a3ce4c37b2dde725301bef8b9ef8e04160088 through the sanctioned merger workflow. NEXT_PROMPT: ```text Merge Gitea-Tools PR #791 in Scaled-Tech-Consulting/Gitea-Tools on the prgs remote. It closes Issue #760 and is approved at head a30a3ce4c37b2dde725301bef8b9ef8e04160088 against base master. Adopt the reviewer lease from session 7835-d4bf525741c2 with gitea_adopt_merger_pr_lease pinned to that exact head rather than acquiring a new one, from a clean merger worktree under branches/. Confirm approval_at_current_head, that the head has not moved, that the PR is still open and mergeable, and that master is freshly fetched, then merge. Do not update or rebase the author branch. After merging, hand branch and worktree cleanup to a prgs-reconciler session with gitea.branch.delete capability proof, and hand control back to the controller so Issue #790 can be reassessed against the renewal behavior this PR delivers. ``` WHAT_HAPPENED: Re-reviewed PR #791 at head a30a3ce4c37b2dde725301bef8b9ef8e04160088 after 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: a30a3ce4c37b2dde725301bef8b9ef8e04160088 REVIEW_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 1a97ced133fbb16a560ea4c3bf71b97b7401950c carried 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 a30a3ce4c37b2dde725301bef8b9ef8e04160088 confirmed 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 at 3d0c13fa5a756ff94441dc57cf4e74318f45ba41. 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 is 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 and the head is not an ancestor of master, so the PR is not already landed. Tests from the reviewer worktree at a30a3ce4: 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 master 3d0c13fa5a756ff94441dc57cf4e74318f45ba41, re-run in this session. Reviewer worktree and baseline worktree clean before and after validation; the stable control checkout stayed on master at 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 and 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)
Owner

CTH: Reviewer Handoff — PR #791 approved at a30a3ce4, ready for merger

Review 500 (APPROVED) is posted at head a30a3ce4c37b2dde725301bef8b9ef8e04160088. Read back after submission: approval_at_current_head true, latest_approved_head_sha equals the current head, stale false, dismissed false, quarantined false, has_blocking_change_requests false, review_feedback_stale false. gitea_assess_pr_sync_status returns recommended_next_action: merge_now, commits_behind: 0, mergeable: true, has_conflicts: false, approval_valid_for_merge: true, stale_approval: false, checks not_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_feedback reported dismissed: false for #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 head 1a97ced133fbb16a560ea4c3bf71b97b7401950c, submitted 2026-07-21T23:59:25-05:00, complete body intact, quarantined: false. Only the server-side dismissed flag 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_sanctioned is threaded into assess_issue_lock_worktree and passed from gitea_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_evidence exempts 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.py drives native gitea_lock_issue end 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-760 at a30a3ce4: 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 master 3d0c13fa5a756ff94441dc57cf4e74318f45ba41, 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 about owning_pr_renewal_evidence negatives.

Mutation ledger for this reviewer run

  • File edits by reviewer: none
  • Worktree/index mutations: git worktree add --detach branches/review-pr791-issue-760 a30a3ce4; git worktree add --detach branches/baseline-master-pr791 3d0c13fa
  • Git ref mutations: git fetch prgs for master and the PR branch
  • Review mutations: gitea_mark_final_review_decision (approve, head-pinned); gitea_submit_pr_review APPROVED, review 500
  • MCP/Gitea mutations: reviewer lease acquired (comment 13921), heartbeated (comment 13923), this handoff comment
  • Merge mutations: none
  • Cleanup mutations: none
  • External-state mutations: none

The stable control checkout stayed on master at 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 and 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 a30a3ce4c3 through the sanctioned merger workflow.
NEXT_PROMPT:

Merge Gitea-Tools PR #791 in Scaled-Tech-Consulting/Gitea-Tools on the prgs remote. It closes Issue #760 and is approved by review 500 at head a30a3ce4c37b2dde725301bef8b9ef8e04160088 against base master. Adopt the reviewer lease from session 7835-d4bf525741c2 with gitea_adopt_merger_pr_lease pinned to that exact head rather than acquiring a new one, from a clean merger worktree under branches/. Confirm approval_at_current_head, that the head has not moved, that the PR is still open and mergeable, and that master is freshly fetched, then merge. Do not update or rebase the author branch. After merging, hand branch and worktree cleanup to a prgs-reconciler session with gitea.branch.delete capability proof, and hand control back to the controller so Issue #790 can be reassessed against the renewal behavior this PR delivers.

WHAT_HAPPENED: Re-reviewed PR #791 at head a30a3ce4c3 after 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: a30a3ce4c3
REVIEW_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 1a97ced133 is 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 a30a3ce4c3 equal 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 at 3d0c13fa5a. Reviewer lease session 7835-d4bf525741c2 held at the exact head throughout. Tests from branches/review-pr791-issue-760 at a30a3ce4: 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 master 3d0c13fa5a. 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)

## CTH: Reviewer Handoff — PR #791 approved at a30a3ce4, ready for merger Review **500** (APPROVED) is posted at head `a30a3ce4c37b2dde725301bef8b9ef8e04160088`. Read back after submission: `approval_at_current_head` true, `latest_approved_head_sha` equals the current head, `stale` false, `dismissed` false, `quarantined` false, `has_blocking_change_requests` false, `review_feedback_stale` false. `gitea_assess_pr_sync_status` returns `recommended_next_action: merge_now`, `commits_behind: 0`, `mergeable: true`, `has_conflicts: false`, `approval_valid_for_merge: true`, `stale_approval: false`, checks `not_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_feedback` reported `dismissed: false` for #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 head `1a97ced133fbb16a560ea4c3bf71b97b7401950c`, submitted 2026-07-21T23:59:25-05:00, complete body intact, `quarantined: false`. Only the server-side `dismissed` flag 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_sanctioned` is threaded into `assess_issue_lock_worktree` and passed from `gitea_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_evidence` exempts 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.py` drives native `gitea_lock_issue` end 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-760` at `a30a3ce4`: 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 master `3d0c13fa5a756ff94441dc57cf4e74318f45ba41`, 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 about `owning_pr_renewal_evidence` negatives. ### Mutation ledger for this reviewer run * File edits by reviewer: none * Worktree/index mutations: `git worktree add --detach branches/review-pr791-issue-760 a30a3ce4`; `git worktree add --detach branches/baseline-master-pr791 3d0c13fa` * Git ref mutations: `git fetch prgs` for master and the PR branch * Review mutations: `gitea_mark_final_review_decision` (approve, head-pinned); `gitea_submit_pr_review` APPROVED, review 500 * MCP/Gitea mutations: reviewer lease acquired (comment 13921), heartbeated (comment 13923), this handoff comment * Merge mutations: none * Cleanup mutations: none * External-state mutations: none The stable control checkout stayed on master at `3d0c13fa5a756ff94441dc57cf4e74318f45ba41` and 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 a30a3ce4c37b2dde725301bef8b9ef8e04160088 through the sanctioned merger workflow. NEXT_PROMPT: ```text Merge Gitea-Tools PR #791 in Scaled-Tech-Consulting/Gitea-Tools on the prgs remote. It closes Issue #760 and is approved by review 500 at head a30a3ce4c37b2dde725301bef8b9ef8e04160088 against base master. Adopt the reviewer lease from session 7835-d4bf525741c2 with gitea_adopt_merger_pr_lease pinned to that exact head rather than acquiring a new one, from a clean merger worktree under branches/. Confirm approval_at_current_head, that the head has not moved, that the PR is still open and mergeable, and that master is freshly fetched, then merge. Do not update or rebase the author branch. After merging, hand branch and worktree cleanup to a prgs-reconciler session with gitea.branch.delete capability proof, and hand control back to the controller so Issue #790 can be reassessed against the renewal behavior this PR delivers. ``` WHAT_HAPPENED: Re-reviewed PR #791 at head a30a3ce4c37b2dde725301bef8b9ef8e04160088 after 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: a30a3ce4c37b2dde725301bef8b9ef8e04160088 REVIEW_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 1a97ced133fbb16a560ea4c3bf71b97b7401950c is 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 a30a3ce4c37b2dde725301bef8b9ef8e04160088 equal 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 at 3d0c13fa5a756ff94441dc57cf4e74318f45ba41. Reviewer lease session 7835-d4bf525741c2 held at the exact head throughout. Tests from branches/review-pr791-issue-760 at a30a3ce4: 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 master 3d0c13fa5a756ff94441dc57cf4e74318f45ba41. 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)
Owner

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: a30a3ce4c3
target_branch: master
target_branch_sha: 3d0c13fa5a
last_activity: 2026-07-22T06:01:51Z
expires_at: 2026-07-22T06:11:51Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: a30a3ce4c37b2dde725301bef8b9ef8e04160088 target_branch: master target_branch_sha: 3d0c13fa5a756ff94441dc57cf4e74318f45ba41 last_activity: 2026-07-22T06:01:51Z expires_at: 2026-07-22T06:11:51Z blocker: none
sysadmin merged commit 620ed6e9a9 into master 2026-07-22 01:02:40 -05:00
Owner

Stale #332 review-decision lock cleanup (#594)

Status: APPLIED

Manual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.

## Stale #332 review-decision lock cleanup (#594) Status: **APPLIED** - actor: `sysadmin` - profile: `prgs-merger` - timestamp: `2026-07-22T06:02:41.788228+00:00` - last terminal: `approve` on PR #791 - PR state: `closed` (merged=True) - merge_commit_sha: `620ed6e9a9550b8da2ceb82d9ab8744e8920490f` - prior live_mutations_count: `2` - prior profile_identity: `prgs-reviewer` Manual deletion of session-state files is **not** the workflow. This path only clears a lock when the referenced PR is merged/closed.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#791