@@ -131,7 +131,7 @@ export async function rollback(
131131 let tracesUpdated = 0 ;
132132 let featuresRolledBack = 0 ;
133133
134- if ( currentReleaseTag && currentSpec ) {
134+ if ( currentReleaseTag ) {
135135 // 1. Update the release spec itself (the one with the release_tag)
136136 specsRolledBack = await updateLinkedSpecs (
137137 ctx . db ,
@@ -143,75 +143,94 @@ export async function rollback(
143143 } as any
144144 ) ;
145145
146- // 2. Find and update all feature specs that were part of this release
147- // Features are linked via release_pr_number ( the PR that merged develop->main)
148- if ( currentSpec . release_pr_number ) {
149- const { data : featureSpecs } = await ctx . db
150- . from ( "specs " )
151- . select ( "id" )
152- . eq ( "repo_full_name" , repoFullName )
153- . eq ( "release_pr_number " , currentSpec . release_pr_number )
154- . neq ( "id ", currentSpec . id ) ;
146+ // 2. Find the release TRACE directly to get its release_pr_number
147+ // Look for a release trace that has the current release tag in production_deployment
148+ // or find it via release_pr_number from the spec
149+ const { data : releaseTraces } = await ctx . db
150+ . from ( "release_traces " )
151+ . select ( "id, release_pr_number, type, production_deployment " )
152+ . eq ( "repo_full_name" , repoFullName )
153+ . eq ( "type " , "release" )
154+ . in ( "status ", [ "production_live" , "merged_main" , "completed" ] ) ;
155155
156- if ( featureSpecs ?. length ) {
157- const featureIds = featureSpecs . map ( s => s . id ) ;
156+ // Find the trace that matches the current release tag
157+ const releaseTrace = releaseTraces ?. find ( ( t : any ) => {
158+ const prodDeploy = t . production_deployment as { releaseTag ?: string ; tag ?: string } | null ;
159+ return prodDeploy ?. releaseTag === currentReleaseTag || prodDeploy ?. tag === currentReleaseTag ;
160+ } ) ;
158161
159- // Mark feature specs as rolled_back
160- await ctx . db
161- . from ( "specs" )
162- . update ( {
163- release_status : "rolled_back" ,
164- updated_at : new Date ( ) . toISOString ( )
165- } )
166- . in ( "id" , featureIds ) ;
162+ // Get release_pr_number from either the trace or the spec
163+ const releasePrNumber = releaseTrace ?. release_pr_number ?? currentSpec ?. release_pr_number ;
164+
165+ logAction ( "rollback:findFeatures" , ctx , {
166+ currentReleaseTag,
167+ releasePrNumber,
168+ releaseTraceFound : ! ! releaseTrace ,
169+ currentSpecReleasePrNumber : currentSpec ?. release_pr_number
170+ } ) ;
171+
172+ // 3. Find and update all feature traces that were part of this release
173+ // Features are linked via release_pr_number on the traces table
174+ if ( releasePrNumber ) {
175+ // Find feature traces with the same release_pr_number
176+ const { data : featureTraces } = await ctx . db
177+ . from ( "release_traces" )
178+ . select ( "id, spec_id, title" )
179+ . eq ( "repo_full_name" , repoFullName )
180+ . eq ( "release_pr_number" , releasePrNumber )
181+ . eq ( "type" , "feature" )
182+ . in ( "status" , [ "production_live" , "merged_main" , "completed" ] ) ;
167183
168- featuresRolledBack = featureIds . length ;
184+ if ( featureTraces ?. length ) {
185+ featuresRolledBack = featureTraces . length ;
169186
170- // Update feature traces to rolled_back status and add audit events
171- for ( const featureId of featureIds ) {
172- // Get the trace for this feature
173- const { data : featureTrace } = await ctx . db
187+ // Update each feature trace
188+ for ( const featureTrace of featureTraces ) {
189+ // Update trace status
190+ await ctx . db
174191 . from ( "release_traces" )
175- . select ( "id, title" )
176- . eq ( "spec_id" , featureId )
177- . maybeSingle ( ) ;
192+ . update ( {
193+ status : "rolled_back" ,
194+ rollback_metadata : {
195+ rolledBackAt : new Date ( ) . toISOString ( ) ,
196+ rolledBackTo : targetReleaseTag ,
197+ rolledBackFrom : currentReleaseTag ,
198+ initiatedBy : ctx . actor ,
199+ source : ctx . source
200+ } ,
201+ updated_at : new Date ( ) . toISOString ( )
202+ } )
203+ . eq ( "id" , featureTrace . id ) ;
178204
179- if ( featureTrace ) {
180- // Update trace status
205+ // Update linked spec if exists
206+ if ( featureTrace . spec_id ) {
181207 await ctx . db
182- . from ( "release_traces " )
208+ . from ( "specs " )
183209 . update ( {
184- status : "rolled_back" ,
185- rollback_metadata : {
186- rolledBackAt : new Date ( ) . toISOString ( ) ,
187- rolledBackTo : targetReleaseTag ,
188- rolledBackFrom : currentReleaseTag ,
189- initiatedBy : ctx . actor ,
190- source : ctx . source
191- } ,
210+ release_status : "rolled_back" ,
192211 updated_at : new Date ( ) . toISOString ( )
193212 } )
194- . eq ( "id" , featureTrace . id ) ;
195-
196- // Add audit trail event
197- await ctx . db . from ( "trace_events" ) . insert ( {
198- trace_id : featureTrace . id ,
199- event_type : "feature_rolled_back" ,
200- actor : ctx . actor ,
201- actor_type : ctx . source === "system" || ctx . source === "webhook" ? "system" : "user" ,
202- source : ctx . source ,
203- details : {
204- rolledBackFrom : currentReleaseTag ,
205- rolledBackTo : targetReleaseTag ,
206- reason : `Feature was part of release ${ currentReleaseTag } which was rolled back to ${ targetReleaseTag } `
207- }
208- } ) ;
213+ . eq ( "id" , featureTrace . spec_id ) ;
209214 }
215+
216+ // Add audit trail event
217+ await ctx . db . from ( "trace_events" ) . insert ( {
218+ trace_id : featureTrace . id ,
219+ event_type : "feature_rolled_back" ,
220+ actor : ctx . actor ,
221+ actor_type : ctx . source === "system" || ctx . source === "webhook" ? "system" : "user" ,
222+ source : ctx . source ,
223+ details : {
224+ rolledBackFrom : currentReleaseTag ,
225+ rolledBackTo : targetReleaseTag ,
226+ reason : `Feature was part of release ${ currentReleaseTag } which was rolled back to ${ targetReleaseTag } `
227+ }
228+ } ) ;
210229 }
211230 }
212231 }
213232
214- // 3 . Update traces with current release tag (release traces)
233+ // 4 . Update the release trace itself
215234 tracesUpdated = await updateLinkedTraces (
216235 ctx . db ,
217236 repoFullName ,
@@ -241,57 +260,68 @@ export async function rollback(
241260 } )
242261 . eq ( "id" , targetSpec . id ) ;
243262
244- // 5. Also update any feature specs that were part of the target release back to deployed
245- // Update deployed_at so they appear as the most recently deployed
246- if ( targetSpec . release_pr_number ) {
247- await ctx . db
248- . from ( "specs" )
249- . update ( {
250- release_status : "deployed" ,
251- deployed_at : rollbackDeployedAt ,
252- updated_at : rollbackDeployedAt
253- } )
254- . eq ( "repo_full_name" , repoFullName )
255- . eq ( "release_pr_number" , targetSpec . release_pr_number ) ;
263+ // 5. Find the target release trace to get its release_pr_number
264+ const { data : targetReleaseTraces } = await ctx . db
265+ . from ( "release_traces" )
266+ . select ( "id, release_pr_number, type, production_deployment" )
267+ . eq ( "repo_full_name" , repoFullName )
268+ . eq ( "type" , "release" )
269+ . in ( "status" , [ "rolled_back" , "production_live" , "merged_main" , "completed" ] ) ;
270+
271+ const targetReleaseTrace = targetReleaseTraces ?. find ( ( t : any ) => {
272+ const prodDeploy = t . production_deployment as { releaseTag ?: string ; tag ?: string } | null ;
273+ return prodDeploy ?. releaseTag === targetReleaseTag || prodDeploy ?. tag === targetReleaseTag ;
274+ } ) ;
256275
257- // 6. Update feature traces for target release back to production_live
258- const { data : targetFeatureSpecs } = await ctx . db
259- . from ( "specs" )
260- . select ( "id" )
276+ const targetReleasePrNumber = targetReleaseTrace ?. release_pr_number ?? targetSpec . release_pr_number ;
277+
278+ // 6. Update any feature traces that were part of the target release back to production_live
279+ if ( targetReleasePrNumber ) {
280+ // Find feature traces with the same release_pr_number
281+ const { data : targetFeatureTraces } = await ctx . db
282+ . from ( "release_traces" )
283+ . select ( "id, spec_id, title" )
261284 . eq ( "repo_full_name" , repoFullName )
262- . eq ( "release_pr_number" , targetSpec . release_pr_number ) ;
285+ . eq ( "release_pr_number" , targetReleasePrNumber )
286+ . eq ( "type" , "feature" )
287+ . in ( "status" , [ "rolled_back" , "production_live" , "merged_main" , "completed" ] ) ;
263288
264- if ( targetFeatureSpecs ?. length ) {
265- for ( const spec of targetFeatureSpecs ) {
266- const { data : trace } = await ctx . db
289+ if ( targetFeatureTraces ?. length ) {
290+ for ( const featureTrace of targetFeatureTraces ) {
291+ // Update trace status back to production_live
292+ await ctx . db
267293 . from ( "release_traces" )
268- . select ( "id" )
269- . eq ( "spec_id" , spec . id )
270- . maybeSingle ( ) ;
294+ . update ( {
295+ status : "production_live" ,
296+ updated_at : new Date ( ) . toISOString ( )
297+ } )
298+ . eq ( "id" , featureTrace . id ) ;
271299
272- if ( trace ) {
300+ // Update linked spec if exists
301+ if ( featureTrace . spec_id ) {
273302 await ctx . db
274- . from ( "release_traces " )
303+ . from ( "specs " )
275304 . update ( {
276- status : "production_live" ,
277- updated_at : new Date ( ) . toISOString ( )
305+ release_status : "deployed" ,
306+ deployed_at : rollbackDeployedAt ,
307+ updated_at : rollbackDeployedAt
278308 } )
279- . eq ( "id" , trace . id ) ;
280-
281- // Add audit event for restored feature
282- await ctx . db . from ( "trace_events" ) . insert ( {
283- trace_id : trace . id ,
284- event_type : "feature_restored" ,
285- actor : ctx . actor ,
286- actor_type : ctx . source === "system" || ctx . source === "webhook" ? "system" : "user" ,
287- source : ctx . source ,
288- details : {
289- restoredViaRollback : true ,
290- releaseTag : targetReleaseTag ,
291- rolledBackFrom : currentReleaseTag
292- }
293- } ) ;
309+ . eq ( "id" , featureTrace . spec_id ) ;
294310 }
311+
312+ // Add audit event for restored feature
313+ await ctx . db . from ( "trace_events" ) . insert ( {
314+ trace_id : featureTrace . id ,
315+ event_type : "feature_restored" ,
316+ actor : ctx . actor ,
317+ actor_type : ctx . source === "system" || ctx . source === "webhook" ? "system" : "user" ,
318+ source : ctx . source ,
319+ details : {
320+ restoredViaRollback : true ,
321+ releaseTag : targetReleaseTag ,
322+ rolledBackFrom : currentReleaseTag
323+ }
324+ } ) ;
295325 }
296326 }
297327 }
0 commit comments