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

@@ -1158,6 +1158,7 @@ describe("DELETE /tasks/:id", () => {
expect(res.status).toBe(200);
expect(res.body.id).toBe("KB-001");
expect(store.deleteTask).toHaveBeenCalledWith("KB-001", expect.objectContaining({
allowResurrection: false,
removeDependencyReferences: false,
removeLineageReferences: false,
githubIssueAction: undefined,
@@ -1223,6 +1224,20 @@ describe("DELETE /tasks/:id", () => {
}));
});
it("passes allowResurrection when explicitly requested", async () => {
const deletedTask = { ...FAKE_TASK_DETAIL, id: "KB-001" };
(store.deleteTask as ReturnType<typeof vi.fn>).mockResolvedValue(deletedTask);
const res = await REQUEST(buildApp(), "DELETE", "/api/tasks/KB-001?allowResurrection=true");
expect(res.status).toBe(200);
expect(store.deleteTask).toHaveBeenCalledWith("KB-001", expect.objectContaining({
allowResurrection: true,
removeDependencyReferences: false,
removeLineageReferences: false,
}));
});
it.each(["close", "delete", "leave", "auto"] as const)("forwards githubIssueAction=%s", async (githubIssueAction) => {
const deletedTask = { ...FAKE_TASK_DETAIL, id: "KB-001" };
(store.deleteTask as ReturnType<typeof vi.fn>).mockResolvedValue(deletedTask);

View File

@@ -3415,6 +3415,8 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
const removeLineageReferences = req.query.removeLineageReferences === "1"
|| req.query.removeLineageReferences === "true";
const githubIssueActionRaw = req.query.githubIssueAction;
const allowResurrection = req.query.allowResurrection === "1"
|| req.query.allowResurrection === "true";
const githubIssueActionValues: readonly GithubIssueAction[] = ["close", "delete", "leave", "auto"];
let githubIssueAction: GithubIssueAction | undefined;
if (typeof githubIssueActionRaw === "string") {
@@ -3426,6 +3428,7 @@ export function registerTaskWorkflowRoutes(ctx: ApiRoutesContext, deps: TaskWork
const task = await scopedStore.deleteTask(req.params.id, {
removeDependencyReferences,
removeLineageReferences,
allowResurrection,
githubIssueAction,
auditContext: {
agentId: "system",