feat(engine): post-session branch attribution audit

Contamination on fusion/<id> branches (FN-5233 was the recent example:
two untrailered feat(FN-5353): commits sitting on fusion/fn-5233) used
to be invisible until merge time, days after it happened. The executor
already runs assertCleanBranchAtBase at worktree acquisition and reclaim
— the gap was the active session window itself.

Add reportBranchAttribution(repoDir, branch, baseSha, taskId) which
walks base..branch and bins every commit into ownTrailed (healthy),
ownUntrailed (subject tag but commit-msg hook didn't fire), foreign
(different FN-id), or unattributed (no subject pattern, no trailer —
typically a hand-merge or plumbing commit). Wire it into the executor
right after captureModifiedFiles in the post-session path: when any
anomaly bucket is non-empty, emit a structured branch:attribution-
anomaly audit event and a task log entry. The audit itself is wrapped
in a try/catch so a probe failure never destabilizes a completing
session. New branch:attribution-anomaly and branch:auto-reattach-
authoritative GitMutationType variants accept the structured metadata
(the latter for the handoff re-attach added earlier this session).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-21 19:20:33 -07:00
parent a8715ed963
commit 5848606a9a
5 changed files with 177 additions and 0 deletions

View File

@@ -48,6 +48,7 @@ import {
assertCleanBranchAtBase,
inspectBranchConflict,
listUniqueBranchCommits,
reportBranchAttribution,
} from "../branch-conflicts.js";
const mockedExecSync = vi.mocked(execSync);
@@ -465,5 +466,65 @@ describe("branch-conflicts", () => {
await expect(assertion).resolves.toBeUndefined();
});
describe("reportBranchAttribution", () => {
const RS = "\x1e";
const FS = "\x1f";
function setupLog(records: { sha: string; subject: string; body: string }[]) {
const log = records.map((r) => `${r.sha}${FS}${r.subject}${FS}${r.body}${RS}`).join("");
mockedExecSync.mockImplementation((cmd: string | string[]) => {
const command = typeof cmd === "string" ? cmd : cmd[0];
if (command.includes("git log --format=%H%x1f%s%x1f%b%x1e")) {
return Buffer.from(log);
}
throw new Error(`Unexpected command: ${command}`);
});
}
it("counts own-trailed commits as healthy", async () => {
setupLog([
{ sha: "aaa", subject: "feat(FN-1): add x", body: "Fusion-Task-Id: FN-1\n" },
{ sha: "bbb", subject: "fix(FN-1): tweak", body: "Fusion-Task-Id: FN-1\n" },
]);
const r = await reportBranchAttribution("/tmp/repo", "fusion/fn-1", "main", "FN-1");
expect(r.ownTrailed).toBe(2);
expect(r.ownUntrailed).toEqual([]);
expect(r.foreign).toEqual([]);
expect(r.unattributed).toEqual([]);
});
it("flags FN-5233-class foreign commits", async () => {
setupLog([
{ sha: "fff", subject: "feat(FN-5353): wire something", body: "" },
{ sha: "ggg", subject: "feat(FN-1): legit", body: "Fusion-Task-Id: FN-1\n" },
]);
const r = await reportBranchAttribution("/tmp/repo", "fusion/fn-1", "main", "FN-1");
expect(r.foreign).toEqual([{ sha: "fff", subject: "feat(FN-5353): wire something", foreignTaskId: "FN-5353" }]);
expect(r.ownTrailed).toBe(1);
});
it("flags own-but-untrailed commits (hook didn't fire)", async () => {
setupLog([
{ sha: "ccc", subject: "feat(FN-1): no trailer", body: "" },
]);
const r = await reportBranchAttribution("/tmp/repo", "fusion/fn-1", "main", "FN-1");
expect(r.ownUntrailed).toEqual([{ sha: "ccc", subject: "feat(FN-1): no trailer" }]);
expect(r.ownTrailed).toBe(0);
});
it("flags unattributed commits (no subject pattern, no trailer)", async () => {
setupLog([
{ sha: "ddd", subject: "hand-merge", body: "" },
]);
const r = await reportBranchAttribution("/tmp/repo", "fusion/fn-1", "main", "FN-1");
expect(r.unattributed).toEqual([{ sha: "ddd", subject: "hand-merge" }]);
});
it("returns empty report when range is empty", async () => {
setupLog([]);
const r = await reportBranchAttribution("/tmp/repo", "fusion/fn-1", "main", "FN-1");
expect(r).toEqual({ ownTrailed: 0, ownUntrailed: [], foreign: [], unattributed: [] });
});
});
});