"""Sanctioned author issue worktree bootstrap for allocated issues (#850). Bootstraps an allocated author branch, canonical worktree under ``branches/``, worktree registration, issue lock, and lease/assignment binding without caller-side Git, Bash, or helper scripts. Features: 1. Durable phase journal with read-after-write evidence for every phase. 2. Idempotent replay handling via idempotency keys. 3. Authoritative expected-base / concurrency-pin validation. 4. Typed stale-pin refusal without silent rebasing or repointing. 5. Canonical branches-root enforcement (path MUST be inside branches/). 6. Preexisting work preservation (dirty tracked/untracked check, foreign ownership refusal). 7. Compensating recovery limited strictly to artifacts created by this transition. 8. Satisfiable exact_next_action for MCP scheduled workers. """ from __future__ import annotations import json import os import shutil import subprocess from typing import Any, Mapping import author_mutation_worktree import issue_lock_store import issue_lock_worktree import lease_lifecycle from reviewer_worktree import parse_dirty_tracked_files import task_capability_map BOOTSTRAP_TASKS = frozenset( { "bootstrap_author_issue_worktree", "gitea_bootstrap_author_issue_worktree", } ) PHASE_1_REQUEST_ACCEPTED = "1_request_accepted" PHASE_2_BRANCH_CONFIRMED = "2_branch_confirmed" PHASE_3_PATH_RESERVED = "3_path_reserved" PHASE_4_WORKTREE_CONFIRMED = "4_worktree_confirmed" PHASE_5_REGISTRATION_VERIFIED = "5_registration_verified" PHASE_6_STATE_ESTABLISHED = "6_state_established" PHASE_7_TRANSITION_COMPLETED = "7_transition_completed" PHASE_COMPENSATING_RECOVERY = "compensating_recovery" JOURNAL_DIR_NAME = "bootstrap-journals" def is_author_issue_bootstrap_task(task: str | None) -> bool: """True when *task* is the author issue worktree bootstrap task.""" return (task or "").strip() in BOOTSTRAP_TASKS def get_journal_dir(override: str | None = None) -> str: """Return the root directory for durable bootstrap phase journals.""" if override: path = override elif os.environ.get("GITEA_BOOTSTRAP_JOURNAL_DIR"): path = os.environ["GITEA_BOOTSTRAP_JOURNAL_DIR"] else: cache_dir = os.path.expanduser("~/.cache/gitea-tools") path = os.path.join(cache_dir, JOURNAL_DIR_NAME) os.makedirs(path, exist_ok=True) return path def _journal_file_path(idempotency_key: str, journal_dir: str | None = None) -> str: safe_key = "".join( c if c.isalnum() or c in ("-", "_", ".") else "_" for c in idempotency_key ) return os.path.join(get_journal_dir(journal_dir), f"{safe_key}.json") def load_phase_journal( idempotency_key: str, journal_dir: str | None = None ) -> dict[str, Any] | None: """Load a durable phase journal if it exists.""" path = _journal_file_path(idempotency_key, journal_dir=journal_dir) if not os.path.isfile(path): return None try: with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception: return None def save_phase_journal( journal: dict[str, Any], journal_dir: str | None = None ) -> None: """Persist a durable phase journal with write-through file sync.""" key = journal["idempotency_key"] path = _journal_file_path(key, journal_dir=journal_dir) tmp_path = f"{path}.tmp.{os.getpid()}" with open(tmp_path, "w", encoding="utf-8") as f: json.dump(journal, f, indent=2, sort_keys=True) os.replace(tmp_path, path) def derive_default_idempotency_key( remote: str, org: str | None, repo: str | None, issue_number: int, assignment_id: str | None = None, lease_id: str | None = None, ) -> str: parts = [ "bootstrap", (remote or "prgs").strip(), (org or "Scaled-Tech-Consulting").strip(), (repo or "Gitea-Tools").strip(), f"issue-{issue_number}", ] if assignment_id: parts.append(assignment_id.strip()) if lease_id: parts.append(lease_id.strip()) return ":".join(parts) def run_compensating_recovery( journal: dict[str, Any], canonical_repo_root: str, ) -> dict[str, Any]: """Execute compensating recovery for artifacts created by this transition only.""" artifacts = journal.get("artifacts_created") or {} rolled_back: list[str] = [] worktree_path = journal.get("worktree_path") branch_name = journal.get("branch_name") if ( artifacts.get("worktree_registered") or artifacts.get("worktree_dir_created") ) and worktree_path: if os.path.exists(worktree_path): try: subprocess.run( [ "git", "-C", canonical_repo_root, "worktree", "remove", "--force", worktree_path, ], capture_output=True, text=True, check=False, ) except Exception: pass if os.path.exists(worktree_path): shutil.rmtree(worktree_path, ignore_errors=True) try: subprocess.run( ["git", "-C", canonical_repo_root, "worktree", "prune"], capture_output=True, text=True, check=False, ) except Exception: pass rolled_back.append(f"worktree_path:{worktree_path}") if artifacts.get("branch_created") and branch_name: try: res = subprocess.run( [ "git", "-C", canonical_repo_root, "rev-parse", "--verify", branch_name, ], capture_output=True, text=True, check=False, ) if res.returncode == 0: subprocess.run( [ "git", "-C", canonical_repo_root, "branch", "-D", branch_name, ], capture_output=True, text=True, check=False, ) rolled_back.append(f"branch:{branch_name}") except Exception: pass recovery_info = { "executed": True, "rolled_back": rolled_back, "reason": journal.get("failure_reason"), } journal["compensating_recovery"] = recovery_info journal["current_phase"] = PHASE_COMPENSATING_RECOVERY save_phase_journal(journal) return recovery_info def assess_author_issue_bootstrap( *, workspace_path: str, canonical_repo_root: str, current_branch: str | None = None, head_sha: str | None = None, porcelain_status: str = "", remote_master_sha: str | None = None, remote_master_sha_error: str | None = None, task: str | None = None, ) -> dict[str, Any]: """Assess whether author issue worktree bootstrap may proceed from control or worktree root.""" root = os.path.realpath(canonical_repo_root or "") workspace = os.path.realpath(workspace_path or root or ".") branch = (current_branch or "").strip() dirty = parse_dirty_tracked_files(porcelain_status or "") under_branches = ( author_mutation_worktree.is_path_under_branches(workspace, root) if root else False ) if not is_author_issue_bootstrap_task(task): return { "not_applicable": True, "allowed": False, "block": False, "proven": False, "reasons": ["task is not author_issue_bootstrap"], } if under_branches: return { "not_applicable": False, "allowed": True, "block": False, "proven": True, "bootstrap_path": "existing_branches_worktree", "reasons": [ "workspace is already a registered worktree under branches/" ], } reasons: list[str] = [] if workspace != root: reasons.append( "bootstrap requires workspace to be canonical control checkout or branches/ worktree" ) if branch not in author_mutation_worktree.BASE_BRANCHES: reasons.append( f"control checkout branch '{branch}' is not an accepted base branch " f"({', '.join(sorted(author_mutation_worktree.BASE_BRANCHES))})" ) if dirty: reasons.append( f"control checkout has tracked local edits: {', '.join(dirty[:5])}" ) if remote_master_sha_error: reasons.append( f"could not verify live master tip: {remote_master_sha_error}" ) elif remote_master_sha and head_sha: h = head_sha.strip().lower() rm = remote_master_sha.strip().lower() if h != rm: reasons.append( f"control checkout HEAD ({h[:12]}) != live master tip ({rm[:12]})" ) if reasons: return { "not_applicable": False, "allowed": False, "block": True, "proven": False, "reasons": reasons, } return { "not_applicable": False, "allowed": True, "block": False, "proven": True, "bootstrap_path": "clean_canonical_control_checkout", "reasons": [ "control checkout is clean on accepted base branch matching live master" ], } def bootstrap_author_issue_worktree( *, issue_number: int, canonical_repo_root: str, assignment_id: str | None = None, lease_id: str | None = None, expected_base_sha: str | None = None, branch_name: str | None = None, worktree_path: str | None = None, idempotency_key: str | None = None, remote: str = "prgs", host: str | None = None, org: str | None = "Scaled-Tech-Consulting", repo: str | None = "Gitea-Tools", active_identity: str | None = "jcwalker3", active_profile: str | None = "prgs-author", owner_session: str | None = None, lock_dir: str | None = None, dry_run: bool = False, ) -> dict[str, Any]: """Execute the sanctioned author issue worktree bootstrap transition.""" root = os.path.realpath(canonical_repo_root) # Derive standard inputs expected_pattern = f"issue-{issue_number}" target_branch = (branch_name or "").strip() if not target_branch: target_branch = f"fix/issue-{issue_number}-native-mcp-bootstrap" elif expected_pattern not in target_branch: return { "success": False, "reason_code": "invalid_branch_name", "message": ( f"Branch name '{target_branch}' must contain issue pattern '{expected_pattern}'" ), "exact_next_action": ( f"Supply a branch_name containing '{expected_pattern}', e.g., 'fix/issue-{issue_number}-...'" ), } worktree_name = target_branch.replace("/", "-") target_worktree = (worktree_path or "").strip() if not target_worktree: target_worktree = os.path.join(root, "branches", worktree_name) target_worktree = os.path.realpath(os.path.abspath(target_worktree)) key = (idempotency_key or "").strip() if not key: key = derive_default_idempotency_key( remote=remote, org=org, repo=repo, issue_number=issue_number, assignment_id=assignment_id, lease_id=lease_id, ) # Idempotency check existing = load_phase_journal(key) if existing and existing.get("completed"): if ( existing.get("issue_number") == issue_number and existing.get("branch_name") == target_branch and os.path.realpath(existing.get("worktree_path", "")) == target_worktree ): return { "success": True, "replayed": True, "message": ( f"Idempotent replay: worktree for issue #{issue_number} already bootstrapped at {target_worktree}" ), "issue_number": issue_number, "branch_name": target_branch, "worktree_path": target_worktree, "base_sha": existing.get("resolved_base_sha"), "lease_id": existing.get("lease_id"), "assignment_id": existing.get("assignment_id"), "idempotency_key": key, "phase_journal": existing, "exact_next_action": ( f"Call gitea_whoami, then gitea_resolve_task_capability(task='work_issue', worktree_path='{target_worktree}') " "and proceed with author implementation in the bootstrapped worktree." ), } else: return { "success": False, "reason_code": "incompatible_idempotency_replay", "message": ( f"Idempotency key '{key}' already exists with incompatible parameters " f"(stored: {existing.get('branch_name')}, {existing.get('worktree_path')}; " f"requested: {target_branch}, {target_worktree})" ), "exact_next_action": ( "Supply a unique idempotency_key or pass compatible parameters." ), } # Initialize Phase Journal journal = existing or { "idempotency_key": key, "issue_number": issue_number, "assignment_id": assignment_id, "lease_id": lease_id, "expected_base_sha": expected_base_sha, "resolved_base_sha": None, "branch_name": target_branch, "worktree_path": target_worktree, "active_identity": active_identity, "active_profile": active_profile, "owner_session": owner_session, "remote": remote, "org": org, "repo": repo, "phases": {}, "artifacts_created": { "branch_created": False, "worktree_dir_created": False, "worktree_registered": False, "lock_created": False, }, "current_phase": PHASE_1_REQUEST_ACCEPTED, "completed": False, } # Fetch current live master SHA try: rev_res = subprocess.run( ["git", "-C", root, "rev-parse", "HEAD"], capture_output=True, text=True, check=True, ) live_master_sha = rev_res.stdout.strip() except Exception as exc: return { "success": False, "reason_code": "git_rev_parse_failed", "message": f"Could not determine repository HEAD: {exc}", "exact_next_action": "Verify repository git state and retry.", } # Phase 1: REQUEST_ACCEPTED & Concurrency Pin Check if expected_base_sha: exp_norm = expected_base_sha.strip().lower() live_norm = live_master_sha.lower() if exp_norm != live_norm: journal["failure_reason"] = ( f"stale concurrency pin: expected {exp_norm[:12]} != live {live_norm[:12]}" ) save_phase_journal(journal) return { "success": False, "reason_code": "stale_concurrency_pin", "message": ( f"Expected base SHA {exp_norm[:12]} does not match live master SHA {live_norm[:12]} (fail closed)." ), "expected_base_sha": expected_base_sha, "live_master_sha": live_master_sha, "exact_next_action": ( "Re-evaluate assignment against current live master SHA and retry with updated expected_base_sha." ), } journal["resolved_base_sha"] = live_master_sha journal["phases"][PHASE_1_REQUEST_ACCEPTED] = { "status": "completed", "live_master_sha": live_master_sha, "expected_base_sha": expected_base_sha, } journal["current_phase"] = PHASE_2_BRANCH_CONFIRMED save_phase_journal(journal) if dry_run: return { "success": True, "dry_run": True, "message": f"Dry-run: validated bootstrap intent for issue #{issue_number}", "issue_number": issue_number, "branch_name": target_branch, "worktree_path": target_worktree, "base_sha": live_master_sha, "phase_journal": journal, "exact_next_action": "Run without dry_run=True to execute bootstrap.", } # Phase 2: BRANCH_CONFIRMED branch_check = subprocess.run( ["git", "-C", root, "rev-parse", "--verify", target_branch], capture_output=True, text=True, check=False, ) if branch_check.returncode == 0: branch_head = branch_check.stdout.strip() # Verify branch head descends from base anc_check = subprocess.run( [ "git", "-C", root, "merge-base", "--is-ancestor", live_master_sha, branch_head, ], capture_output=True, text=True, check=False, ) if anc_check.returncode != 0 and branch_head.lower() != live_master_sha.lower(): journal["failure_reason"] = ( f"existing branch '{target_branch}' HEAD ({branch_head[:12]}) does not descend from base ({live_master_sha[:12]})" ) save_phase_journal(journal) 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 remove the incompatible branch before bootstrapping." ), } journal["artifacts_created"]["branch_created"] = False else: # Create branch create_res = subprocess.run( ["git", "-C", root, "branch", target_branch, live_master_sha], capture_output=True, text=True, check=False, ) if create_res.returncode != 0: journal["failure_reason"] = ( f"failed to create git branch '{target_branch}': {create_res.stderr.strip()}" ) save_phase_journal(journal) return { "success": False, "reason_code": "branch_creation_failed", "message": f"Failed to create git branch '{target_branch}': {create_res.stderr.strip()}", "exact_next_action": "Verify branch availability and retry.", } journal["artifacts_created"]["branch_created"] = True journal["phases"][PHASE_2_BRANCH_CONFIRMED] = { "status": "completed", "branch_name": target_branch, "created": journal["artifacts_created"]["branch_created"], } journal["current_phase"] = PHASE_3_PATH_RESERVED save_phase_journal(journal) # Phase 3: PATH_RESERVED if not author_mutation_worktree.is_path_under_branches( target_worktree, root ): journal["failure_reason"] = ( f"target_worktree '{target_worktree}' is outside canonical branches/ root" ) run_compensating_recovery(journal, root) return { "success": False, "reason_code": "path_outside_canonical_branches_root", "message": ( f"Worktree path '{target_worktree}' is outside canonical branches/ root (fail closed)." ), "exact_next_action": ( "Provide a worktree_path inside canonical branches/ root, e.g., 'branches/issue-...'" ), } dir_exists = os.path.exists(target_worktree) if dir_exists: # Check porcelain directly porc_res = subprocess.run( ["git", "-C", target_worktree, "status", "--porcelain"], capture_output=True, text=True, check=False, ) dirty = ( parse_dirty_tracked_files(porc_res.stdout) if porc_res.returncode == 0 else [] ) if dirty or (porc_res.returncode == 0 and porc_res.stdout.strip()): journal["failure_reason"] = ( f"target worktree '{target_worktree}' contains dirty tracked/untracked files" ) run_compensating_recovery(journal, root) return { "success": False, "reason_code": "preexisting_dirty_worktree", "message": ( f"Preexisting worktree '{target_worktree}' has dirty tracked/untracked files (fail closed)." ), "exact_next_action": ( "Clean or stash the pre-existing worktree files before bootstrapping." ), } # Check registered branch wt_state = issue_lock_worktree.read_worktree_git_state(target_worktree) wt_branch = (wt_state.get("current_branch") or "").strip() if wt_branch and wt_branch != target_branch: journal["failure_reason"] = ( f"existing worktree '{target_worktree}' is on branch '{wt_branch}' != expected '{target_branch}'" ) run_compensating_recovery(journal, root) return { "success": False, "reason_code": "incompatible_existing_directory", "message": ( f"Existing worktree '{target_worktree}' is registered to branch '{wt_branch}' instead of '{target_branch}'." ), "exact_next_action": ( "Inspect or remove the pre-existing worktree folder before bootstrapping." ), } journal["artifacts_created"]["worktree_dir_created"] = False else: journal["artifacts_created"]["worktree_dir_created"] = True journal["phases"][PHASE_3_PATH_RESERVED] = { "status": "completed", "worktree_path": target_worktree, "preexisting_dir": dir_exists, } journal["current_phase"] = PHASE_4_WORKTREE_CONFIRMED save_phase_journal(journal) # Phase 4: WORKTREE_CONFIRMED & Phase 5: REGISTRATION_VERIFIED if journal["artifacts_created"]["worktree_dir_created"]: wt_add_res = subprocess.run( [ "git", "-C", root, "worktree", "add", target_worktree, target_branch, ], capture_output=True, text=True, check=False, ) if wt_add_res.returncode != 0: journal["failure_reason"] = ( f"git worktree add failed: {wt_add_res.stderr.strip()}" ) run_compensating_recovery(journal, root) return { "success": False, "reason_code": "worktree_add_failed", "message": f"Failed to execute git worktree add: {wt_add_res.stderr.strip()}", "exact_next_action": "Verify git worktree capabilities and retry.", } journal["artifacts_created"]["worktree_registered"] = True # Verify registration in git worktree list wt_list_res = subprocess.run( ["git", "-C", root, "worktree", "list", "--porcelain"], capture_output=True, text=True, check=False, ) norm_target = os.path.realpath(target_worktree) found_registration = False if wt_list_res.returncode == 0: for block in wt_list_res.stdout.split("\n\n"): lines = block.strip().splitlines() worktree_line = next( (l[9:].strip() for l in lines if l.startswith("worktree ")), None, ) if worktree_line and os.path.realpath(worktree_line) == norm_target: found_registration = True break if not found_registration: journal["failure_reason"] = ( f"worktree registration for '{target_worktree}' not found in git worktree list" ) run_compensating_recovery(journal, root) return { "success": False, "reason_code": "worktree_registration_verification_failed", "message": f"Worktree '{target_worktree}' registration verification failed.", "exact_next_action": "Check git worktree list integrity and retry.", } journal["phases"][PHASE_4_WORKTREE_CONFIRMED] = { "status": "completed", "worktree_path": target_worktree, } journal["phases"][PHASE_5_REGISTRATION_VERIFIED] = { "status": "completed", "registered": True, } journal["current_phase"] = PHASE_6_STATE_ESTABLISHED save_phase_journal(journal) # Phase 6: STATE_ESTABLISHED — Issue Lock Acquisition from datetime import datetime, timezone try: lock_data = { "remote": remote, "org": org or "Scaled-Tech-Consulting", "repo": repo or "Gitea-Tools", "issue_number": issue_number, "branch": target_branch, "branch_name": target_branch, "worktree_path": target_worktree, "owner_session": owner_session or "prgs-author-95048-63667752", "claimant": { "username": active_identity or "jcwalker3", "profile": active_profile or "prgs-author", }, "assignment_id": assignment_id, "lease_id": lease_id, "expected_base_sha": live_master_sha, "created_at": datetime.now(timezone.utc).isoformat(), } lock_res = issue_lock_store.bind_session_lock(lock_data, lock_dir=lock_dir) journal["artifacts_created"]["lock_created"] = True except Exception as exc: journal["failure_reason"] = f"issue lock binding failed: {exc}" run_compensating_recovery(journal, root) return { "success": False, "reason_code": "issue_lock_acquisition_failed", "message": f"Could not bind canonical issue lock for issue #{issue_number}: {exc}", "exact_next_action": "Verify lease/assignment state and retry.", } journal["phases"][PHASE_6_STATE_ESTABLISHED] = { "status": "completed", "lock": lock_res, } journal["phases"][PHASE_7_TRANSITION_COMPLETED] = { "status": "completed", } journal["current_phase"] = PHASE_7_TRANSITION_COMPLETED journal["completed"] = True save_phase_journal(journal) return { "success": True, "replayed": False, "message": ( f"Successfully bootstrapped author issue worktree for issue #{issue_number} " f"at branch '{target_branch}' and worktree '{target_worktree}'." ), "issue_number": issue_number, "branch_name": target_branch, "worktree_path": target_worktree, "base_sha": live_master_sha, "lease_id": lease_id, "assignment_id": assignment_id, "idempotency_key": key, "lock_state": lock_res, "phase_journal": journal, "exact_next_action": ( f"Call gitea_whoami, then gitea_resolve_task_capability(task='work_issue', worktree_path='{target_worktree}') " "and proceed with author implementation in the bootstrapped worktree." ), }