feat(FN-2895): add FTS5 corruption recovery path

- Add Database APIs to rebuild the tasks_fts index and detect FTS5 corruption signatures
- Retry task upserts once after rebuilding FTS5 when corruption errors are encountered
- Extend TaskStore health checks to include FTS5 integrity verification
- Add regression tests for rebuild/integrity behavior and upsert recovery, plus a patch changeset for @runfusion/fusion
This commit is contained in:
Fusion
2026-04-28 16:51:04 -07:00
committed by gsxdsm
parent 0e2480980b
commit cc9181db47
5 changed files with 248 additions and 4 deletions

View File

@@ -1079,6 +1079,33 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
this.db.bumpLastModified();
}
private upsertTaskWithFtsRecovery(task: Task): void {
try {
this.upsertTask(task);
return;
} catch (error) {
if (!this.db.isFts5CorruptionError(error)) {
throw error;
}
console.warn(`[fusion:store] FTS5 corruption detected during upsert for task ${task.id}; rebuilding index and retrying once`);
try {
this.db.rebuildFts5Index();
} catch (rebuildError) {
console.warn("[fusion:store] FTS5 rebuild failed; propagating original upsert error", rebuildError);
throw error;
}
try {
this.upsertTask(task);
} catch (retryError) {
console.warn("[fusion:store] Upsert retry after FTS5 rebuild failed; propagating original upsert error", retryError);
throw error;
}
}
}
/**
* Read a task from SQLite by ID.
*/
@@ -1330,7 +1357,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
* for backward compatibility and debugging.
*/
private async atomicWriteTaskJson(dir: string, task: Task): Promise<void> {
this.upsertTask(task);
this.upsertTaskWithFtsRecovery(task);
// Also write to disk for backward compatibility
await this.writeTaskJsonFile(dir, task);
}
@@ -1350,7 +1377,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
): Promise<void> {
this.db.transaction(() => {
// Upsert the task
this.upsertTask(task);
this.upsertTaskWithFtsRecovery(task);
// Optionally record the audit event in the same transaction
if (auditInput) {
@@ -5598,7 +5625,7 @@ ${stepsSection}`;
try {
// Simple query to verify database responsiveness
this.db.prepare("SELECT 1").get();
return true;
return this.db.checkFts5Integrity();
} catch {
return false;
}