diff --git a/packages/engine/src/__tests__/reliability-interactions/executor-pending-review-skip-retry.test.ts b/packages/engine/src/__tests__/reliability-interactions/executor-pending-review-skip-retry.test.ts index e95933255a..85eef64c5a 100644 --- a/packages/engine/src/__tests__/reliability-interactions/executor-pending-review-skip-retry.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/executor-pending-review-skip-retry.test.ts @@ -144,7 +144,17 @@ describe("reliability interactions: FN-5436 executor pending-review skip", () => error: "executor-exit-while-review-pending", }); expect(store.updateTask).not.toHaveBeenCalledWith("FN-5436-RI-C", expect.objectContaining({ taskDoneRetryCount: 3 })); - expect(store.moveTask).toHaveBeenCalledWith("FN-5436-RI-C", "in-review"); + /* + FNXC:WorkflowResolvedColumns 2026-07-30-22:00: + The review handoff now carries move options, so the two-argument form no longer matches. Asserting + the PROVENANCE rather than `expect.anything()` (which the sibling assertions in this file use): + this pins that the move came from the review-pending-handoff node with progress preserved, so a + move made by some other path to the same destination cannot satisfy it. + */ + expect(store.moveTask).toHaveBeenCalledWith("FN-5436-RI-C", "in-review", expect.objectContaining({ + preserveProgress: true, + workflowMoveMetadata: expect.objectContaining({ nodeId: "review-pending-handoff" }), + })); }); it("FN-5436 composition: recoverApprovedStepsOnResume leaves pending-review skip disabled after approval resolves step", async () => { diff --git a/packages/engine/src/__tests__/reliability-interactions/explicit-duplicate-marker-sweep.test.ts b/packages/engine/src/__tests__/reliability-interactions/explicit-duplicate-marker-sweep.test.ts index 358b180c2c..bfe989bb22 100644 --- a/packages/engine/src/__tests__/reliability-interactions/explicit-duplicate-marker-sweep.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/explicit-duplicate-marker-sweep.test.ts @@ -201,15 +201,34 @@ const canRun = hasGit && hasPg; }); it("honors the disable flag", async () => { - const fx = await makeReliabilityFixture({ settings: { resolveExplicitDuplicateMarkerEnabled: false, taskPrefix: "FN" } as never }); + /* + FNXC:WorkflowResolvedColumns 2026-07-30-21:45 (this guard could not fire): + `triageDuplicateResolution: "delete"` is REQUIRED for this case to mean anything. Without it the + sweep has no resolution action to take, so the duplicate survives whether the disable flag is + honoured or ignored — the assertion held for a reason unrelated to the flag. Proven by mutation: + forcing `enabled = true` in resolveExplicitDuplicateMarkerTasks left all 16 cases green. With the + resolution mode set, that same mutation deletes the duplicate and this case fails. + */ + const fx = await makeReliabilityFixture({ settings: { resolveExplicitDuplicateMarkerEnabled: false, taskPrefix: "FN", triageDuplicateResolution: "delete" } as never }); fixtures.push(fx); const canonical = await fx.store.createTask({ title: "Canonical", description: "canonical", column: "todo" }); - const duplicate = await createPromptTask(fx, { id: "FN-5303", column: "triage", prompt: duplicateStub(canonical.id) }); + const duplicate = await createPromptTask(fx, { id: "FN-5303", column: "todo", prompt: duplicateStub(canonical.id) }); + + /* + FNXC:WorkflowResolvedColumns 2026-07-30-21:30: + Assert the card DID NOT MOVE, rather than that it sits in a named column. The invariant this + case owns is "the disable flag stops the sweep"; the column id was incidental, and pinning + `triage` broke it post-U11 (that column is no longer declared, so the seed lands in the merged + Planning column `todo`). Reading the column back BEFORE the sweep also means this cannot pass + because the seed happened to land where the assertion looked. + */ + const columnBefore = (await fx.store.getTask(duplicate.id)).column; await (fx.manager as any).resolveExplicitDuplicateMarkerTasks(); - expect((await fx.store.getTask(duplicate.id)).column).toBe("triage"); + // Survives at all (the flag blocked the delete) AND did not move. + expect((await fx.store.getTask(duplicate.id)).column).toBe(columnBefore); }); it("caps work at 50 tasks per sweep", async () => { @@ -241,8 +260,11 @@ const canRun = hasGit && hasPg; fixtures.push(fx); const canonical = await fx.store.createTask({ title: "Canonical", description: "canonical", column: "todo" }); - const first = await createPromptTask(fx, { id: "FN-5304", column: "triage", prompt: duplicateStub(canonical.id) }); - const second = await createPromptTask(fx, { id: "FN-5305", column: "triage", prompt: duplicateStub(canonical.id) }); + const first = await createPromptTask(fx, { id: "FN-5304", column: "todo", prompt: duplicateStub(canonical.id) }); + const second = await createPromptTask(fx, { id: "FN-5305", column: "todo", prompt: duplicateStub(canonical.id) }); + // Same reason as the disable-flag case: what matters is that `first` SURVIVES the thrown + // delete, not which column it sits in. + const firstColumnBefore = (await fx.store.getTask(first.id)).column; const originalDeleteTask = fx.store.deleteTask.bind(fx.store); const deleteSpy = vi.spyOn(fx.store, "deleteTask").mockImplementation(async (taskId, options) => { @@ -254,7 +276,7 @@ const canRun = hasGit && hasPg; expect(await (fx.manager as any).resolveExplicitDuplicateMarkerTasks()).toBe(1); expect(deleteSpy).toHaveBeenCalled(); - expect((await fx.store.getTask(first.id)).column).toBe("triage"); + expect((await fx.store.getTask(first.id)).column).toBe(firstColumnBefore); await expect(fx.store.getTask(second.id)).rejects.toThrow(`Task ${second.id} not found`); }); }); diff --git a/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-approval-gate.test.ts b/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-approval-gate.test.ts index bffa4c9561..d696efcc80 100644 --- a/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-approval-gate.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-approval-gate.test.ts @@ -13,7 +13,13 @@ function task(overrides: Partial & Pick): Task { title: overrides.id, description: overrides.id, priority: "normal", - column: "triage", + /* + FNXC:WorkflowResolvedColumns 2026-07-30-22:00: + The INTAKE column post-U11 is `todo` (the merged Planning column). `triage` is no longer declared + on any workflow, and the starved-refinement sweep filters by ROLE — so a card seeded in `triage` + carried no intake role, the filter found no candidates, and the sweep reported 0 escalations. + */ + column: "todo", dependencies: [], steps: [], currentStep: 0, diff --git a/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-triage-poll.test.ts b/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-triage-poll.test.ts index d3c6a784c7..c0a7752d76 100644 --- a/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-triage-poll.test.ts +++ b/packages/engine/src/__tests__/reliability-interactions/starved-refinement-x-triage-poll.test.ts @@ -13,7 +13,13 @@ function triageTask(overrides: Partial & Pick): Task { title: overrides.id, description: overrides.id, priority: "low", - column: "triage", + /* + FNXC:WorkflowResolvedColumns 2026-07-30-22:00: + The INTAKE column post-U11 is `todo` (the merged Planning column). `triage` is no longer declared + on any workflow, and the starved-refinement sweep filters by ROLE — so a card seeded in `triage` + carried no intake role, the filter found no candidates, and the sweep reported 0 escalations. + */ + column: "todo", dependencies: [], steps: [], currentStep: 0,