fix(engine): auto-reattach HEAD at handoff when branch ref is authoritative
The merge handoff refused with head-branch-mismatch whenever the reused worktree's HEAD wasn't on fusion/<id> (detached, recycled to main, or on a sibling branch), even when the branch ref itself still held a clean, task-attributed lineage. That wedged FN-5339-class tasks in review for no good reason. Add isBranchAuthoritativeForTask in branch-conflicts.ts (branch ref exists, tip carries Fusion-Task-Id trailer, base..branch is foreign- contamination-free) and use it in acquireReuseHandoff: when HEAD drifts but the branch ref is authoritative, run a plain `git checkout <branch>` inside the already-asserted-clean worktree, re-read HEAD, and emit a branch:auto-reattach-authoritative audit. Refusal still fires unchanged when the branch ref is missing, missing the trailer, or contaminated, so FN-5363 strict-lease and foreign-commit guards remain authoritative. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -275,6 +275,62 @@ async function summarizeTaskAttributedCommits(repoDir: string, range: string, ta
|
||||
return { ownCount, foreignCount };
|
||||
}
|
||||
|
||||
/**
|
||||
* True iff `branch`'s tip commit carries a `Fusion-Task-Id: <taskId>` trailer.
|
||||
* Used as the cheap "is this branch ref authoritative for this task" probe
|
||||
* at merge handoff so that HEAD drift (detached, wrong branch) can recover
|
||||
* via a safe re-attach instead of refusing the handoff outright.
|
||||
*/
|
||||
export async function branchTipCarriesTaskIdTrailer(
|
||||
repoDir: string,
|
||||
branch: string,
|
||||
taskId: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const body = await runGit(repoDir, `git log -1 --pretty=%B ${quoteShellArg(branch)}`);
|
||||
const escaped = taskId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const pattern = new RegExp(`(?:^|\\n)${FUSION_TASK_ID_TRAILER_KEY}: ${escaped}\\s*(?:\\n|$)`);
|
||||
return pattern.test(body);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whole-branch authority check: the branch ref exists, its tip carries the
|
||||
* task's Fusion-Task-Id trailer, and (when a base is supplied) the range
|
||||
* `base..branch` has no foreign FN-attributed commits.
|
||||
*
|
||||
* Returns `{ ok: true }` when safe to treat the branch ref as authoritative
|
||||
* for `taskId`. On failure, returns `{ ok: false, reason }` so callers can
|
||||
* log/audit why the gentle recovery was refused.
|
||||
*/
|
||||
export async function isBranchAuthoritativeForTask(
|
||||
repoDir: string,
|
||||
branch: string,
|
||||
taskId: string,
|
||||
baseSha?: string,
|
||||
): Promise<{ ok: true } | { ok: false; reason: string }> {
|
||||
try {
|
||||
await revParse(repoDir, `refs/heads/${branch}`);
|
||||
} catch {
|
||||
return { ok: false, reason: "branch-ref-missing" };
|
||||
}
|
||||
const tipCarriesTrailer = await branchTipCarriesTaskIdTrailer(repoDir, branch, taskId);
|
||||
if (!tipCarriesTrailer) {
|
||||
return { ok: false, reason: "tip-missing-task-trailer" };
|
||||
}
|
||||
if (baseSha) {
|
||||
try {
|
||||
await assertCleanBranchAtBase(repoDir, branch, baseSha, taskId);
|
||||
} catch (err) {
|
||||
const reason = err instanceof BranchCrossContaminationError ? "foreign-contamination" : "clean-branch-check-failed";
|
||||
return { ok: false, reason };
|
||||
}
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export async function assertCleanBranchAtBase(
|
||||
repoDir: string,
|
||||
branchName: string,
|
||||
|
||||
Reference in New Issue
Block a user