feat(FN-4990): complete Step 3 — guard out-of-order done updates
Fusion-Task-Id: FN-4990 Fusion-Task-Lineage: 167a6011-d246-45bd-ad7a-1f2fe8b99323
This commit is contained in:
committed by
gsxdsm
parent
7bcffa5a58
commit
dca221829d
57
packages/core/src/__tests__/store-update-step-order.test.ts
Normal file
57
packages/core/src/__tests__/store-update-step-order.test.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { beforeAll, beforeEach, afterEach, afterAll, describe, expect, it } from "vitest";
|
||||||
|
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||||
|
|
||||||
|
describe("TaskStore.updateStep step-order guard", () => {
|
||||||
|
const harness = createSharedTaskStoreTestHarness();
|
||||||
|
|
||||||
|
beforeAll(harness.beforeAll);
|
||||||
|
beforeEach(harness.beforeEach);
|
||||||
|
afterEach(harness.afterEach);
|
||||||
|
afterAll(harness.afterAll);
|
||||||
|
|
||||||
|
it("no-ops out-of-order done updates when an earlier step is pending", async () => {
|
||||||
|
const store = harness.store();
|
||||||
|
const task = await harness.createTaskWithSteps();
|
||||||
|
|
||||||
|
await store.updateStep(task.id, 0, "done");
|
||||||
|
const updated = await store.updateStep(task.id, 2, "done");
|
||||||
|
|
||||||
|
expect(updated.steps[2].status).toBe("pending");
|
||||||
|
expect(updated.log.some((entry) => entry.action.includes("Ignored out-of-order done for step 2"))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows done when prior steps are skipped", async () => {
|
||||||
|
const store = harness.store();
|
||||||
|
const task = await harness.createTaskWithSteps();
|
||||||
|
|
||||||
|
await store.updateStep(task.id, 0, "done");
|
||||||
|
await store.updateStep(task.id, 1, "skipped");
|
||||||
|
const updated = await store.updateStep(task.id, 2, "done");
|
||||||
|
|
||||||
|
expect(updated.steps[2].status).toBe("done");
|
||||||
|
expect(updated.currentStep).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows done when prior steps are done and advances currentStep", async () => {
|
||||||
|
const store = harness.store();
|
||||||
|
const task = await harness.createTaskWithSteps();
|
||||||
|
|
||||||
|
await store.updateStep(task.id, 0, "done");
|
||||||
|
await store.updateStep(task.id, 1, "done");
|
||||||
|
const updated = await store.updateStep(task.id, 2, "done");
|
||||||
|
|
||||||
|
expect(updated.steps[2].status).toBe("done");
|
||||||
|
expect(updated.currentStep).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps done→in-progress regression guard behavior", async () => {
|
||||||
|
const store = harness.store();
|
||||||
|
const task = await harness.createTaskWithSteps();
|
||||||
|
|
||||||
|
await store.updateStep(task.id, 0, "done");
|
||||||
|
const updated = await store.updateStep(task.id, 0, "in-progress");
|
||||||
|
|
||||||
|
expect(updated.steps[0].status).toBe("done");
|
||||||
|
expect(updated.log.some((entry) => entry.action.includes("Ignored done→in-progress regression"))).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5190,14 +5190,34 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
|||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status === "done") {
|
||||||
|
for (let i = 0; i < stepIndex; i++) {
|
||||||
|
const priorStatus = task.steps[i].status;
|
||||||
|
if (priorStatus === "pending" || priorStatus === "in-progress") {
|
||||||
|
const ts = new Date().toISOString();
|
||||||
|
task.updatedAt = ts;
|
||||||
|
task.log.push({
|
||||||
|
timestamp: ts,
|
||||||
|
action:
|
||||||
|
`Ignored out-of-order ${status} for step ${stepIndex} (${task.steps[stepIndex].name}) — ` +
|
||||||
|
`earlier step ${i} (${task.steps[i].name}) is still ${priorStatus}`,
|
||||||
|
});
|
||||||
|
await this.atomicWriteTaskJson(dir, task);
|
||||||
|
if (this.isWatching) this.taskCache.set(id, { ...task });
|
||||||
|
this.emit("task:updated", task);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
task.steps[stepIndex].status = status;
|
task.steps[stepIndex].status = status;
|
||||||
task.updatedAt = new Date().toISOString();
|
task.updatedAt = new Date().toISOString();
|
||||||
|
|
||||||
// Advance currentStep to first non-done step
|
// Advance currentStep to first non-done/non-skipped step
|
||||||
if (status === "done") {
|
if (status === "done") {
|
||||||
while (
|
while (
|
||||||
task.currentStep < task.steps.length &&
|
task.currentStep < task.steps.length &&
|
||||||
task.steps[task.currentStep].status === "done"
|
(task.steps[task.currentStep].status === "done" || task.steps[task.currentStep].status === "skipped")
|
||||||
) {
|
) {
|
||||||
task.currentStep++;
|
task.currentStep++;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user