Merge pull request #43 from timothyjlaurent/timothyjlaurent/broken-insights
fix(insights): add missing lifecycle column compatibility check
This commit is contained in:
@@ -963,4 +963,56 @@ describe("Migration: pre-33 DB upgrade", () => {
|
|||||||
rmSync(testDir, { recursive: true, force: true });
|
rmSync(testDir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ensureInsightRunsSchemaCompatibility adds lifecycle column to legacy table", () => {
|
||||||
|
const compatDir = mkdtempSync(join(tmpdir(), "fn-insight-compat-"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Create a fresh DB and run migrations
|
||||||
|
const db1 = createDatabase(compatDir);
|
||||||
|
db1.init();
|
||||||
|
expect(db1.getSchemaVersion()).toBe(62);
|
||||||
|
|
||||||
|
// Step 2: Strip lifecycle and cancelledAt columns by recreating the
|
||||||
|
// table without them. This simulates a DB that was created before the
|
||||||
|
// lifecycle columns were added and already past v59 when they landed.
|
||||||
|
db1.exec(`
|
||||||
|
CREATE TABLE project_insight_runs_legacy AS
|
||||||
|
SELECT id, projectId, trigger, status, summary, error,
|
||||||
|
insightsCreated, insightsUpdated, inputMetadata, outputMetadata,
|
||||||
|
createdAt, startedAt, completedAt
|
||||||
|
FROM project_insight_runs
|
||||||
|
`);
|
||||||
|
db1.exec("DROP TABLE project_insight_runs");
|
||||||
|
db1.exec("ALTER TABLE project_insight_runs_legacy RENAME TO project_insight_runs");
|
||||||
|
|
||||||
|
// Verify lifecycle column is gone
|
||||||
|
const colsBefore = db1.prepare("PRAGMA table_info(project_insight_runs)").all() as Array<{ name: string }>;
|
||||||
|
const colNamesBefore = colsBefore.map((c) => c.name);
|
||||||
|
expect(colNamesBefore).not.toContain("lifecycle");
|
||||||
|
expect(colNamesBefore).not.toContain("cancelledAt");
|
||||||
|
db1.close();
|
||||||
|
|
||||||
|
// Step 3: Re-open — ensureInsightRunsSchemaCompatibility should add the
|
||||||
|
// missing columns unconditionally.
|
||||||
|
const db2 = createDatabase(compatDir);
|
||||||
|
db2.init();
|
||||||
|
|
||||||
|
const colsAfter = db2.prepare("PRAGMA table_info(project_insight_runs)").all() as Array<{ name: string }>;
|
||||||
|
const colNamesAfter = colsAfter.map((c) => c.name);
|
||||||
|
expect(colNamesAfter).toContain("lifecycle");
|
||||||
|
expect(colNamesAfter).toContain("cancelledAt");
|
||||||
|
|
||||||
|
// Step 4: Creating a run must not throw — proves the INSERT path works
|
||||||
|
// with the restored columns.
|
||||||
|
const s = new InsightStore(db2);
|
||||||
|
const run = s.createRun("proj", { trigger: "manual" });
|
||||||
|
expect(run.id).toBeTruthy();
|
||||||
|
expect(run.lifecycle).toBeDefined();
|
||||||
|
|
||||||
|
db2.close();
|
||||||
|
} finally {
|
||||||
|
rmSync(compatDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1068,6 +1068,7 @@ export class Database {
|
|||||||
|
|
||||||
// Compatibility backfills that must run even when schemaVersion is current.
|
// Compatibility backfills that must run even when schemaVersion is current.
|
||||||
this.ensureRoutinesSchemaCompatibility();
|
this.ensureRoutinesSchemaCompatibility();
|
||||||
|
this.ensureInsightRunsSchemaCompatibility();
|
||||||
|
|
||||||
// Seed config row idempotently with default settings
|
// Seed config row idempotently with default settings
|
||||||
const configNow = new Date().toISOString();
|
const configNow = new Date().toISOString();
|
||||||
@@ -1122,6 +1123,25 @@ export class Database {
|
|||||||
this.db.exec("CREATE INDEX IF NOT EXISTS idxRoutinesScope ON routines(scope)");
|
this.db.exec("CREATE INDEX IF NOT EXISTS idxRoutinesScope ON routines(scope)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies idempotent compatibility fixes for the project_insight_runs table.
|
||||||
|
*
|
||||||
|
* The `lifecycle` and `cancelledAt` columns were added to SCHEMA_SQL and
|
||||||
|
* retroactively inserted into migration v33's CREATE TABLE, with a safety-net
|
||||||
|
* in migration v59. However, databases that were already at v59+ when the
|
||||||
|
* commit landed never re-run v59, leaving the columns missing. Running this
|
||||||
|
* unconditionally on every init guarantees the columns exist.
|
||||||
|
*/
|
||||||
|
private ensureInsightRunsSchemaCompatibility(): void {
|
||||||
|
if (!this.hasTable("project_insight_runs")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addColumnIfMissing("project_insight_runs", "lifecycle", "TEXT");
|
||||||
|
this.addColumnIfMissing("project_insight_runs", "cancelledAt", "TEXT");
|
||||||
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idxInsightRunsProjectTriggerStatus ON project_insight_runs(projectId, trigger, status)`);
|
||||||
|
}
|
||||||
|
|
||||||
private migrate(): void {
|
private migrate(): void {
|
||||||
const version = this.getSchemaVersion() || 1;
|
const version = this.getSchemaVersion() || 1;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user