79334d48408fd446ddf1e8be332495960b847af6
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
79334d4840 |
fix(gate): preserve exact-owner renewal evidence across duplicate rechecks (Closes #945)
An exact-owner lease renewal (#760) is granted the owning-PR duplicate-work
waiver inside gitea_lock_issue, but every later enforcement path rebuilt that
proof from the durable lock through
issue_lock_recovery.recovered_owning_pr_from_lock, which reads only the
dead_session_recovery block. A plain renewal persists its proof under
lease_renewal instead, so the waiver expired with the lock call and the next
author mutation was refused duplicate_commit_prevented with
owning_pr_recovery_exempted: false on the very PR the renewal had just proved.
Add issue_lock_renewal.owning_pr_renewal_from_lock as the renewal mirror of the
recovery rebuild, and resolve both halves through one helper,
_owning_pr_continuation_from_lock, in the same precedence gitea_lock_issue
applies. The three enforcement paths that previously saw only recovery evidence
now share that resolver: the commit/create-PR duplicate recheck, the read-only
duplicate assessor, and the push-ownership prover.
The rebuild re-applies the equality the renewal assessor required (PR head ==
local head == remote head) and additionally binds the record to the claimant the
lock names, so a renewal block cannot be reused under another identity, profile,
or workflow session. Validation of the resulting token against live PR state is
unchanged and still owned solely by
issue_work_duplicate_gate._assess_owning_pr_exemption, so repository, issue, PR,
branch and head binding continue to be enforced in exactly one place.
No public signature, MCP tool schema, refusal shape, or reason code changes.
Dead-session recovery behaviour is untouched.
Tests: tests/test_issue_945_owning_pr_renewal_continuation.py - 49 tests,
8 subtests, all in-memory (no durable branch, worktree, lock, lease or PR).
Against unmodified
|
||
|
|
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 |