test(FN-5152): cover same-millisecond near-duplicate finalize race
Fusion-Task-Id: FN-5152 Fusion-Task-Lineage: 954570d8-82e1-4519-a739-c52ee790d2db
This commit is contained in:
committed by
gsxdsm
parent
80ffed7a47
commit
4d9068d927
@@ -148,4 +148,32 @@ describe("reliability interactions: near-duplicate intake", () => {
|
||||
const updatedNewer = await fx.store.getTask(newer.id);
|
||||
expect(updatedNewer.column).toBe("todo");
|
||||
});
|
||||
|
||||
it("archives at most one sibling when both near-duplicates finalize in the same millisecond", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(new Date("2026-05-19T12:00:00.000Z"));
|
||||
|
||||
const fx = await createFixture();
|
||||
fixtures.push(fx);
|
||||
|
||||
const first = await fx.store.createTask({
|
||||
title: "Create PR routes missing handlers",
|
||||
description: "Missing /api/tasks/:id/pr/options and /api/tasks/:id/pr/preflight and /api/tasks/:id/pr/generate-metadata",
|
||||
});
|
||||
const second = await fx.store.createTask({
|
||||
title: "Missing handlers for create PR routes",
|
||||
description: "GET /api/tasks/:id/pr/options and GET /api/tasks/:id/pr/preflight and POST /api/tasks/:id/pr/generate-metadata all fail",
|
||||
});
|
||||
|
||||
const settings = await fx.store.getSettings();
|
||||
await Promise.all([
|
||||
(fx.triage as any).finalizeApprovedTask(first, basePrompt, settings, {}),
|
||||
(fx.triage as any).finalizeApprovedTask(second, basePrompt, settings, {}),
|
||||
]);
|
||||
|
||||
const refreshed = await fx.store.listTasks({ includeArchived: true });
|
||||
const archived = refreshed.filter((task) => task.id === first.id || task.id === second.id).filter((task) => task.column === "archived");
|
||||
expect(archived.length).toBeLessThanOrEqual(1);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2367,19 +2367,24 @@ export class TriageProcessor {
|
||||
|
||||
const taskCreatedAt = Date.parse(task.createdAt);
|
||||
const candidatesById = new Map(candidates.map((candidate) => [candidate.id, candidate]));
|
||||
const isStrictlyOlderOrTieCanonical = (candidate: NearDuplicateCandidate): boolean => {
|
||||
if (candidate.createdAt < taskCreatedAt) return true;
|
||||
if (candidate.createdAt > taskCreatedAt) return false;
|
||||
return candidate.id.localeCompare(task.id, undefined, { numeric: true }) < 0;
|
||||
};
|
||||
const olderMatches = matches.filter((match) => {
|
||||
const candidate = candidatesById.get(match.id);
|
||||
return candidate ? candidate.createdAt <= taskCreatedAt : false;
|
||||
return candidate ? isStrictlyOlderOrTieCanonical(candidate) : false;
|
||||
});
|
||||
const canonical = (olderMatches[0] ?? matches[0]);
|
||||
const canonical = olderMatches[0] ?? matches[0];
|
||||
const canonicalTask = candidatesById.get(canonical.id);
|
||||
if (!canonicalTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FN-5152: only archive the task being finalized when it is the newer-or-equal
|
||||
// sibling. Older tasks never lose to a newer near-duplicate candidate.
|
||||
if (taskCreatedAt >= canonicalTask.createdAt) {
|
||||
// FN-5152: only archive the task being finalized when it is the newer sibling,
|
||||
// or the tie-loser when both rows share the same millisecond timestamp.
|
||||
if (isStrictlyOlderOrTieCanonical(canonicalTask)) {
|
||||
await this.store.updateTask(task.id, {
|
||||
sourceMetadataPatch: {
|
||||
nearDuplicateOf: canonical.id,
|
||||
|
||||
Reference in New Issue
Block a user