Skip to content

Commit 137a5fd

Browse files
JeevantheDevclaude
andcommitted
fix: rollback feature traces along with release traces
- Find release trace directly by production_deployment.releaseTag - Find feature traces directly by release_pr_number on traces table - Mark all linked features as rolled_back when rolling back a release - Restore features to production_live when rolling back TO a release - Added logging to help debug feature trace linking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent fca6dc0 commit 137a5fd

1 file changed

Lines changed: 127 additions & 97 deletions

File tree

lib/actions/rollback.ts

Lines changed: 127 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)