feat(FN-4192): add task ID integrity detection and dashboard alerts

Implements task ID integrity detection across the stack (FN-4192): a new `task-id-integrity` module in core provides detector logic wired into the store, integrity health is surfaced via the dashboard API and legacy endpoint, and a banner component surfaces alerts in the UI — backed by comprehensive

Fusion-Task-Id: FN-4192
This commit is contained in:
Fusion
2026-05-12 19:06:19 -07:00
committed by gsxdsm
parent d0b1fd01ae
commit a3478718d7
17 changed files with 1217 additions and 7 deletions

View File

@@ -0,0 +1,111 @@
import { mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { Database } from "../db.js";
import { TaskStore } from "../store.js";
import { makeTmpDir } from "./store-test-helpers.js";
async function seedIntegrityPrecondition(rootDir: string): Promise<void> {
const fusionDir = join(rootDir, ".fusion");
await mkdir(fusionDir, { recursive: true });
const db = new Database(fusionDir);
db.init();
const now = new Date().toISOString();
db.prepare(
"INSERT INTO tasks (id, description, \"column\", createdAt, updatedAt) VALUES (?, '', 'todo', ?, ?)",
).run("FN-100", now, now);
db.prepare(
"INSERT INTO distributed_task_id_state (prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt) VALUES (?, ?, ?, ?, ?)",
).run("FN", 100, 0, null, now);
db.close();
}
describe("TaskStore task ID integrity wiring", () => {
let rootDir = "";
let globalDir = "";
beforeEach(() => {
rootDir = makeTmpDir();
globalDir = makeTmpDir();
});
afterEach(async () => {
vi.restoreAllMocks();
await rm(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
await rm(globalDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
});
it("constructs cleanly and exposes an ok integrity report by default", async () => {
const store = new TaskStore(rootDir, globalDir);
await store.init();
const report = store.getTaskIdIntegrityReport();
expect(report.status).toBe("ok");
expect(report.anomalies).toEqual([]);
expect(report.checkedAt).toEqual(expect.any(String));
store.close();
});
it("logs a structured core error and exposes anomaly status when startup detects corruption preconditions", async () => {
await seedIntegrityPrecondition(rootDir);
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const store = new TaskStore(rootDir, globalDir);
await store.init();
const report = store.getTaskIdIntegrityReport();
expect(report.status).toBe("anomaly");
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "next_sequence_at_or_below_used",
prefix: "FN",
affectedIds: ["FN-100"],
}),
);
expect(errorSpy).toHaveBeenCalledWith(
expect.stringContaining("[core] [task-id-integrity] anomaly detected"),
expect.objectContaining({
anomalies: expect.arrayContaining([
expect.objectContaining({
kind: "next_sequence_at_or_below_used",
affectedIds: ["FN-100"],
}),
]),
}),
);
store.close();
});
it("refreshTaskIdIntegrityReport picks up newly introduced anomalies", async () => {
const store = new TaskStore(rootDir, globalDir, { inMemoryDb: true });
await store.init();
const db = store.getDatabase();
const now = new Date().toISOString();
db.prepare(
"INSERT INTO tasks (id, description, \"column\", createdAt, updatedAt) VALUES (?, '', 'todo', ?, ?)",
).run("FN-100", now, now);
db.prepare(
"INSERT OR REPLACE INTO distributed_task_id_state (prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt) VALUES (?, ?, ?, ?, ?)",
).run("FN", 100, 0, null, now);
const report = store.refreshTaskIdIntegrityReport();
expect(report.status).toBe("anomaly");
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "next_sequence_at_or_below_used",
prefix: "FN",
affectedIds: ["FN-100"],
}),
);
expect(store.getTaskIdIntegrityReport()).toEqual(report);
store.close();
});
});

View File

@@ -0,0 +1,154 @@
import { describe, expect, it } from "vitest";
import { Database } from "../db.js";
import { detectTaskIdIntegrityAnomalies } from "../task-id-integrity.js";
function createDb(): Database {
const db = new Database("/tmp/fusion-task-id-integrity-test", { inMemory: true });
db.init();
return db;
}
function insertTask(db: Database, id: string): void {
const now = new Date().toISOString();
db.prepare(
"INSERT INTO tasks (id, description, \"column\", createdAt, updatedAt) VALUES (?, '', 'todo', ?, ?)",
).run(id, now, now);
}
describe("detectTaskIdIntegrityAnomalies", () => {
it("returns ok for a clean database", () => {
const db = createDb();
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.status).toBe("ok");
expect(report.checkedAt).toEqual(expect.any(String));
expect(report.anomalies).toEqual([]);
});
it("returns ok when allocator tables are missing", () => {
const db = createDb();
db.exec("DROP TABLE distributed_task_id_reservations");
db.exec("DROP TABLE distributed_task_id_state");
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.status).toBe("ok");
expect(report.anomalies).toEqual([]);
});
it("detects duplicate active task IDs", () => {
const db = createDb();
db.exec("ALTER TABLE tasks RENAME TO tasks_original");
db.exec("CREATE TABLE tasks (id TEXT NOT NULL, description TEXT, \"column\" TEXT, createdAt TEXT, updatedAt TEXT)");
db.exec(`
INSERT INTO tasks (id, description, "column", createdAt, updatedAt) VALUES
('FN-101', '', 'todo', '2026-05-12T00:00:00.000Z', '2026-05-12T00:00:00.000Z'),
('FN-101', '', 'todo', '2026-05-12T00:00:01.000Z', '2026-05-12T00:00:01.000Z')
`);
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.status).toBe("anomaly");
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "duplicate_active_id",
prefix: "FN",
affectedIds: ["FN-101"],
}),
);
});
it("detects IDs present in both active and archived storage", () => {
const db = createDb();
insertTask(db, "FN-102");
db.prepare("INSERT INTO archivedTasks (id, data, archivedAt) VALUES (?, ?, ?)").run(
"FN-102",
JSON.stringify({ id: "FN-102" }),
new Date().toISOString(),
);
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "id_in_active_and_archived",
prefix: "FN",
affectedIds: ["FN-102"],
}),
);
});
it("detects stale nextSequence values at or below an existing used sequence", () => {
const db = createDb();
const now = new Date().toISOString();
insertTask(db, "FN-100");
db.prepare(
"INSERT INTO distributed_task_id_state (prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt) VALUES (?, ?, ?, ?, ?)",
).run("FN", 100, 0, null, now);
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "next_sequence_at_or_below_used",
prefix: "FN",
affectedIds: ["FN-100"],
}),
);
});
it("detects committed reservations that target existing task IDs", () => {
const db = createDb();
const now = new Date().toISOString();
insertTask(db, "FN-103");
db.prepare(
"INSERT INTO distributed_task_id_state (prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt) VALUES (?, ?, ?, ?, ?)",
).run("FN", 104, 1, "FN-103", now);
db.prepare(
`INSERT INTO distributed_task_id_reservations (
reservationId, prefix, nodeId, sequence, taskId, status, reason, expiresAt, committedAt, createdAt, updatedAt
) VALUES (?, ?, ?, ?, ?, 'committed', NULL, ?, ?, ?, ?)`,
).run(
"res-103",
"FN",
"node-a",
103,
"FN-103",
new Date(Date.now() + 60_000).toISOString(),
now,
now,
now,
);
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "committed_reservation_for_existing_id",
prefix: "FN",
affectedIds: ["FN-103"],
}),
);
});
it("detects active task rows whose prefix is outside distributed state", () => {
const db = createDb();
const now = new Date().toISOString();
insertTask(db, "KB-001");
db.prepare(
"INSERT INTO distributed_task_id_state (prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt) VALUES (?, ?, ?, ?, ?)",
).run("FN", 2, 0, null, now);
const report = detectTaskIdIntegrityAnomalies(db);
expect(report.anomalies).toContainEqual(
expect.objectContaining({
kind: "task_row_outside_known_prefix",
prefix: "KB",
affectedIds: ["KB-001"],
}),
);
});
});