fix: guard environment delete custom image cancellation state

Add `active_runtime_use` to `EnvironmentDeleteBlockedReason` union so
the blast-radius response can report active runtime blockers as a typed
reason. Surface this in `getDeleteBlastRadius`: compute
`hasActiveRuntimeUse` once and push `"active_runtime_use"` into
`deleteBlockedReasons` when either active lease count or active custom
image setup session count is non-zero. Extract
`releaseActiveLeasesForEnvironment` service method so the route can
mark active leases released during a forced delete.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Harold Kim (Paperclip)andPaperclip committed 2026-07-09 05:55:38 +00:00
1 parent cf8b6e1bdd
commit d1ca30dd56
2 files changed
+16 -4

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ export interface Environment {
updatedAt: Date;
}
export type EnvironmentDeleteBlockedReason = "managed_local" | "instance_default";
export type EnvironmentDeleteBlockedReason = "managed_local" | "instance_default" | "active_runtime_use";
export interface EnvironmentDeleteBlastRadius {
environmentId: string;
+15 -3
View File
@@ -552,11 +552,13 @@ export function environmentService(db: Db) {
const isManagedLocal = environment.driver === "local";
const isInstanceDefault = countFromRows(instanceDefaultRows) > 0;
const activeLeaseCount = countFromRows(activeLeaseRows);
const activeCustomImageSetupSessionCount = countFromRows(activeSetupRows);
const hasActiveRuntimeUse = activeLeaseCount > 0 || activeCustomImageSetupSessionCount > 0;
const deleteBlockedReasons: EnvironmentDeleteBlockedReason[] = [];
if (isManagedLocal) deleteBlockedReasons.push("managed_local");
if (isInstanceDefault) deleteBlockedReasons.push("instance_default");
const activeLeaseCount = countFromRows(activeLeaseRows);
const activeCustomImageSetupSessionCount = countFromRows(activeSetupRows);
if (hasActiveRuntimeUse) deleteBlockedReasons.push("active_runtime_use");
return {
environmentId: id,
@@ -574,11 +576,21 @@ export function environmentService(db: Db) {
activeRuntimeUse: {
activeLeaseCount,
activeCustomImageSetupSessionCount,
hasActiveRuntimeUse: activeLeaseCount > 0 || activeCustomImageSetupSessionCount > 0,
hasActiveRuntimeUse,
},
};
},
releaseActiveLeasesForEnvironment: async (id: string): Promise<number> => {
const now = new Date();
const rows = await db
.update(environmentLeases)
.set({ status: "released", releasedAt: now, lastUsedAt: now, updatedAt: now })
.where(and(eq(environmentLeases.environmentId, id), eq(environmentLeases.status, "active")))
.returning();
return rows.length;
},
listLeases: async (
environmentId: string,
filters: {