Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
564 changes: 564 additions & 0 deletions docs/solutions/test-failures/main-full-suite-census-2026-09-25.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,9 @@ describeIfGit("aiMergeTask finalize no-op unproven reproduction (real git)", ()

expect(result.merged).toBe(true);
expect(result.noOp).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-NO-COMMITS-DONE", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-NO-COMMITS-DONE", "done", {
workflowMoveSource: "merger-complete-task",
});
}, 20_000);

it("FN-213: clears a removed worktree pointer while retaining an operator branch", async () => {
Expand Down Expand Up @@ -435,7 +437,9 @@ describeIfGit("aiMergeTask finalize no-op unproven reproduction (real git)", ()

expect(result.merged).toBe(true);
expect(result.noOp).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-EMPTY-DONE", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-EMPTY-DONE", "done", {
workflowMoveSource: "merger-complete-task",
});
}, 20_000);

it("blocks FN-4653 shape: foreign start-point branch with no FN-owned commits", async () => {
Expand Down
24 changes: 21 additions & 3 deletions packages/engine/src/__tests__/merger-merge-details.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,21 @@ describe("aiMergeTask — agent log persistence", () => {

await aiMergeTask(store, "/tmp/root", "FN-050");

expect(store.appendAgentLog).toHaveBeenCalledWith("FN-050", "Bash", "tool", undefined, "merger");
/*
FNXC:MergerAgentLogProvenance 2026-09-25-09:40 (FUSI-020):
`appendAgentLog`'s 4th parameter is `summarizeToolArgs(name, args)`. This
agent emits one string `command` arg, so the summary is that command
verbatim. The old `undefined` assertion encoded the pre-provenance call
shape; assert the real value, not `expect.any(String)`, so a summarizer
change cannot silently alter what the agent log records.
*/
expect(store.appendAgentLog).toHaveBeenCalledWith(
"FN-050",
"Bash",
"tool",
"git status",
"merger",
);
});

it("still fires onAgentText callback alongside logging", async () => {
Expand Down Expand Up @@ -1129,8 +1143,12 @@ describe("aiMergeTask — merge details collection", () => {
);
expect(mergeDetailsCall).toBeUndefined();

// Task should still be moved to done
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
// Task should still be moved to done. `moveTask` now carries a third
// `{ workflowMoveSource }` provenance argument, so assert the audit marker
// rather than the pre-provenance 2-argument call shape.
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});

it("handles missing shortstat gracefully when show --shortstat fails", async () => {
Expand Down
5 changes: 4 additions & 1 deletion packages/engine/src/__tests__/merger-skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,10 @@ describe("aiMergeTask — skill selection non-fatal diagnostics (FN-1510/FN-1511
});

expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
// `moveTask` carries a third `{ workflowMoveSource }` provenance argument.
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});

it("records skill source in context result for debugging", async () => {
Expand Down
39 changes: 32 additions & 7 deletions packages/engine/src/__tests__/merger-verification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,9 @@ describe("aiMergeTask — build verification", () => {
const result = await aiMergeTask(store, "/tmp/root", "FN-050");

expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});

it("merge aborts when build fails via fn_report_build_failure tool", async () => {
Expand Down Expand Up @@ -559,7 +561,9 @@ describe("aiMergeTask — build verification", () => {
const result = await aiMergeTask(store, "/tmp/root", "FN-050");

expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});

it("merge proceeds when buildCommand is empty string (treated as undefined)", async () => {
Expand All @@ -583,7 +587,9 @@ describe("aiMergeTask — build verification", () => {
const result = await aiMergeTask(store, "/tmp/root", "FN-050");

expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});

function setupDependencySyncVerificationScenario({
Expand Down Expand Up @@ -1108,7 +1114,9 @@ describe("aiMergeTask — deterministic merge verification", () => {
const result = await aiMergeTask(store, "/tmp/root", "FN-050");

expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
expect(store.logEntry).toHaveBeenCalledWith(
"FN-050",
expect.stringMatching(/^\[timing\] \[verification\] test command succeeded \(exit 0(?:, output exceeded buffer)?\) in \d+ms$/),
Expand Down Expand Up @@ -1909,7 +1917,9 @@ describe("aiMergeTask — inferred test command execution", () => {
await aiMergeTask(store, "/tmp/root", "FN-050");

expect(verificationCalls).toContain("pnpm test");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});

it("logs that test command was inferred from project files", async () => {
Expand Down Expand Up @@ -2086,7 +2096,9 @@ describe("aiMergeTask — inferred test command execution", () => {
expect(verificationCalls).toHaveLength(0);
// Merge should still succeed
expect(result.merged).toBe(true);
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done");
expect(store.moveTask).toHaveBeenCalledWith("FN-050", "done", {
workflowMoveSource: "merger-complete-task",
});
});
});

Expand Down Expand Up @@ -2367,7 +2379,20 @@ describe("aiMergeTask — in-merge verification fix", () => {
expect(capturedFixOptions.onToolStart).toBeTypeOf("function");
expect(capturedFixOptions.onToolEnd).toBeTypeOf("function");

expect(store.appendAgentLog).toHaveBeenCalledWith("FN-050", "Bash", "tool", undefined, "merger");
/*
FNXC:MergerAgentLogProvenance 2026-09-25-09:40 (FUSI-020):
`appendAgentLog`'s 4th argument is `summarizeToolArgs(name, args)`. The fix
agent emits a single string `command` arg, so the summary is that command
verbatim — assert the real value, not `expect.any(String)`, so a future
change to the summarizer cannot silently alter what the log records.
*/
expect(store.appendAgentLog).toHaveBeenCalledWith(
"FN-050",
"Bash",
"tool",
"vitest run",
"merger",
);

const logMessages = (store.logEntry as ReturnType<typeof vi.fn>).mock.calls
.map((call: any[]) => call[1])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,33 @@ const autoHeal = (task: Task, max: number, isReviewColumn?: boolean): boolean =>
hasAutoHealableVerificationBufferFailure: (this: unknown, t: unknown, m: number, r?: boolean) => boolean;
}).hasAutoHealableVerificationBufferFailure.call({}, task, max, isReviewColumn);

const canMerge = (task: Task, max: number, isReviewColumn?: boolean): boolean =>
/*
FNXC:MergeAuthorityHarness 2026-09-25-07:55 (FUSI-020):
`canMergeTask`'s third positional parameter is the RESOLVED REVIEW-LANE SET
(`reviewColumns?: ReadonlySet<string>`), not a boolean — it forwards
`reviewColumns.has(task.column)` into `hasAutoHealableVerificationBufferFailure`.
Passing a boolean here made the call throw `TypeError: reviewColumns.has is not a
function` in CI, and the sibling sweep harness below threw
`TypeError: this.resolveMergeGateBlocker is not a function` because
`enqueueEligibleInReviewTasks` now probes the merge gate per card. Both are stale
harness shapes against a product signature that moved, NOT product bugs and NOT
flakes: they reproduce deterministically file-scoped, on two consecutive CI runs,
and the ledger/register hold no record of them.

The merge-gate probe is a COLLABORATOR of the sweep, not its subject (the subject is
per-card lane resolution plus the one-IR-read contract), so it is faked the same way
`isRetryCooldownElapsed` and `isMergePending` already are here. Faking it also keeps
the one-IR-read assertion honest: the real probe would add its own IR reads and
silently measure a different contract.
*/
const canMerge = (task: Task, max: number, reviewColumns?: ReadonlySet<string>): boolean =>
(ProjectEngine.prototype as unknown as {
canMergeTask: (this: unknown, t: unknown, m: number, r?: boolean) => boolean;
canMergeTask: (
this: unknown,
t: unknown,
m: number,
reviewColumns?: ReadonlySet<string>,
) => boolean;
}).canMergeTask.call(
{
options: {},
Expand All @@ -79,7 +103,7 @@ const canMerge = (task: Task, max: number, isReviewColumn?: boolean): boolean =>
},
task,
max,
isReviewColumn,
reviewColumns,
);

describe("auto-heal recognises the board's own review lane", () => {
Expand All @@ -106,9 +130,11 @@ describe("auto-heal recognises the board's own review lane", () => {

it("forwards the resolved answer through canMergeTask", () => {
// Retry-exhausted (5 >= 3) and cooldown not elapsed, so canMergeTask can only return true via
// the auto-heal branch — which makes this a direct probe of the forwarding.
expect(canMerge(healableTask("signoff"), 3, true)).toBe(true);
expect(canMerge(healableTask("signoff"), 3, false)).toBe(false);
// the auto-heal branch — which makes this a direct probe of the forwarding. The lane answer
// arrives as the resolved SET the product takes, so a set containing the card's own column
// is "this card is in review" and one that omits it is "this card is not".
expect(canMerge(healableTask("signoff"), 3, new Set(["signoff"]))).toBe(true);
expect(canMerge(healableTask("signoff"), 3, new Set(["building"]))).toBe(false);
});
});

Expand Down Expand Up @@ -138,6 +164,14 @@ describe("the in-review enqueue sweep resolves each card's own review lane", ()
ProjectEngine.prototype["classifyMergeSweepCandidate" as keyof ProjectEngine],
loadMergeSweepBatch: ProjectEngine.prototype["loadMergeSweepBatch" as keyof ProjectEngine],
isMergePending: async () => false,
/* FNXC:MergeAuthorityHarness 2026-09-25-07:55: the sweep now also probes the merge gate per
card, so a fake `this` without that method throws
`TypeError: this.resolveMergeGateBlocker is not a function` before it ever reaches the
lane assertions. The probe is a COLLABORATOR of the sweep, not its subject, so it is
faked like `isMergePending`/`isRetryCooldownElapsed` above; running the real one would
also add its own IR reads and silently measure a different contract than the one-IR-read
assertion this file exists to pin. */
resolveMergeGateBlocker: async () => undefined,
mergeSweepHoldReasons: new Map<string, string>(),
hasAutoHealableVerificationBufferFailure:
ProjectEngine.prototype["hasAutoHealableVerificationBufferFailure" as keyof ProjectEngine],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ describe("the other merge-lane surfaces on a renamed board", () => {
classifyMergeSweepCandidate: vi.fn(async () => ({ admit: true })),
mergeSweepHoldReasons: new Map<string, string>(),
internalEnqueueMerge: vi.fn(),
/*
FNXC:MergeAuthorityHarness 2026-09-25-08:35 (FUSI-020):
The merge-gate probe is a collaborator of the sweep, not its subject. Keep
it resolved here so the real canMergeTask prototype receives the lane and
performs the blocker call that this test is intended to pin.
*/
resolveMergeGateBlocker: vi.fn(async () => undefined),
};

const admitted = await (ProjectEngine.prototype as unknown as {
Expand Down Expand Up @@ -271,6 +278,7 @@ describe("the other merge-lane surfaces on a renamed board", () => {
hasMergeResolvers: vi.fn(() => false),
allowInReviewMergeProcessing: vi.fn(async () => true),
canMergeTask: (ProjectEngine.prototype as unknown as { canMergeTask: (...args: unknown[]) => boolean }).canMergeTask,
resolveMergeGateBlocker: vi.fn(async () => undefined),
schedulePrMergeRetry: vi.fn(),
clearActiveMergeClaim: vi.fn(),
clearMergeActive: vi.fn(),
Expand Down
Loading