fix(engine): verify resumed worktrees aren't bootstrap-misbound

The resume path in acquireTaskWorktree returned a reused worktree
without checking whether its branch contained foreign commits. If a
sibling task's tip had been baked into the branch at creation time,
the executor preflight would later fail contamination checks forever
(observed in the FN-5475 cascade).

The resume path now computes a fresh merge-base and runs
classifyBootstrapMisbinding. For the foreign-only / zero-own-commits
shape it re-anchors inline and emits a branch:reanchor audit event.
Mixed contamination continues to flow through the executor's
primary recovery path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-23 02:32:01 -07:00
parent 9ce26eef26
commit e7088704e6
3 changed files with 179 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ import { promisify } from "node:util";
import { acquireTaskWorktree } from "../worktree-acquisition.js";
import { classifyTaskWorktree, PoolDoubleLeaseError } from "../worktree-pool.js";
import * as desktopArtifacts from "../worktree-desktop-artifacts.js";
import * as branchConflicts from "../branch-conflicts.js";
vi.mock("../worktree-pool.js", async () => {
const actual = await vi.importActual<any>("../worktree-pool.js");
@@ -13,6 +14,20 @@ vi.mock("../worktree-pool.js", async () => {
};
});
vi.mock("../branch-conflicts.js", async () => {
const actual = await vi.importActual<any>("../branch-conflicts.js");
return {
...actual,
classifyBootstrapMisbinding: vi.fn().mockResolvedValue({
isBootstrapMisbinding: false,
ownCommitCount: 0,
foreignCommitCount: 0,
nonAttributedCount: 0,
}),
reanchorBranchToBase: vi.fn().mockResolvedValue({ previousTipSha: "abc", newTipSha: "def" }),
};
});
vi.mock("../worktree-db-hydrate.js", () => ({
hydrateWorktreeDb: vi.fn().mockResolvedValue({ degraded: false, tasksCopied: 1, documentsCopied: 1 }),
}));
@@ -52,6 +67,48 @@ describe("acquireTaskWorktree", () => {
expect(result.worktreePath).toBe(process.cwd());
});
// Regression: FN-5475 — when a resumed worktree's branch was created from
// a poisoned local-main tip carrying a sibling task's commits and has zero
// commits of its own, acquireTaskWorktree must re-anchor inline so the
// executor preflight doesn't pause on contamination forever.
it("re-anchors a resumed branch when classified as bootstrap-misbinding", async () => {
const audit = { git: vi.fn().mockResolvedValue(undefined), filesystem: vi.fn() } as any;
vi.mocked(branchConflicts.classifyBootstrapMisbinding).mockResolvedValueOnce({
isBootstrapMisbinding: true,
ownCommitCount: 0,
foreignCommitCount: 2,
nonAttributedCount: 0,
});
const result = await acquireTaskWorktree({
task: { ...task, worktree: process.cwd(), branch: "fusion/fn-1" },
rootDir: process.cwd(),
store,
settings: {},
audit,
createWorktree: vi.fn(),
});
expect(result.source).toBe("existing");
expect(vi.mocked(branchConflicts.reanchorBranchToBase)).toHaveBeenCalledTimes(1);
expect(audit.git).toHaveBeenCalledWith(expect.objectContaining({
type: "branch:reanchor",
metadata: expect.objectContaining({ trigger: "resume-misbinding" }),
}));
});
it("does not re-anchor a resumed branch when not misbound", async () => {
const result = await acquireTaskWorktree({
task: { ...task, worktree: process.cwd(), branch: "fusion/fn-1" },
rootDir: process.cwd(),
store,
settings: {},
createWorktree: vi.fn(),
});
expect(result.source).toBe("existing");
expect(vi.mocked(branchConflicts.reanchorBranchToBase)).not.toHaveBeenCalled();
});
it("acquires from pool when enabled", async () => {
const prepareForTask = vi.fn().mockResolvedValue({ branch: "fusion/fn-1", worktreePath: "/tmp/pooled", reclaimed: false });
const release = vi.fn();