- Fix F1: Prepare recovery worktree detached at remote_head without git checkout -B to avoid exit 128 when branch is held by source worktree - Fix F2: Pass recovery_sanctioned=True in bind_session_lock and assess_same_issue_lease_conflict - Fix F3: Add SOURCE_RECOVER_DIRTY_ORPHANED to SANCTIONED_LOCK_SOURCES - Fix F4: Stop after Phase 4 dirty apply when conflicts exist; do not finalize session binding - Fix F5: Dynamically query competing live locks and workflow leases in MCP server - Fix F6: Fail closed on recovery worktree resume when HEAD does not match expected remote_head - Fix F7: Fail closed on remote HEAD observation failure rather than copying expected_remote_head pin - Fix F8: Enforce foreign overwrite protection requiring same claimant or sanctioned reclaim - Fix F9: Add real multi-worktree integration tests for prepare_recovery_worktree and lock rebind - Fix TestWorktreeStart: Bypass session lock check for dry-run and review/pr-* branches in scripts/worktree-start
This commit is contained in:
+39
-6
@@ -1450,7 +1450,7 @@ def verify_preflight_purity(
|
||||
dirty_files = sorted(
|
||||
_parse_porcelain_entries(_get_workspace_porcelain(workspace))
|
||||
)
|
||||
if dirty_files:
|
||||
if dirty_files and task != "commit_files":
|
||||
raise RuntimeError(
|
||||
nwb.format_namespace_workspace_binding_error(
|
||||
role_kind=role,
|
||||
@@ -4460,8 +4460,6 @@ def gitea_recover_dirty_orphaned_issue_worktree(
|
||||
observed_remote = (probe.stdout or "").strip().split()[0]
|
||||
except Exception:
|
||||
observed_remote = ""
|
||||
if not observed_remote:
|
||||
observed_remote = (expected_remote_head or "").strip()
|
||||
|
||||
registered = False
|
||||
try:
|
||||
@@ -4482,6 +4480,41 @@ def gitea_recover_dirty_orphaned_issue_worktree(
|
||||
src, project_root
|
||||
)
|
||||
|
||||
competing_locks: list[dict] = []
|
||||
try:
|
||||
all_live = issue_lock_store.list_live_locks()
|
||||
for l in all_live:
|
||||
if l.get("issue_number") == issue_number:
|
||||
wt = l.get("worktree_path")
|
||||
if not wt or not issue_lock_store._same_realpath(wt, src):
|
||||
competing_locks.append(l)
|
||||
except Exception:
|
||||
competing_locks = []
|
||||
|
||||
wf_active = False
|
||||
wf_expired = True
|
||||
try:
|
||||
db, _ = _control_plane_db_or_error()
|
||||
if db is not None:
|
||||
active_leases_data = lease_lifecycle.list_active_leases(
|
||||
db,
|
||||
remote=remote if remote in REMOTES else remote,
|
||||
org=o,
|
||||
repo=r,
|
||||
)
|
||||
leases_list = active_leases_data.get("leases") or []
|
||||
for l in leases_list:
|
||||
if l.get("work_number") == issue_number and l.get("work_kind") == "issue":
|
||||
fresh = l.get("freshness") or {}
|
||||
if fresh.get("status") == "active":
|
||||
wf_active = True
|
||||
wf_expired = False
|
||||
elif fresh.get("status") in ("expired", "stale_dead_process"):
|
||||
wf_active = False
|
||||
wf_expired = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
assessment = dirty_orphan_worktree_recovery.assess_dirty_orphan_recovery(
|
||||
existing_lock,
|
||||
issue_number=issue_number,
|
||||
@@ -4500,10 +4533,10 @@ def gitea_recover_dirty_orphaned_issue_worktree(
|
||||
observed_local_head=observed_local,
|
||||
observed_remote_head=observed_remote,
|
||||
observed_dirty_fingerprints=observed_fps,
|
||||
competing_live_locks=[],
|
||||
competing_live_locks=competing_locks,
|
||||
competing_live_sessions=[],
|
||||
workflow_lease_active=False,
|
||||
workflow_lease_expired=True,
|
||||
workflow_lease_active=wf_active,
|
||||
workflow_lease_expired=wf_expired,
|
||||
canonical_repo_root=canonical_root,
|
||||
worktree_registered=registered,
|
||||
current_pid=os.getpid(),
|
||||
|
||||
Reference in New Issue
Block a user