fix(bootstrap): address PR #853 REQUEST_CHANGES findings (#850)

- Remove /branches/ string-split fallback in resolve_canonical_repo_root;
  recover roots via commonpath ancestry only (review #531 F2).
- Refuse existing branches that do not contain live master; no weak
  merge-base acceptance (F3).
- Verify caller-supplied assignment_id/lease_id against the control plane
  or fail closed (F4).
- Compensating recovery releases bound workflow leases via lease_lifecycle (F5).
- Regression tests for each finding.
This commit is contained in:
2026-07-24 07:46:34 -04:00
parent 0ae05cb9bc
commit 06e95254f0
4 changed files with 299 additions and 30 deletions
+152 -22
View File
@@ -24,6 +24,7 @@ import subprocess
from typing import Any, Mapping
import author_mutation_worktree
import control_plane_db
import issue_lock_store
import issue_lock_worktree
import lease_lifecycle
@@ -123,10 +124,114 @@ def derive_default_idempotency_key(
return ":".join(parts)
def _verify_assignment_and_lease_ids(
*,
assignment_id: str | None,
lease_id: str | None,
issue_number: int,
owner_session: str,
remote: str,
org: str | None,
repo: str | None,
db: control_plane_db.ControlPlaneDB | None = None,
) -> dict[str, Any] | None:
"""Fail closed when caller-supplied assignment/lease IDs are unverified (#531 F4).
Both IDs are optional together. When either is supplied, both must be
present and must resolve to the same live control-plane lease/assignment
bound to this issue and owner session. Fabricated identifiers never pass.
"""
asn = (assignment_id or "").strip()
lid = (lease_id or "").strip()
if not asn and not lid:
return None
if not asn or not lid:
return {
"success": False,
"reason_code": "incomplete_assignment_lease_ids",
"message": (
"assignment_id and lease_id must be supplied together when "
"either is provided (fail closed)."
),
"exact_next_action": (
"Pass both identifiers from allocate_next_work / control-plane "
"assignment proof, or omit both."
),
}
try:
store = db if db is not None else control_plane_db.ControlPlaneDB()
state = store.get_lease_workflow_state(lid)
except Exception as exc:
return {
"success": False,
"reason_code": "assignment_lease_lookup_failed",
"message": f"Could not verify assignment/lease against control plane: {exc}",
"exact_next_action": (
"Ensure the control-plane DB is available and retry with live IDs."
),
}
if not state or not state.get("lease"):
return {
"success": False,
"reason_code": "unknown_lease_id",
"message": f"lease_id '{lid}' is not present in the control plane (fail closed).",
"exact_next_action": "Pass a live lease_id from control-plane assignment.",
}
lease = state["lease"]
assignment = state.get("assignment") or {}
work = state.get("work_item") or {}
recorded_asn = str(assignment.get("assignment_id") or "").strip()
if recorded_asn and recorded_asn != asn:
return {
"success": False,
"reason_code": "assignment_lease_mismatch",
"message": (
f"assignment_id '{asn}' does not match lease '{lid}' "
f"(recorded assignment '{recorded_asn}') (fail closed)."
),
"exact_next_action": "Re-read allocate_next_work proof and pass matching IDs.",
}
if not recorded_asn:
# Some lease rows may not yet have an assignment join; still require
# the lease itself to exist and bind to the claimed session/issue.
pass
lease_session = str(lease.get("session_id") or "").strip()
if lease_session and lease_session != owner_session:
return {
"success": False,
"reason_code": "lease_session_mismatch",
"message": (
f"lease_id '{lid}' is owned by session '{lease_session}', not "
f"'{owner_session}' (fail closed)."
),
"exact_next_action": "Use the session that holds the lease, or re-allocate.",
}
# Bind issue number when the work item records one.
work_number = work.get("number") or work.get("issue_number") or assignment.get("issue_number")
try:
if work_number is not None and int(work_number) != int(issue_number):
return {
"success": False,
"reason_code": "lease_issue_mismatch",
"message": (
f"lease_id '{lid}' is bound to issue #{work_number}, not "
f"#{issue_number} (fail closed)."
),
"exact_next_action": "Pass the lease issued for this issue number.",
}
except (TypeError, ValueError):
pass
_ = (remote, org, repo) # reserved for host-scoped DBs
return None
def run_compensating_recovery(
journal: dict[str, Any],
canonical_repo_root: str,
journal_dir: str | None = None,
*,
db: control_plane_db.ControlPlaneDB | None = None,
) -> dict[str, Any]:
"""Execute compensating recovery for artifacts created by this transition only."""
artifacts = journal.get("artifacts_created") or {}
@@ -135,10 +240,22 @@ def run_compensating_recovery(
worktree_path = journal.get("worktree_path")
branch_name = journal.get("branch_name")
# Roll back workflow lease/assignment when this transition bound one (#531 F5).
lease_id = str(journal.get("lease_id") or "").strip()
session_id = str(journal.get("owner_session") or "").strip()
if lease_id and session_id:
try:
store = db if db is not None else control_plane_db.ControlPlaneDB()
lease_lifecycle.release_lease(
store, lease_id=lease_id, session_id=session_id
)
rolled_back.append(f"lease:{lease_id}")
except Exception as exc:
rolled_back.append(f"lease_release_failed:{lease_id}:{type(exc).__name__}")
# Roll back issue lock if created
if artifacts.get("lock_created") or pending.get("lock"):
issue_num = journal.get("issue_number")
session_id = journal.get("owner_session")
if issue_num and session_id:
try:
issue_lock_store.release_session_lock(
@@ -510,6 +627,19 @@ def bootstrap_author_issue_worktree(
lease_id=lease_id,
)
# Review #531 Finding 4: never embed unverified caller-supplied IDs.
id_block = _verify_assignment_and_lease_ids(
assignment_id=assignment_id,
lease_id=lease_id,
issue_number=issue_number,
owner_session=session,
remote=remote,
org=org,
repo=repo,
)
if id_block is not None:
return id_block
# Acquire cross-process file lock scoped to the idempotency key / transition identity
with BootstrapTransitionLock(key, journal_dir=lock_dir):
# Idempotency check
@@ -677,29 +807,29 @@ def bootstrap_author_issue_worktree(
text=True,
check=False,
)
# F-8 / review #531 Finding 3: require the existing branch head to
# *contain* live master (is-ancestor) or equal it. Sharing any
# historical merge-base is not enough — that would accept stale or
# diverged branches that merely share history with master.
if anc_check.returncode != 0 and branch_head.lower() != live_master_sha.lower():
# F-8: Check if branch shares a common ancestor with live master
mb_check = subprocess.run(
["git", "-C", root, "merge-base", live_master_sha, branch_head],
capture_output=True,
text=True,
check=False,
journal["failure_reason"] = (
f"existing branch '{target_branch}' HEAD ({branch_head[:12]}) "
f"does not contain live master ({live_master_sha[:12]})"
)
if mb_check.returncode != 0 or not mb_check.stdout.strip():
journal["failure_reason"] = (
f"existing branch '{target_branch}' HEAD ({branch_head[:12]}) is incompatible with live master ({live_master_sha[:12]})"
)
save_phase_journal(journal, journal_dir=lock_dir)
return {
"success": False,
"reason_code": "incompatible_existing_branch",
"message": (
f"Existing branch '{target_branch}' HEAD ({branch_head[:12]}) is incompatible with live master ({live_master_sha[:12]})."
),
"exact_next_action": (
"Inspect or sync the existing branch with master before bootstrapping."
),
}
save_phase_journal(journal, journal_dir=lock_dir)
return {
"success": False,
"reason_code": "incompatible_existing_branch",
"message": (
f"Existing branch '{target_branch}' HEAD ({branch_head[:12]}) "
f"does not contain live master ({live_master_sha[:12]}). "
"Stale or diverged branches are refused (fail closed)."
),
"exact_next_action": (
"Update the branch by merging current master (no rebase/"
"force-push), or choose a branch that already contains master."
),
}
# Preserve creation provenance monotonically across interruption and replay
if was_branch_created_previously or pending_branch == target_branch:
journal["artifacts_created"]["branch_created"] = True