@@ -8,6 +8,7 @@ import { Effect, Layer } from "effect"
88import { eq , inArray , sql } from "drizzle-orm"
99import { DatabaseMigration } from "@opencode-ai/core/database/migration"
1010import { migrations } from "@opencode-ai/core/database/migration.gen"
11+ import workspaceNameMigration from "@opencode-ai/core/database/migration/20260410174513_workspace-name"
1112import sessionUsageMigration from "@opencode-ai/core/database/migration/20260510033149_session_usage"
1213import normalizeStoragePathsMigration from "@opencode-ai/core/database/migration/20260601010001_normalize_storage_paths"
1314import sessionMessageProjectionOrderMigration from "@opencode-ai/core/database/migration/20260603040000_session_message_projection_order"
@@ -38,6 +39,68 @@ const run = <A, E>(effect: Effect.Effect<A, E, SqlClientService>) =>
3839const makeDb = EffectDrizzleSqlite . makeWithDefaults ( )
3940
4041describe ( "DatabaseMigration" , ( ) => {
42+ test ( "defaults missing workspace names while preserving legacy workspace data" , async ( ) => {
43+ await run (
44+ Effect . gen ( function * ( ) {
45+ const db = yield * makeDb
46+ yield * db . run ( sql `
47+ CREATE TABLE workspace (
48+ id text PRIMARY KEY,
49+ type text NOT NULL,
50+ branch text,
51+ directory text,
52+ extra text,
53+ project_id text NOT NULL
54+ )
55+ ` )
56+ yield * db . run ( sql `
57+ INSERT INTO workspace (id, type, branch, directory, extra, project_id)
58+ VALUES ('wrk_legacy', 'remote', 'main', '/repo', '{}', 'proj_legacy')
59+ ` )
60+
61+ yield * DatabaseMigration . applyOnly ( db , [ workspaceNameMigration ] )
62+
63+ expect ( yield * db . get ( sql `SELECT id, name, branch, directory, extra FROM workspace` ) ) . toEqual ( {
64+ id : "wrk_legacy" ,
65+ name : "" ,
66+ branch : "main" ,
67+ directory : "/repo" ,
68+ extra : "{}" ,
69+ } )
70+ } ) ,
71+ )
72+ } )
73+
74+ test ( "imports unnamed legacy Drizzle journal entries by their actual migration timestamps" , async ( ) => {
75+ await run (
76+ Effect . gen ( function * ( ) {
77+ const db = yield * makeDb
78+ yield * db . run ( sql `CREATE TABLE __drizzle_migrations (id integer PRIMARY KEY, hash text, created_at integer)` )
79+ yield * db . run ( sql `
80+ INSERT INTO __drizzle_migrations (hash, created_at)
81+ VALUES ('', ${ Date . UTC ( 2026 , 3 , 10 , 17 , 45 , 13 ) } )
82+ ` )
83+
84+ yield * DatabaseMigration . applyOnly ( db , [ workspaceNameMigration ] )
85+
86+ expect ( yield * db . all ( sql `SELECT id FROM migration` ) ) . toEqual ( [ { id : "20260410174513_workspace-name" } ] )
87+ } ) ,
88+ )
89+ } )
90+
91+ test ( "rejects unknown legacy Drizzle journal timestamps instead of guessing completed migrations" , async ( ) => {
92+ await expect (
93+ run (
94+ Effect . gen ( function * ( ) {
95+ const db = yield * makeDb
96+ yield * db . run ( sql `CREATE TABLE __drizzle_migrations (id integer PRIMARY KEY, hash text, created_at integer)` )
97+ yield * db . run ( sql `INSERT INTO __drizzle_migrations (hash, created_at) VALUES ('', 1234567890000)` )
98+ yield * DatabaseMigration . applyOnly ( db , [ workspaceNameMigration ] )
99+ } ) ,
100+ ) ,
101+ ) . rejects . toThrow ( "does not match any known migration" )
102+ } )
103+
41104 test ( "serializes concurrent embedded initialization for one database path" , async ( ) => {
42105 await using tmp = await tmpdir ( )
43106 const filename = path . join ( tmp . path , "embedded.sqlite" )
0 commit comments