import { FatalError, RetryableError, getWorkflowMetadata, sleep, } from "workflow"; import { admin, required } from "./supabase"; import { LinearSupportClient, LinearPermissionError, } from "./linear-support-client"; import { purgeClosedBugReport } from "./bug-retention"; const linearClient = () => new LinearSupportClient( process.env.LINEAR_API_KEY!, process.env.LINEAR_TEAM_ID!, process.env.LINEAR_PROJECT_ID, ); async function record(id: string) { const result = await admin() .from("bug_reports") .select("*") .eq("id", id) .maybeSingle(); if (result.error) required(result); return result.data; } export async function cleanupDoneBugReport( id: string, closedAt: string, claim: string, ) { "use workflow"; const run = getWorkflowMetadata().workflowRunId; try { const deadline = await markLinearDone(id, closedAt); if (deadline) { await sleep(new Date(deadline)); await purgeDone(id, closedAt); } } catch { await releaseCleanup(id, closedAt, claim, run); throw new Error("Bug report cleanup paused; the saved report will retry."); } } async function markLinearDone( id: string, closedAt: string, ): Promise { "use step"; try { const report = await record(id); if (!report || report.state !== "done" || report.closed_at !== closedAt) return null; if (!report.linear_done_synced) { await linearClient().done(id); required( await admin() .from("bug_reports") .update({ linear_done_synced: true, cleanup_error: null }) .eq("id", id) .eq("state", "done") .eq("closed_at", closedAt) .select("id"), ); } return report.delete_after ?? null; } catch (error) { if (error instanceof LinearPermissionError) throw new FatalError("The Linear key needs Write permission."); throw new RetryableError( "Could not move the Linear report to Done. Check the API key's Write permission.", { retryAfter: "1m" }, ); } } markLinearDone.maxRetries = 10; async function purgeDone(id: string, closedAt: string) { "use step"; try { await purgeClosedBugReport(id, closedAt, { record: () => record(id), linear: linearClient(), begin: async () => required( await admin().rpc("begin_bug_purge", { p_report: id, p_closed_at: closedAt, }), ), reopen: async () => { required( await admin().rpc("set_bug_report_done", { p_report: id, p_done: false, }), ); }, removeFiles: async (paths) => { required(await admin().storage.from("minegeo-support").remove(paths)); }, finish: async () => { required( await admin().rpc("finish_bug_purge", { p_report: id, p_closed_at: closedAt, }), ); }, }); } catch { throw new RetryableError( "Report deletion could not be confirmed. The saved record will retry.", { retryAfter: "5m" }, ); } } purgeDone.maxRetries = 10; async function releaseCleanup( id: string, closedAt: string, claim: string, run: string, ) { "use step"; required( await admin() .from("bug_reports") .update({ cleanup_run_id: null, cleanup_lease_until: new Date(Date.now() + 60000).toISOString(), cleanup_error: "Support cleanup paused. Check Linear Write permission and retry.", }) .eq("id", id) .eq("closed_at", closedAt) .in("cleanup_run_id", [claim, run]) .select("id"), ); console.error("Bug report cleanup paused", { id, run }); } releaseCleanup.maxRetries = 10;