fix(author-lock): dead-session recovery cross-session PR base-sync (Closes #872)
This commit is contained in:
+24
-15
@@ -150,23 +150,14 @@ def read_merge_sync_provenance(
|
||||
*,
|
||||
prior_head_sha: str | None,
|
||||
synced_head_sha: str | None,
|
||||
remote: str | None = None,
|
||||
) -> dict:
|
||||
"""Observe whether ``synced_head_sha`` is a sanctioned merge-based branch sync
|
||||
that advanced the PR branch past ``prior_head_sha`` (#871).
|
||||
"""Observe whether ``synced_head_sha`` is a sanctioned merge-sync of a base
|
||||
into the branch above ``prior_head_sha`` (#871/#872).
|
||||
|
||||
``gitea_update_pr_branch_by_merge`` advances a PR branch by merging the base
|
||||
branch *into* the branch (``POST /pulls/{n}/update?style=merge``). The result
|
||||
is a merge commit ``M`` on the branch whose **first** parent is the prior
|
||||
branch head and whose second parent is the base tip. When the owning session
|
||||
then dies without the durable lock's recorded head being refreshed, the local
|
||||
worktree still sits at ``prior_head_sha`` while the live PR head is ``M``.
|
||||
|
||||
Recovering that drift safely requires proving the remote head is *exactly*
|
||||
such a merge-sync — not a rewrite, rebase, force-push, or an unrelated
|
||||
commit. This is that server-side observation. It reports facts only; the
|
||||
disposition lives in ``issue_lock_recovery``. Every field is read from git in
|
||||
the declared worktree — nothing is supplied by, or reachable from, an MCP
|
||||
caller (#871).
|
||||
Reports server-derived git facts only; the recovery disposition lives in
|
||||
``issue_lock_recovery``. All comparisons are executed locally in the
|
||||
declared worktree -- nothing is taken from caller parameters.
|
||||
|
||||
Provenance is proven only when ALL hold:
|
||||
|
||||
@@ -183,6 +174,7 @@ def read_merge_sync_provenance(
|
||||
path = (worktree_path or "").strip()
|
||||
prior = (prior_head_sha or "").strip()
|
||||
synced = (synced_head_sha or "").strip()
|
||||
target_remote = (remote or "").strip() or None
|
||||
result: dict = {
|
||||
"prior_head_sha": prior or None,
|
||||
"synced_head_sha": synced or None,
|
||||
@@ -235,6 +227,23 @@ def read_merge_sync_provenance(
|
||||
try:
|
||||
result["prior_present"] = _present(prior)
|
||||
result["synced_present"] = _present(synced)
|
||||
if not result["synced_present"] and path and os.path.isdir(path):
|
||||
if target_remote:
|
||||
subprocess.run(
|
||||
["git", "-C", path, "fetch", target_remote, "--quiet"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
result["synced_present"] = _present(synced)
|
||||
if not result["synced_present"]:
|
||||
subprocess.run(
|
||||
["git", "-C", path, "fetch", "--quiet"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
result["synced_present"] = _present(synced)
|
||||
except OSError as exc: # git unavailable — fail closed, never assume
|
||||
result["reasons"].append(f"merge-sync provenance probe could not run: {exc}")
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user