fix(core): bump SCHEMA_VERSION so migrations 56-57 actually run

Migrations 56 (chat_sessions.cliSessionFile) and 57 (ai_sessions.archived)
were added without bumping SCHEMA_VERSION, so migrate() early-returned on
existing v55 DBs. The dashboard surfaced this as repeated 500s on
/api/ai-sessions: "no such column: archived". Add a guardrail test that
parses db.ts and asserts SCHEMA_VERSION matches the highest applyMigration
target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-01 11:13:05 -07:00
parent 99493481f9
commit 1d175e1ce3
2 changed files with 38 additions and 19 deletions

View File

@@ -1,9 +1,10 @@
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Database, createDatabase, toJson, toJsonNullable, fromJson, normalizeTaskComments } from "../db.js";
import { DEFAULT_PROJECT_SETTINGS } from "../types.js";
import { mkdtempSync, existsSync } from "node:fs";
import { join } from "node:path";
import { mkdtempSync, existsSync, readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";
import { rm } from "node:fs/promises";
function makeTmpDir(): string {
@@ -131,7 +132,7 @@ describe("Database", () => {
});
it("seeds schema version", () => {
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
});
it("seeds lastModified", () => {
@@ -154,7 +155,7 @@ describe("Database", () => {
it("is idempotent - calling init() twice does not fail", () => {
expect(() => db.init()).not.toThrow();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
});
it("does not overwrite existing config on re-init", () => {
@@ -761,7 +762,7 @@ describe("schema migrations", () => {
db.init();
// Verify version bumped to 29 (includes v1→v2 through v26→v29)
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
// Verify new columns exist and existing data is intact
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
@@ -786,11 +787,11 @@ describe("schema migrations", () => {
const db = new Database(fusionDir);
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
// Re-init should not fail
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
db.close();
});
@@ -825,7 +826,7 @@ describe("schema migrations", () => {
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
expect(cols.map((col) => col.name)).toContain("priority");
@@ -866,7 +867,7 @@ describe("schema migrations", () => {
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
const colNames = cols.map((col) => col.name);
@@ -935,7 +936,7 @@ describe("schema migrations", () => {
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
const colNames = cols.map((col) => col.name);
@@ -1038,7 +1039,7 @@ describe("schema migrations", () => {
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
const cols = db.prepare("PRAGMA table_info(chat_messages)").all() as Array<{ name: string }>;
expect(cols.map((col) => col.name)).toContain("attachments");
@@ -1112,7 +1113,7 @@ describe("schema migrations", () => {
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'agentRatings'").all() as Array<{ name: string }>;
expect(tables).toEqual([{ name: "agentRatings" }]);
@@ -1136,7 +1137,7 @@ describe("schema migrations", () => {
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'mission_events'").all() as Array<{ name: string }>;
expect(tables).toEqual([{ name: "mission_events" }]);
@@ -1240,7 +1241,7 @@ describe("schema migrations", () => {
db.init();
// Verify version bumped to 29
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
// Verify new columns exist and existing data is intact
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
@@ -1422,6 +1423,24 @@ describe("schema migrations", () => {
db.close();
});
it("SCHEMA_VERSION matches the highest applyMigration target", () => {
tmpDir = makeTmpDir();
const dbSourcePath = join(dirname(fileURLToPath(import.meta.url)), "..", "db.ts");
const source = readFileSync(dbSourcePath, "utf8");
const versionMatch = source.match(/^const SCHEMA_VERSION = (\d+);/m);
expect(versionMatch, "SCHEMA_VERSION constant not found in db.ts").not.toBeNull();
const declaredVersion = Number(versionMatch![1]);
const migrationTargets = Array.from(source.matchAll(/this\.applyMigration\((\d+),/g)).map(
(m) => Number(m[1]),
);
expect(migrationTargets.length).toBeGreaterThan(0);
const maxMigration = Math.max(...migrationTargets);
expect(declaredVersion).toBe(maxMigration);
});
});
describe("FTS5 full-text search", () => {
@@ -1624,12 +1643,12 @@ describe("FTS5 full-text search", () => {
it("checkFts5Integrity returns false when integrity-check command fails", () => {
const execSpy = vi.spyOn((db as any).db, "exec");
execSpy.mockImplementation((sql: string) => {
execSpy.mockImplementation(((sql: string) => {
if (sql.includes("integrity-check")) {
throw new Error("corruption found reading blob");
}
return undefined as any;
});
return undefined;
}) as never);
expect(db.checkFts5Integrity()).toBe(false);
});
@@ -1691,7 +1710,7 @@ describe("createDatabase factory", () => {
const db = createDatabase(fusionDir);
db.init();
expect(db.getSchemaVersion()).toBe(56);
expect(db.getSchemaVersion()).toBe(57);
expect(db.getLastModified()).toBeGreaterThan(0);
db.close();

View File

@@ -86,7 +86,7 @@ export function probeFts5(db: DatabaseSync): boolean {
// ── Schema Definition ────────────────────────────────────────────────
const SCHEMA_VERSION = 55;
const SCHEMA_VERSION = 57;
function normalizeTaskComments(
steeringComments: SteeringComment[] | undefined,