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) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-16 16:38:25 -07:00
parent c6be0b158b
commit ee28f5e45e

View File

@@ -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",