feat(FN-3173): add SQLite WAL tuning, integrity checks, and agent log batch

This merge brings FN-3173's SQLite stability improvements: WAL tuning pragmas for better concurrency, periodic integrity checks with self-healing recovery, and batched agent log writes to reduce I/O overhead. It also includes a new cron-runner for scheduled maintenance tasks, TUI mouse wheel scrolli

Fusion-Task-Id: FN-3173
This commit is contained in:
Fusion
2026-05-01 23:26:14 -07:00
committed by gsxdsm
parent 3fdeac1fc4
commit a284138167
10 changed files with 326 additions and 77 deletions

View File

@@ -51,6 +51,29 @@ describe("Database", () => {
expect(row.foreign_keys).toBe(1);
});
it("sets WAL tuning pragmas for disk-backed databases", () => {
const synchronous = db.prepare("PRAGMA synchronous").get() as { synchronous: number };
const autoCheckpoint = db.prepare("PRAGMA wal_autocheckpoint").get() as { wal_autocheckpoint: number };
const journalSizeLimit = db.prepare("PRAGMA journal_size_limit").get() as { journal_size_limit: number };
expect(synchronous.synchronous).toBe(1); // NORMAL
expect(autoCheckpoint.wal_autocheckpoint).toBe(100);
expect(journalSizeLimit.journal_size_limit).toBe(4_194_304);
});
it("does not force WAL tuning pragmas for in-memory databases", () => {
const memDb = new Database(fusionDir, { inMemory: true });
memDb.init();
const autoCheckpoint = memDb.prepare("PRAGMA wal_autocheckpoint").get() as { wal_autocheckpoint: number };
const journalSizeLimit = memDb.prepare("PRAGMA journal_size_limit").get() as { journal_size_limit: number };
expect(autoCheckpoint.wal_autocheckpoint).toBe(1000);
expect(journalSizeLimit.journal_size_limit).toBe(-1);
memDb.close();
});
it("creates all expected tables", () => {
const tables = db.prepare(
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
@@ -409,6 +432,28 @@ describe("Database", () => {
});
});
describe("integrity check", () => {
it("returns ok for healthy databases and leaves corruption flag false", () => {
expect(db.corruptionDetected).toBe(false);
expect(db.integrityCheck()).toEqual({ ok: true });
});
it("keeps corruptionDetected false after init for healthy database", () => {
const diskDb = new Database(fusionDir);
diskDb.init();
expect(diskDb.corruptionDetected).toBe(false);
diskDb.close();
});
it("skips integrity check side effects for in-memory databases", () => {
const memDb = new Database(fusionDir, { inMemory: true });
memDb.init();
expect(memDb.integrityCheck()).toEqual({ ok: true });
expect(memDb.corruptionDetected).toBe(false);
memDb.close();
});
});
describe("foreign key cascade across reopen", () => {
it("cascade delete works after closing and reopening the database", () => {
const now = new Date().toISOString();

View File

@@ -4141,6 +4141,31 @@ Task with acceptance criteria
expect(events[0].taskId).toBe(task.id);
});
it("appendAgentLogBatch inserts all entries and emits per-entry events", async () => {
const task = await createTestTask();
const events: any[] = [];
store.on("agent:log", (entry) => events.push(entry));
await store.appendAgentLogBatch([
{ taskId: task.id, text: "batch 1", type: "text" },
{ taskId: task.id, text: "tool", type: "tool", detail: "read file", agent: "executor" },
]);
const logs = await store.getAgentLogs(task.id);
expect(logs).toHaveLength(2);
expect(logs.map((entry) => entry.text)).toEqual(["batch 1", "tool"]);
expect(events).toHaveLength(2);
expect(events[1]).toMatchObject({ text: "tool", type: "tool", detail: "read file", agent: "executor" });
});
it("appendAgentLogBatch with empty entries is a no-op", async () => {
const task = await createTestTask();
await store.appendAgentLogBatch([]);
expect(await store.getAgentLogCount(task.id)).toBe(0);
});
it("appendAgentLog writes detail when provided", async () => {
const task = await createTestTask();