fix(FN-4917): harden session-start recovery assertions and cap coverage

Fusion-Task-Id: FN-4917
Fusion-Task-Lineage: 1422a545-f4b5-4e1f-a347-8be2a4202f45
This commit is contained in:
Fusion (runfusion.ai)
2026-05-17 11:29:07 -07:00
committed by gsxdsm
parent 56f8fc3cdf
commit 1dffcb492e
3 changed files with 38 additions and 10 deletions

View File

@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import "../executor-test-helpers.js";
import { TaskExecutor } from "../../executor.js";
import { createFnAgent } from "../../pi.js";
import { createMockStore, resetExecutorMocks } from "../executor-test-helpers.js";
import { createMockStore, mockedExecSync, resetExecutorMocks } from "../executor-test-helpers.js";
const mockedCreateFnAgent = vi.mocked(createFnAgent);
@@ -25,6 +25,7 @@ function makeTask(overrides: Record<string, unknown> = {}) {
describe("reliability interactions: FN-4917 worktree incomplete session-start", () => {
beforeEach(() => {
resetExecutorMocks();
mockedExecSync.mockReturnValue("");
});
it.each([
@@ -57,13 +58,15 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
expect(task.worktree).toBeNull();
expect(task.branch).toBeNull();
expect(task.sessionFile).toBeNull();
expect(events.map((e) => e.mutationType)).toEqual(expect.arrayContaining([
"worktree:incomplete-detected",
"worktree:auto-recovered",
]));
const mutationTypes = events.map((e) => e.mutationType);
const firstDetectedIndex = mutationTypes.indexOf("worktree:incomplete-detected");
const firstRecoveredIndex = mutationTypes.indexOf("worktree:auto-recovered");
expect(firstDetectedIndex).toBeGreaterThanOrEqual(0);
expect(firstRecoveredIndex).toBeGreaterThan(firstDetectedIndex);
const sessionStartEvent = events.find((e) => e.mutationType === "worktree:incomplete-detected" && e.metadata?.source === "session-start");
expect(sessionStartEvent?.metadata?.classification).toBe(classification);
expect(store.logEntry).not.toHaveBeenCalledWith("FN-4917-T", expect.stringMatching(/Refusing to start coding agent/), expect.anything(), expect.anything());
expect(events.some((e) => e.mutationType === "worktree:incomplete-detected" && e.metadata?.source === "resume")).toBe(true);
expect(store.logEntry.mock.calls.some((call: unknown[]) => String(call[1] ?? "").includes("Refusing to start coding agent"))).toBe(false);
});
it("preserves progress when steps already completed", async () => {
@@ -91,6 +94,28 @@ describe("reliability interactions: FN-4917 worktree incomplete session-start",
expect(store.moveTask).toHaveBeenCalledWith("FN-4917-T", "todo", { preserveProgress: true });
});
it("escalates when session-start auto-recovery reaches retry cap", async () => {
const store = createMockStore();
const events: any[] = [];
let task = makeTask({ worktree: "/tmp/wt", branch: "fusion/fn-4917-t", worktreeSessionRetryCount: 3 });
store.recordRunAuditEvent = vi.fn(async (event: any) => events.push(event));
store.getTask.mockImplementation(async () => task);
store.updateTask.mockImplementation(async (_id: string, updates: any) => {
task = { ...task, ...updates };
return task;
});
mockedCreateFnAgent.mockRejectedValueOnce(new Error("Refusing to start coding agent in incomplete worktree: /tmp/wt"));
const executor = new TaskExecutor(store, process.cwd());
await executor.execute(task);
expect(store.moveTask).not.toHaveBeenCalledWith("FN-4917-T", "todo", expect.anything());
expect(events).toEqual(expect.arrayContaining([
expect.objectContaining({ domain: "git", mutationType: "worktree:auto-recovered", metadata: expect.objectContaining({ action: "escalate-exhausted" }) }),
]));
});
it("does not intercept unrelated session-start failures", async () => {
const store = createMockStore();
let task = makeTask({ worktree: "/tmp/wt", branch: "fusion/fn-4917-t" });