fix(engine): skip promoted-foreign commits in contamination audit

assertCleanBranchAtBase now checks each foreign-attributed commit
against `git merge-base --is-ancestor <sha> main`. If the commit is
already on local main, it was promoted through integration regardless
of whose Fusion-Task-Id trailer it carries — treating it as foreign
contamination is wrong and was the root cause of the FN-5475 cascade
(downstream worktrees inherited a sibling task's tip during the brief
fast-forward window before main moved further).

Audit cost: O(N) extra git calls per audit run, where N is the number
of foreign-trailer commits in baseSha..branchName. Each call is ~5-10ms
and N is typically 1-5. Negligible relative to the surrounding I/O.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-23 02:59:23 -07:00
parent a7ad30f22a
commit 02971efcfe
3 changed files with 104 additions and 2 deletions

View File

@@ -0,0 +1,25 @@
---
"@fusion/engine": patch
---
fix(engine): treat foreign-attributed commits already on main as promoted
`assertCleanBranchAtBase` flagged any commit in `baseSha..branchName`
whose `Fusion-Task-Id` trailer pointed at a different task as
contamination. That misclassified the FN-5475 cascade: the engine
fast-forwards local `main` with single-parent task commits, and any
worktree created during the brief window where local `main` carried a
sibling task's tip inherited that commit. The audit later (correctly)
saw the commit as not-yet-on-main from its merge-base perspective and
threw `BranchCrossContaminationError`.
The audit now skips foreign-attributed commits that are reachable from
local `main` (`git merge-base --is-ancestor <sha> main`). Commits on
main were promoted through integration regardless of whose trailer
they carry, and downstream branches that inherited them via main are
not contaminated.
Resume verifier (FN-5475 fix #2) and the auto-recovery handler
fallback (FN-5475 fix #3) remain in place as defense-in-depth for
the rarer variants (local main rewound, foreign commit not yet on
main when the audit fires).

View File

@@ -466,6 +466,48 @@ describe("branch-conflicts", () => {
await expect(assertion).resolves.toBeUndefined();
});
// FN-5475 / option-2 promotion check: a commit attributed to another
// task that's already reachable from local `main` was integrated via
// fast-forward and shouldn't be treated as contamination on a
// downstream branch that briefly inherited it.
it("assertCleanBranchAtBase treats foreign-attributed commits that are ancestors of main as promoted", async () => {
mockedExecSync.mockImplementation((cmd: string | string[]) => {
const command = typeof cmd === "string" ? cmd : cmd[0];
if (command.includes("git log --format=%H%x1f%s%x1f%b 'main..fusion/fn-4068'")) {
return Buffer.from("bbb222feat(FN-4386): foreignFusion-Task-Id: FN-4386\n");
}
if (command.includes("git merge-base --is-ancestor 'bbb222' 'main'")) {
// Simulate the FN-5475 case: foreign commit is already on main.
return Buffer.from("");
}
throw new Error(`Unexpected command: ${command}`);
});
await expect(
assertCleanBranchAtBase("/tmp/repo", "fusion/fn-4068", "main", "FN-4068"),
).resolves.toBeUndefined();
});
it("assertCleanBranchAtBase still throws when foreign-attributed commits are NOT on main", async () => {
mockedExecSync.mockImplementation((cmd: string | string[]) => {
const command = typeof cmd === "string" ? cmd : cmd[0];
if (command.includes("git log --format=%H%x1f%s%x1f%b 'main..fusion/fn-4068'")) {
return Buffer.from("bbb222feat(FN-4386): foreignFusion-Task-Id: FN-4386\n");
}
if (command.includes("git merge-base --is-ancestor")) {
// Not on main — exits non-zero.
const err = new Error("not an ancestor") as Error & { stderr?: string };
err.stderr = "";
throw err;
}
throw new Error(`Unexpected command: ${command}`);
});
await expect(
assertCleanBranchAtBase("/tmp/repo", "fusion/fn-4068", "main", "FN-4068"),
).rejects.toBeInstanceOf(BranchCrossContaminationError);
});
describe("reportBranchAttribution", () => {
const RS = "\x1e";
const FS = "\x1f";

View File

@@ -400,6 +400,28 @@ export async function isBranchAuthoritativeForTask(
return { ok: true };
}
/**
* Cheap ancestry check: is `commitSha` reachable from `ref`?
*
* Used to recognize "promoted" commits during contamination audits: when
* the engine fast-forwards local `main` with a sibling task's commit, that
* commit's `Fusion-Task-Id` trailer still points at the sibling, but the
* commit itself is now integrated. Treating it as foreign contamination
* for downstream tasks branched from the same main tip is incorrect — the
* commit is, by definition, ancestral on the integration target.
*
* Returns `false` on any git error (missing ref, repo unreadable, etc.)
* so the caller falls back to the conservative trailer-only judgement.
*/
async function isAncestorOf(repoDir: string, commitSha: string, ref: string): Promise<boolean> {
try {
await runGit(repoDir, `git merge-base --is-ancestor ${quoteShellArg(commitSha)} ${quoteShellArg(ref)}`);
return true;
} catch {
return false;
}
}
export async function assertCleanBranchAtBase(
repoDir: string,
branchName: string,
@@ -412,17 +434,30 @@ export async function assertCleanBranchAtBase(
const subjectPattern = /^(feat|fix|test|chore|docs|refactor|perf|build)\((FN-\d+)\):/i;
const trailerPattern = /(?:^|\n)Fusion-Task-Id:\s*(FN-\d+)\s*(?:\n|$)/i;
const foreignCommits: BranchCrossContaminationCommit[] = [];
const candidateForeign: BranchCrossContaminationCommit[] = [];
for (const line of output.split("\n").map((entry) => entry.trim()).filter(Boolean)) {
const [sha, subject, body] = line.split("\u001f");
const subjectMatch = (subject ?? "").match(subjectPattern);
const trailerMatch = (body ?? "").match(trailerPattern);
const attributedTaskId = (trailerMatch?.[1] ?? subjectMatch?.[2] ?? "").toUpperCase();
if (attributedTaskId && attributedTaskId !== taskId.toUpperCase()) {
foreignCommits.push({ sha, subject: subject ?? "", foreignTaskId: attributedTaskId });
candidateForeign.push({ sha, subject: subject ?? "", foreignTaskId: attributedTaskId });
}
}
if (candidateForeign.length === 0) return;
// FN-5475: a commit attributed to another task that's already reachable
// from local `main` was promoted through integration. Treat it as
// ancestral, not contamination. This closes the race where a sibling
// task's commit briefly sat at local-main's tip while a downstream
// worktree was created (cross-task tip absorption).
const foreignCommits: BranchCrossContaminationCommit[] = [];
for (const commit of candidateForeign) {
if (await isAncestorOf(repoDir, commit.sha, "main")) continue;
foreignCommits.push(commit);
}
if (foreignCommits.length > 0) {
throw new BranchCrossContaminationError({ branchName, baseSha, taskId, foreignCommits });
}