Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions packages/core/src/__tests__/insight-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -963,4 +963,56 @@ describe("Migration: pre-33 DB upgrade", () => {
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 });
}
});
});
20 changes: 20 additions & 0 deletions packages/core/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,7 @@ export class Database {

// Compatibility backfills that must run even when schemaVersion is current.
this.ensureRoutinesSchemaCompatibility();
this.ensureInsightRunsSchemaCompatibility();

// Seed config row idempotently with default settings
const configNow = new Date().toISOString();
Expand Down Expand Up @@ -1122,6 +1123,25 @@ export class Database {
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)`);
}
Comment on lines +1135 to +1143
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing changeset for user-facing bug fix

AGENTS.md requires a changeset file for any change that affects @runfusion/fusion — including bug fixes. Because @fusion/core is bundled into the published CLI via noExternal, this fix to db.ts ships to end-users and needs a patch changeset. None of the three commits in this PR include one. A missing changeset means the next pnpm release will silently skip version bumping and changelog entries for this fix.

Context Used: AGENTS.md (source)


private migrate(): void {
const version = this.getSchemaVersion() || 1;

Expand Down
Loading