Przeglądaj źródła

fix(core): adopt consolidated migration aliases

The V1-to-V2 consolidation replaced a shipped migration chain with a new ID, causing upgraded databases to replay already-applied schema changes. Treat the terminal pre-consolidation migration as an alias and record the canonical migration without rerunning its SQL.
Filip 4 dni temu
rodzic
commit
4a6f5635f9

+ 9 - 0
packages/core/src/database/migration.ts

@@ -13,6 +13,7 @@ const lock = Semaphore.makeUnsafe(1)
 
 export type Migration = {
   id: string
+  aliases?: string[]
   foreignKeys?: boolean
   up: (tx: Transaction) => Effect.Effect<void, unknown, Global.Service>
 }
@@ -77,6 +78,14 @@ export function applyOnly(db: Database, input: Migration[]) {
 
     for (const migration of input) {
       if (completed.has(migration.id)) continue
+      const alias = migration.aliases?.find((id) => completed.has(id))
+      if (alias) {
+        yield* db.run(
+          sql`INSERT INTO ${sql.identifier("migration")} (id, time_completed) VALUES (${migration.id}, ${Date.now()})`,
+        )
+        yield* Effect.logInfo("database migration adopted", { migration: migration.id, alias })
+        continue
+      }
       const started = Date.now()
       yield* Effect.logInfo("database migration started", { migration: migration.id })
       const apply = db.transaction((tx) =>

+ 1 - 0
packages/core/src/database/migration/20260804233008_loose_psylocke.ts

@@ -3,6 +3,7 @@ import type { DatabaseMigration } from "../migration"
 
 const migration: DatabaseMigration.Migration = {
   id: "20260804233008_loose_psylocke",
+  aliases: ["20260730195856_optional_session_title"],
   up(tx) {
     return Effect.gen(function* () {
       yield* tx.run(`

+ 23 - 0
packages/core/test/database-migration.test.ts

@@ -12,6 +12,7 @@ import { Database } from "@opencode-ai/core/database/database"
 import { tmpdir } from "./fixture/tmpdir"
 import type { SqlClient } from "effect/unstable/sql/SqlClient"
 import legacyCredentialsMigration from "@opencode-ai/core/database/migration/20260805200742_import_legacy_credentials"
+import consolidatedV2Migration from "@opencode-ai/core/database/migration/20260804233008_loose_psylocke"
 import { Global } from "@opencode-ai/util/global"
 
 const run = <A, E>(
@@ -114,6 +115,28 @@ describe("DatabaseMigration", () => {
     )
   })
 
+  test("adopts the consolidated V2 migration after the superseded chain", async () => {
+    await run(
+      Effect.gen(function* () {
+        const db = yield* makeDb
+        yield* db.run(sql`CREATE TABLE session (id text PRIMARY KEY)`)
+        yield* db.run(sql`CREATE TABLE event (id text PRIMARY KEY, created integer DEFAULT 0 NOT NULL)`)
+        yield* db.run(sql`CREATE TABLE migration (id text PRIMARY KEY, time_completed integer NOT NULL)`)
+        yield* db.run(sql`
+          INSERT INTO migration (id, time_completed)
+          VALUES ('20260730195856_optional_session_title', 1)
+        `)
+
+        yield* DatabaseMigration.applyOnly(db, [consolidatedV2Migration])
+
+        expect(yield* db.get(sql`SELECT id FROM migration WHERE id = ${consolidatedV2Migration.id}`)).toEqual({
+          id: consolidatedV2Migration.id,
+        })
+        expect(yield* db.get(sql`SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'kv'`)).toBeUndefined()
+      }),
+    )
+  })
+
   test("imports legacy JSON credentials without changing the source file or existing credentials", async () => {
     await using tmp = await tmpdir()
     const source = path.join(tmp.path, "auth.json")