fix(FN-6035): address workflow projection review feedback

This commit is contained in:
gsxdsm
2026-06-09 00:01:00 -07:00
parent ad68c9d612
commit 40d05c8139
6 changed files with 113 additions and 16 deletions

View File

@@ -1198,5 +1198,43 @@ Task with acceptance criteria
});
});
describe("updateTaskAtomic", () => {
it("serializes read-merge-write patches against the freshest task snapshot", async () => {
const task = await store.createTask({ description: "atomic task projection" });
let releaseFirst: () => void = () => {};
const firstCanFinish = new Promise<void>((resolve) => {
releaseFirst = resolve;
});
let markFirstRead: () => void = () => {};
const firstRead = new Promise<void>((resolve) => {
markFirstRead = resolve;
});
const first = store.updateTaskAtomic(task.id, async (current) => {
markFirstRead();
expect(current.modifiedFiles).toBeUndefined();
await firstCanFinish;
return {
modifiedFiles: [...new Set([...(current.modifiedFiles ?? []), "src/a.ts"])].sort(),
mergeDetails: { ...(current.mergeDetails ?? {}), filesChanged: 1 },
};
});
await firstRead;
const second = store.updateTaskAtomic(task.id, (current) => ({
modifiedFiles: [...new Set([...(current.modifiedFiles ?? []), "src/b.ts"])].sort(),
mergeDetails: { ...(current.mergeDetails ?? {}), insertions: 2 },
}));
releaseFirst();
await Promise.all([first, second]);
const updated = await store.getTask(task.id);
expect(updated.modifiedFiles).toEqual(["src/a.ts", "src/b.ts"]);
expect(updated.mergeDetails).toEqual({ filesChanged: 1, insertions: 2 });
});
});
});

View File

@@ -7445,6 +7445,23 @@ ${TASK_UPSERT_SQL_ASSIGNMENTS}
return this.withTaskLock(id, () => this.updateTaskUnlocked(id, updates, runContext));
}
async updateTaskAtomic(
id: string,
updater: (
current: Task,
) => Parameters<TaskStore["updateTask"]>[1] | null | undefined | Promise<Parameters<TaskStore["updateTask"]>[1] | null | undefined>,
runContext?: RunMutationContext,
): Promise<Task> {
return this.withTaskLock(id, async () => {
const current = await this.readTaskJson(this.taskDir(id));
const updates = await updater(current);
if (!updates || Object.values(updates).every((value) => value === undefined)) {
return current;
}
return this.updateTaskUnlocked(id, updates, runContext);
});
}
/**
* Merge a validated/normalized custom-field patch into the existing values.
* `null` in the patch deletes that field's value (the delete sentinel from