feat(FN-5233): add tombstone recreate guard and allow-resurrection delete f

Implements the FN-5233 tombstone system for soft-delete resurrection: a configurable `tombstoneWindowSeconds` deduplicates recreation of recently deleted tasks, with an `allowResurrection` flag that permits explicit resurrect-on-recreate, tombstone recreate guards in the store layer, and cleanup of

Fusion-Task-Id: FN-5233
This commit is contained in:
Fusion (runfusion.ai)
2026-05-21 21:35:11 -07:00
committed by gsxdsm
parent 916047c2ae
commit 2d2e5b809f
18 changed files with 603 additions and 29 deletions

View File

@@ -0,0 +1,71 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { mkdtemp, mkdir, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { TaskStore } from "@fusion/core";
import kbExtension from "../extension.js";
type RegisteredTool = {
name: string;
execute: (toolCallId: string, params: any, signal: AbortSignal | undefined, onUpdate: any, ctx: { cwd: string }) => Promise<any>;
};
function createMockAPI() {
const tools = new Map<string, RegisteredTool>();
return {
tools,
registerTool(tool: RegisteredTool) {
tools.set(tool.name, tool);
},
registerCommand() {
// no-op for tests
},
on() {
// no-op for tests
},
} as any;
}
describe("task delete allowResurrection plumbing", () => {
let rootDir: string;
beforeEach(async () => {
rootDir = await mkdtemp(join(tmpdir(), "fn-task-delete-allow-"));
await mkdir(join(rootDir, ".fusion"), { recursive: true });
});
afterEach(async () => {
await rm(rootDir, { recursive: true, force: true });
});
it("fn_task_delete forwards allowResurrection=true", async () => {
const store = new TaskStore(rootDir);
await store.init();
const task = await store.createTask({ title: "x", description: "y", column: "todo" });
const api = createMockAPI();
kbExtension(api);
const tool = api.tools.get("fn_task_delete") as RegisteredTool;
await tool.execute("call-1", { id: task.id, allowResurrection: true }, undefined, undefined, { cwd: rootDir });
const deleted = (store as any).readTaskFromDb(task.id, { includeDeleted: true }) as { allowResurrection?: boolean; deletedAt?: string };
expect(deleted.deletedAt).toBeTruthy();
expect(deleted.allowResurrection).toBe(true);
});
it("fn_task_delete defaults allowResurrection=false", async () => {
const store = new TaskStore(rootDir);
await store.init();
const task = await store.createTask({ title: "x", description: "y", column: "todo" });
const api = createMockAPI();
kbExtension(api);
const tool = api.tools.get("fn_task_delete") as RegisteredTool;
await tool.execute("call-2", { id: task.id }, undefined, undefined, { cwd: rootDir });
const deleted = (store as any).readTaskFromDb(task.id, { includeDeleted: true }) as { allowResurrection?: boolean; deletedAt?: string };
expect(deleted.deletedAt).toBeTruthy();
expect(deleted.allowResurrection).toBeUndefined();
});
});