@@ -46,16 +46,16 @@ const environment = {
4646 SOCKET_SERVER_URL : process . env . SOCKET_SERVER_URL ,
4747}
4848const execute = promisify ( execFile )
49- const run = ( command : string ) =>
49+ const run = ( command : string , artifacts = { manifest , report } ) =>
5050 execute (
5151 'bun' ,
5252 [
5353 '--no-env-file' ,
5454 'scripts/backfill-projects.ts' ,
5555 command ,
5656 '--manifest' ,
57- manifest ,
58- ...( command === 'plan' ? [ ] : [ '--report' , report , '--ack-release-drained' ] ) ,
57+ artifacts . manifest ,
58+ ...( command === 'plan' ? [ ] : [ '--report' , artifacts . report , '--ack-release-drained' ] ) ,
5959 ] ,
6060 {
6161 cwd : new URL ( '../../../' , import . meta. url ) ,
@@ -121,6 +121,61 @@ afterAll(async () => {
121121} )
122122
123123describe ( 'Operator archive repair against the full compatible schema' , ( ) => {
124+ it . each ( [ 'workspace' , 'project' , 'row' ] as const ) (
125+ 'defers a busy %s lock, repairs unrelated environments and resumes without losing progress' ,
126+ async ( lock ) => {
127+ const ownerId = `owner-${ lock } `
128+ const busyId = `busy-${ lock } `
129+ const freeId = `free-${ lock } `
130+ const projectId = `project-${ lock } `
131+ const artifacts = {
132+ manifest : join ( directory , `${ lock } -manifest.json` ) ,
133+ report : join ( directory , `${ lock } -report.json` ) ,
134+ }
135+ const holder = postgres ( url . toString ( ) , { max : 1 , onnotice : ( ) => { } } )
136+ try {
137+ await client `INSERT INTO "user" (id,name,email,email_verified,created_at,updated_at)
138+ VALUES (${ ownerId } ,'Owner',${ `${ lock } @fixture.test` } ,true,now(),now())`
139+ await client `INSERT INTO project (id,name,owner_id,archived_at)
140+ VALUES (${ projectId } ,'Archived Project',${ ownerId } ,'2026-05-01')`
141+ await client `INSERT INTO workspace (id,name,owner_id,billed_account_user_id,project_id,archived_at)
142+ VALUES (${ busyId } ,'Busy',${ ownerId } ,${ ownerId } ,${ projectId } ,'2026-05-01'),
143+ (${ freeId } ,'Free',${ ownerId } ,${ ownerId } ,NULL,'2026-05-01')`
144+ await client `INSERT INTO workflow (id,name,user_id,workspace_id,last_synced,created_at,updated_at)
145+ VALUES (${ busyId } ,'Busy flow',${ ownerId } ,${ busyId } ,now(),now(),now()),
146+ (${ freeId } ,'Free flow',${ ownerId } ,${ freeId } ,now(),now(),now())`
147+ await run ( 'plan' , artifacts )
148+ await holder . begin ( async ( tx ) => {
149+ if ( lock === 'row' ) await tx `SELECT id FROM workspace WHERE id = ${ busyId } FOR SHARE`
150+ else {
151+ const key = lock === 'workspace' ? `project-backfill:${ busyId } ` : `project:${ projectId } `
152+ await tx `SELECT pg_advisory_xact_lock(hashtextextended(${ key } ,0))`
153+ }
154+ await expect ( run ( 'repair' , artifacts ) ) . rejects . toMatchObject ( { code : 2 } )
155+ expect ( JSON . parse ( await readFile ( artifacts . report , 'utf8' ) ) ) . toMatchObject ( {
156+ status : 'incomplete' ,
157+ repairsCompleted : [ freeId ] ,
158+ } )
159+ expect ( await client `SELECT workspace_id FROM workflow WHERE archived_at IS NULL` ) . toEqual (
160+ [ { workspace_id : busyId } ]
161+ )
162+ } )
163+ await expect ( run ( 'repair' , artifacts ) ) . rejects . toMatchObject ( { code : 2 } )
164+ expect ( JSON . parse ( await readFile ( artifacts . report , 'utf8' ) ) . repairsCompleted ) . toEqual (
165+ expect . arrayContaining ( [ freeId , busyId ] )
166+ )
167+ await run ( 'apply' , artifacts )
168+ await run ( 'verify' , artifacts )
169+ } finally {
170+ await holder . end ( { timeout : 2 } )
171+ await client `DELETE FROM workspace WHERE owner_id = ${ ownerId } `
172+ await client `DELETE FROM project WHERE owner_id = ${ ownerId } `
173+ await client `DELETE FROM "user" WHERE id = ${ ownerId } `
174+ }
175+ } ,
176+ 60000
177+ )
178+
124179 it ( 'commits the complete cascade, reports external failure and resumes cleanup from its original manifest' , async ( ) => {
125180 await client `INSERT INTO "user" (id,name,email,email_verified,created_at,updated_at) VALUES ('owner','Owner','repair@fixture.test',true,now(),now())`
126181 await client `INSERT INTO workspace (id,name,owner_id,billed_account_user_id,archived_at) VALUES ('env','Archived','owner','owner','2026-05-01')`
0 commit comments