feat(FN-3898): add checkout/schema self-heal and import compatibility updates
- Add self-healing for legacy tasks schema/columns and checkout-lease recovery with regression coverage - Extend companies.sh import parsing and dashboard import routes for repo subpath handling - Fix verification command spawning to disable Corepack download prompts in engine tooling - Update dependency-graph plugin packaging/exports and wire CLI dashboard interop types - Add and include follow-up changesets for FN-3856, FN-3883, FN-3896, and FN-3898 Fusion-Task-Id: FN-3898
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Wire the checkout-lease column self-heal as an unconditional startup compatibility backfill (`ensureTasksSchemaCompatibility`) so legacy or mesh-synced task databases no longer fail with `no such column: checkoutNodeId` when schemaVersion is already past migration 20.
|
||||||
@@ -1191,6 +1191,52 @@ describe("schema migrations", () => {
|
|||||||
db.close();
|
db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("backfills missing checkout lease columns when schemaVersion is already current", () => {
|
||||||
|
tmpDir = makeTmpDir();
|
||||||
|
const fusionDir = join(tmpDir, ".fusion");
|
||||||
|
const legacyDb = new Database(fusionDir);
|
||||||
|
|
||||||
|
legacyDb.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS __meta (key TEXT PRIMARY KEY, value TEXT);
|
||||||
|
CREATE TABLE IF NOT EXISTS config (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
|
nextId INTEGER DEFAULT 1,
|
||||||
|
nextWorkflowStepId INTEGER DEFAULT 1,
|
||||||
|
settings TEXT DEFAULT '{}',
|
||||||
|
workflowSteps TEXT DEFAULT '[]',
|
||||||
|
updatedAt TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS tasks (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
"column" TEXT NOT NULL,
|
||||||
|
createdAt TEXT NOT NULL,
|
||||||
|
updatedAt TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
legacyDb.exec("INSERT INTO __meta (key, value) VALUES ('schemaVersion', '70')");
|
||||||
|
legacyDb.exec("INSERT INTO __meta (key, value) VALUES ('lastModified', '1000')");
|
||||||
|
legacyDb.exec(`INSERT INTO tasks (id, description, "column", createdAt, updatedAt) VALUES ('FN-lease', 'legacy', 'triage', '2026-01-01', '2026-01-01')`);
|
||||||
|
legacyDb.close();
|
||||||
|
|
||||||
|
const db = new Database(fusionDir);
|
||||||
|
db.init();
|
||||||
|
|
||||||
|
const columns = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||||
|
const columnNames = columns.map((column) => column.name);
|
||||||
|
expect(columnNames).toContain("checkedOutBy");
|
||||||
|
expect(columnNames).toContain("checkedOutAt");
|
||||||
|
expect(columnNames).toContain("checkoutNodeId");
|
||||||
|
expect(columnNames).toContain("checkoutRunId");
|
||||||
|
expect(columnNames).toContain("checkoutLeaseRenewedAt");
|
||||||
|
expect(columnNames).toContain("checkoutLeaseEpoch");
|
||||||
|
|
||||||
|
const task = db.prepare("SELECT checkoutLeaseEpoch FROM tasks WHERE id = 'FN-lease'").get() as { checkoutLeaseEpoch: number | null };
|
||||||
|
expect(task.checkoutLeaseEpoch).toBe(0);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
});
|
||||||
|
|
||||||
it("backfills legacy routines table missing agentId with safe defaults", () => {
|
it("backfills legacy routines table missing agentId with safe defaults", () => {
|
||||||
tmpDir = makeTmpDir();
|
tmpDir = makeTmpDir();
|
||||||
const fusionDir = join(tmpDir, ".fusion");
|
const fusionDir = join(tmpDir, ".fusion");
|
||||||
|
|||||||
@@ -1064,6 +1064,7 @@ export class Database {
|
|||||||
this.migrate();
|
this.migrate();
|
||||||
|
|
||||||
// Compatibility backfills that must run even when schemaVersion is current.
|
// Compatibility backfills that must run even when schemaVersion is current.
|
||||||
|
this.ensureTasksSchemaCompatibility();
|
||||||
this.ensureRoutinesSchemaCompatibility();
|
this.ensureRoutinesSchemaCompatibility();
|
||||||
this.ensureInsightRunsSchemaCompatibility();
|
this.ensureInsightRunsSchemaCompatibility();
|
||||||
this.ensureEvalTaskResultsSchemaCompatibility();
|
this.ensureEvalTaskResultsSchemaCompatibility();
|
||||||
@@ -1086,6 +1087,29 @@ export class Database {
|
|||||||
* Column additions use `hasColumn()` so they are idempotent — safe to
|
* Column additions use `hasColumn()` so they are idempotent — safe to
|
||||||
* re-run even if a previous migration partially applied.
|
* re-run even if a previous migration partially applied.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Applies idempotent compatibility fixes for legacy tasks checkout lease columns.
|
||||||
|
*
|
||||||
|
* FN-3879 documented a self-heal for missing checkout lease columns, but the
|
||||||
|
* original column adds lived only in the `version < 20` migration block. Some
|
||||||
|
* legacy/mesh-synced databases report `schemaVersion >= 20` despite never
|
||||||
|
* receiving those columns, so task listing queries can fail with `no such
|
||||||
|
* column: checkoutNodeId`. Running this unconditionally on init guarantees the
|
||||||
|
* canonical lease columns exist.
|
||||||
|
*/
|
||||||
|
private ensureTasksSchemaCompatibility(): void {
|
||||||
|
if (!this.hasTable("tasks")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addColumnIfMissing("tasks", "checkedOutBy", "TEXT");
|
||||||
|
this.addColumnIfMissing("tasks", "checkedOutAt", "TEXT");
|
||||||
|
this.addColumnIfMissing("tasks", "checkoutNodeId", "TEXT");
|
||||||
|
this.addColumnIfMissing("tasks", "checkoutRunId", "TEXT");
|
||||||
|
this.addColumnIfMissing("tasks", "checkoutLeaseRenewedAt", "TEXT");
|
||||||
|
this.addColumnIfMissing("tasks", "checkoutLeaseEpoch", "INTEGER DEFAULT 0");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies idempotent compatibility fixes for legacy routines table shapes.
|
* Applies idempotent compatibility fixes for legacy routines table shapes.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user