feat(FN-5128): complete Step 2 — add soft-delete guard to hydration

Fusion-Task-Id: FN-5128
Fusion-Task-Lineage: 1e8d4dd4-9712-496f-a64b-ae85198d93e8
This commit is contained in:
Fusion (runfusion.ai)
2026-05-19 05:48:36 -07:00
committed by gsxdsm
parent 15bb84f862
commit 530a9c8fa4

View File

@@ -142,13 +142,26 @@ export async function hydrateWorktreeDb({
const taskValuePlaceholders = taskColumns.map(() => "?").join(", ");
const docValuePlaceholders = docColumns.map(() => "?").join(", ");
// FN-5105: hydrateWorktreeDb is a live-reader path, so soft-deleted tasks must be excluded.
// Only ID allocators/integrity scans are allowed to read deleted rows.
const hasDeletedAtColumn = srcTaskCols.includes("deletedAt");
const taskRows = srcDb
.prepare(`SELECT ${taskColumnList} FROM tasks WHERE id IN (${placeholders})`)
.prepare(
`SELECT ${taskColumnList} FROM tasks WHERE id IN (${placeholders})${hasDeletedAtColumn ? " AND deletedAt IS NULL" : ""}`,
)
.all(...ids) as Array<Record<string, unknown>>;
const documentRows = srcDb
.prepare(`SELECT ${docColumnList} FROM task_documents WHERE taskId IN (${placeholders})`)
.all(...ids) as Array<Record<string, unknown>>;
const hydratedTaskIds = taskRows
.map((row) => row.id)
.filter((id): id is string => typeof id === "string" && id.length > 0);
// FN-5105: documents are live-reader data too; scope to non-soft-deleted hydrated task IDs.
const documentRows =
hydratedTaskIds.length > 0
? (srcDb
.prepare(`SELECT ${docColumnList} FROM task_documents WHERE taskId IN (${hydratedTaskIds.map(() => "?").join(", ")})`)
.all(...hydratedTaskIds) as Array<Record<string, unknown>>)
: [];
const insertTask = dstDb.prepare(
`INSERT OR REPLACE INTO tasks (${taskColumnList}) VALUES (${taskValuePlaceholders})`,