344dc41ce236ea9ca22484e3250dc6db68168493
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a30a3ce4c3 |
fix(lock): make the exact-owner renewal waiver survive downstream gates (#760)
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
|
||
|
|
1a97ced133 |
feat(lock): allow exact-owner renewal of expired author issue locks (Closes #760)
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 |