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:
Fusion
2026-05-09 16:03:09 -07:00
committed by gsxdsm
parent 5ee21e9f9f
commit 71bf70f79d
3 changed files with 75 additions and 0 deletions

View File

@@ -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.

View File

@@ -1191,6 +1191,52 @@ describe("schema migrations", () => {
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", () => {
tmpDir = makeTmpDir();
const fusionDir = join(tmpDir, ".fusion");

View File

@@ -1064,6 +1064,7 @@ export class Database {
this.migrate();
// Compatibility backfills that must run even when schemaVersion is current.
this.ensureTasksSchemaCompatibility();
this.ensureRoutinesSchemaCompatibility();
this.ensureInsightRunsSchemaCompatibility();
this.ensureEvalTaskResultsSchemaCompatibility();
@@ -1086,6 +1087,29 @@ export class Database {
* Column additions use `hasColumn()` so they are idempotent — safe to
* 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.
*