From ee28f5e45ee47f706218329a00f19b0d82179dab Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 16 Jul 2026 16:38:25 -0700 Subject: [PATCH] FN-8155: test triage retry title handling Cover blank-title behavior across retryable triage failures. - Verify deterministic validation retries retain blank task titles - Verify transient failures retain blank task titles while retries remain Files changed: packages/engine/src/__tests__/triage.test.ts | 81 ++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) Fusion-Task-Id: FN-8155 Fusion-Task-Lineage: d8b4414d-b105-4251-bc42-d02a6d84bf3a Co-authored-by: Fusion (runfusion.ai) --- packages/engine/src/__tests__/triage.test.ts | 81 ++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/packages/engine/src/__tests__/triage.test.ts b/packages/engine/src/__tests__/triage.test.ts index 95880e58ea..dfc58b3270 100644 --- a/packages/engine/src/__tests__/triage.test.ts +++ b/packages/engine/src/__tests__/triage.test.ts @@ -5173,6 +5173,87 @@ describe("taskCreate tool model inheritance", () => { })); }); + /* + FNXC:TriageFallbackTitle 2026-07-16-00:00: + Still-retrying deterministic and transient branches must preserve retry scheduling and never backfill a blank title; only terminal failures backfill. + */ + it("does not backfill blank titles while deterministic validation retries remain", async () => { + const task = { + id: "FN-8155-DETERMINISTIC-RETRY", + title: "", + description: "Keep blank titles while deterministic validation retries remain", + column: "triage", + status: "planning", + recoveryRetryCount: 1, + dependencies: [], + steps: [], + currentStep: 0, + log: [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + } as unknown as Task; + const store = createMockStore({ + getTask: vi.fn().mockResolvedValue({ ...task, attachments: [] }), + }); + mockCreateFnAgent.mockResolvedValue({ + session: { + state: {}, + sessionManager: {}, + prompt: vi.fn().mockResolvedValue(undefined), + dispose: vi.fn(), + navigateTree: vi.fn(), + }, + }); + + const processor = new TriageProcessor(store, "/test/root", { pollIntervalMs: 100_000 }); + await processor.specifyTask(task); + + expect(store.updateTask).toHaveBeenCalledWith("FN-8155-DETERMINISTIC-RETRY", expect.objectContaining({ + status: null, + error: null, + recoveryRetryCount: 2, + nextRecoveryAt: expect.any(String), + })); + expect(store.updateTask).not.toHaveBeenCalledWith("FN-8155-DETERMINISTIC-RETRY", expect.objectContaining({ status: "failed" })); + expect(store.updateTask).not.toHaveBeenCalledWith("FN-8155-DETERMINISTIC-RETRY", expect.objectContaining({ + title: expect.any(String), + })); + }); + + it("does not backfill blank titles while transient retries remain", async () => { + const task = { + id: "FN-8155-TRANSIENT-RETRY", + title: "", + description: "Keep blank titles while transient connection retries remain", + column: "triage", + status: "planning", + recoveryRetryCount: 1, + dependencies: [], + steps: [], + currentStep: 0, + log: [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + } as unknown as Task; + const store = createMockStore({ + getTask: vi.fn().mockResolvedValue({ ...task, attachments: [] }), + }); + mockCreateFnAgent.mockRejectedValue(new Error("connection reset")); + + const processor = new TriageProcessor(store, "/test/root", { pollIntervalMs: 100_000 }); + await processor.specifyTask(task); + + expect(store.updateTask).toHaveBeenCalledWith("FN-8155-TRANSIENT-RETRY", expect.objectContaining({ + status: null, + recoveryRetryCount: 2, + nextRecoveryAt: expect.any(String), + })); + expect(store.updateTask).not.toHaveBeenCalledWith("FN-8155-TRANSIENT-RETRY", expect.objectContaining({ status: "failed" })); + expect(store.updateTask).not.toHaveBeenCalledWith("FN-8155-TRANSIENT-RETRY", expect.objectContaining({ + title: expect.any(String), + })); + }); + it("backfills blank titles when deterministic validation retries are exhausted", async () => { const task = { id: "FN-7961-DETERMINISTIC",