@baochunli/flakes 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/node_modules/@flakes/auth/package.json +1 -1
  2. package/dist/node_modules/@flakes/console/.output/nitro.json +1 -1
  3. package/dist/node_modules/@flakes/console/.output/public/assets/{DocumentationPage-CNPk8RVl.js → DocumentationPage-CGIcMKwf.js} +1 -1
  4. package/dist/node_modules/@flakes/console/.output/public/assets/ProjectsPage-ByTl4Kyy.js +1 -0
  5. package/dist/node_modules/@flakes/console/.output/public/assets/RunPage-5MmbVpaE.js +3 -0
  6. package/dist/node_modules/@flakes/console/.output/public/assets/{RunsPage-CJbycrqM.js → RunsPage-fbDKP9kM.js} +1 -1
  7. package/dist/node_modules/@flakes/console/.output/public/assets/{SandboxesLens-BoLM52PY.js → SandboxesLens-DPG87mUE.js} +1 -1
  8. package/dist/node_modules/@flakes/console/.output/public/assets/SandboxesPage-DIpzR_Ef.js +1 -0
  9. package/dist/node_modules/@flakes/console/.output/public/assets/TranscriptPage-mCgSqfv0.js +1 -0
  10. package/dist/node_modules/@flakes/console/.output/public/assets/_-CM2hE2sZ.js +1 -0
  11. package/dist/node_modules/@flakes/console/.output/public/assets/_runId-3ZZOAqUK.js +2 -0
  12. package/dist/node_modules/@flakes/console/.output/public/assets/_taskId-cy4RxW8Y.js +2 -0
  13. package/dist/node_modules/@flakes/console/.output/public/assets/{account-BoRurhrq.js → account-BWoGGXJw.js} +1 -1
  14. package/dist/node_modules/@flakes/console/.output/public/assets/{account-forms-D4eVqOhW.js → account-forms-Dq70JDyg.js} +1 -1
  15. package/dist/node_modules/@flakes/console/.output/public/assets/{auth-layout-DBnq_eif.js → auth-layout-BFusNq3W.js} +1 -1
  16. package/dist/node_modules/@flakes/console/.output/public/assets/{bits-BOMPEHvv.js → bits-CTGNpp-v.js} +1 -1
  17. package/dist/node_modules/@flakes/console/.output/public/assets/device-BajR3eSF.js +1 -0
  18. package/dist/node_modules/@flakes/console/.output/public/assets/documentation-6ao_Ux6x.js +1 -0
  19. package/dist/node_modules/@flakes/console/.output/public/assets/{forgot-password-BSQ4TWpA.js → forgot-password-DOKn8_7P.js} +1 -1
  20. package/dist/node_modules/@flakes/console/.output/public/assets/main-DVO9gWzi.js +1215 -0
  21. package/dist/node_modules/@flakes/console/.output/public/assets/{otp-verification-CussJQT1.js → otp-verification-qolVtiom.js} +2 -2
  22. package/dist/node_modules/@flakes/console/.output/public/assets/projects-n5LSsNi3.js +2 -0
  23. package/dist/node_modules/@flakes/console/.output/public/assets/{reset-password-DwWhVkEh.js → reset-password-D-OlZeHQ.js} +1 -1
  24. package/dist/node_modules/@flakes/console/.output/public/assets/runs-BB9ogobQ.js +2 -0
  25. package/dist/node_modules/@flakes/console/.output/public/assets/sandboxes-BIimz5FI.js +2 -0
  26. package/dist/node_modules/@flakes/console/.output/public/assets/{security-MjM55vIw.js → security-DdQNcp6k.js} +1 -1
  27. package/dist/node_modules/@flakes/console/.output/public/assets/{signin-KbA1cD6o.js → signin-BoZiVxyq.js} +1 -1
  28. package/dist/node_modules/@flakes/console/.output/public/assets/{signin-form-CBF-d8iK.js → signin-form-tTBWDjhi.js} +1 -1
  29. package/dist/node_modules/@flakes/console/.output/public/assets/{signup-CbHsThKr.js → signup-CBSszmUC.js} +1 -1
  30. package/dist/node_modules/@flakes/console/.output/public/assets/styles-B8OXOvkO.css +2 -0
  31. package/dist/node_modules/@flakes/console/.output/server/{_-BULJ6utw.mjs → _-BGsKgsbF.mjs} +3 -3
  32. package/dist/node_modules/@flakes/console/.output/server/{_-_ST9QYWn.mjs → _-DtPMXzt0.mjs} +3 -3
  33. package/dist/node_modules/@flakes/console/.output/server/_chunks/ProjectsPage.mjs +1 -2
  34. package/dist/node_modules/@flakes/console/.output/server/_chunks/RunPage.mjs +1 -2
  35. package/dist/node_modules/@flakes/console/.output/server/_chunks/RunsPage.mjs +1 -2
  36. package/dist/node_modules/@flakes/console/.output/server/_chunks/SandboxesLens.mjs +1 -1
  37. package/dist/node_modules/@flakes/console/.output/server/_chunks/SandboxesPage.mjs +253 -18
  38. package/dist/node_modules/@flakes/console/.output/server/_chunks/TranscriptPage.mjs +1 -2
  39. package/dist/node_modules/@flakes/console/.output/server/_chunks/bits.mjs +1 -1
  40. package/dist/node_modules/@flakes/console/.output/server/_chunks/button.mjs +461 -3
  41. package/dist/node_modules/@flakes/console/.output/server/_chunks/card.mjs +1 -1
  42. package/dist/node_modules/@flakes/console/.output/server/_chunks/router.mjs +3 -4
  43. package/dist/node_modules/@flakes/console/.output/server/_chunks/table.mjs +1 -1
  44. package/dist/node_modules/@flakes/console/.output/server/{_runId-WZmbo654.mjs → _runId-P1u-vfcm.mjs} +2 -2
  45. package/dist/node_modules/@flakes/console/.output/server/{_runId-CmYWNL8A.mjs → _runId-vO4tlCOP.mjs} +3 -3
  46. package/dist/node_modules/@flakes/console/.output/server/_ssr/{App-CUx8Ftit.mjs → App-C-XaVKoO.mjs} +2 -2
  47. package/dist/node_modules/@flakes/console/.output/server/_ssr/{DocumentationPage-0I9oWWJT.mjs → DocumentationPage-BR2KosFz.mjs} +2 -2
  48. package/dist/node_modules/@flakes/console/.output/server/_ssr/{ProjectsPage-CGj9V2Of.mjs → ProjectsPage-BUrep1wl.mjs} +2 -2
  49. package/dist/node_modules/@flakes/console/.output/server/_ssr/{RunPage-B2vhVaj6.mjs → RunPage-BHs-v0LU.mjs} +2 -2
  50. package/dist/node_modules/@flakes/console/.output/server/_ssr/{RunsPage-BifKogQE.mjs → RunsPage-iTNYVwL0.mjs} +2 -2
  51. package/dist/node_modules/@flakes/console/.output/server/_ssr/SandboxesPage-C1Z6dVce.mjs +423 -0
  52. package/dist/node_modules/@flakes/console/.output/server/_ssr/{TranscriptPage-deSxVZaQ.mjs → TranscriptPage-BcomoTls.mjs} +2 -2
  53. package/dist/node_modules/@flakes/console/.output/server/_ssr/{account-D5y9vkAc.mjs → account-Cp0s_IQK.mjs} +2 -2
  54. package/dist/node_modules/@flakes/console/.output/server/_ssr/{documentation-C91gfyy0.mjs → documentation-BnxmM-Yo.mjs} +2 -2
  55. package/dist/node_modules/@flakes/console/.output/server/_ssr/{documentation-C69e9_eF.mjs → documentation-Cgyr3VKK.mjs} +3 -3
  56. package/dist/node_modules/@flakes/console/.output/server/_ssr/{hooks-DrgN35zn.mjs → hooks-CsVtQ1m-.mjs} +117 -51
  57. package/dist/node_modules/@flakes/console/.output/server/_ssr/{projects-lJ2tIIPj.mjs → projects-R9sxR_t4.mjs} +1 -1
  58. package/dist/node_modules/@flakes/console/.output/server/_ssr/{router-DHxFQ03L.mjs → router-CobdI6gO.mjs} +12 -12
  59. package/dist/node_modules/@flakes/console/.output/server/_ssr/{runs-dnPfp9dt.mjs → runs-D8MHe3QU.mjs} +2 -2
  60. package/dist/node_modules/@flakes/console/.output/server/_ssr/{runs-BWxEGKO_.mjs → runs-mTxbc_pm.mjs} +3 -3
  61. package/dist/node_modules/@flakes/console/.output/server/_ssr/{sandboxes-v28LJQM3.mjs → sandboxes-B2z_9JyU.mjs} +2 -2
  62. package/dist/node_modules/@flakes/console/.output/server/_ssr/{sandboxes-hxFINxDS.mjs → sandboxes-r0y6Ihg6.mjs} +3 -3
  63. package/dist/node_modules/@flakes/console/.output/server/_ssr/ssr.mjs +2 -2
  64. package/dist/node_modules/@flakes/console/.output/server/{_tanstack-start-manifest_v-DfwuEzTr.mjs → _tanstack-start-manifest_v-DpAwCFK9.mjs} +32 -33
  65. package/dist/node_modules/@flakes/console/.output/server/{_taskId-CBo95z-s.mjs → _taskId-Cz9xwj5L.mjs} +2 -2
  66. package/dist/node_modules/@flakes/console/.output/server/{_taskId-Sil3-PKh.mjs → _taskId-wQsG2CQj.mjs} +3 -3
  67. package/dist/node_modules/@flakes/console/package.json +1 -1
  68. package/dist/node_modules/@flakes/control-plane/dist/host-credentials.d.ts +1 -1
  69. package/dist/node_modules/@flakes/control-plane/dist/host-credentials.d.ts.map +1 -1
  70. package/dist/node_modules/@flakes/control-plane/dist/routes/sandboxes.js +48 -3
  71. package/dist/node_modules/@flakes/control-plane/dist/routes/sandboxes.js.map +1 -1
  72. package/dist/node_modules/@flakes/control-plane/package.json +1 -1
  73. package/dist/node_modules/@flakes/core/package.json +1 -1
  74. package/dist/node_modules/@flakes/harness-agent/package.json +1 -1
  75. package/dist/node_modules/@flakes/harness-sdk/package.json +1 -1
  76. package/dist/node_modules/@flakes/pi-agent/package.json +1 -1
  77. package/dist/node_modules/@flakes/protocol/package.json +1 -1
  78. package/dist/node_modules/@flakes/sandbox-runtime/package.json +1 -1
  79. package/dist/node_modules/@flakes/store/dist/managed-sandbox-instances-store.d.ts +9 -1
  80. package/dist/node_modules/@flakes/store/dist/managed-sandbox-instances-store.d.ts.map +1 -1
  81. package/dist/node_modules/@flakes/store/dist/managed-sandbox-instances-store.js +407 -6
  82. package/dist/node_modules/@flakes/store/dist/managed-sandbox-instances-store.js.map +1 -1
  83. package/dist/node_modules/@flakes/store/dist/mappers.d.ts.map +1 -1
  84. package/dist/node_modules/@flakes/store/dist/mappers.js +1 -0
  85. package/dist/node_modules/@flakes/store/dist/mappers.js.map +1 -1
  86. package/dist/node_modules/@flakes/store/dist/types.d.ts +30 -1
  87. package/dist/node_modules/@flakes/store/dist/types.d.ts.map +1 -1
  88. package/dist/node_modules/@flakes/store/package.json +1 -1
  89. package/package.json +1 -1
  90. package/dist/node_modules/@flakes/auth/dist/better-auth-shape.d.ts +0 -10
  91. package/dist/node_modules/@flakes/auth/dist/better-auth-shape.d.ts.map +0 -1
  92. package/dist/node_modules/@flakes/auth/dist/better-auth-shape.js +0 -16
  93. package/dist/node_modules/@flakes/auth/dist/better-auth-shape.js.map +0 -1
  94. package/dist/node_modules/@flakes/console/.output/public/assets/ProjectsPage-CKPR1nnu.js +0 -1
  95. package/dist/node_modules/@flakes/console/.output/public/assets/RunPage-BdugbnA3.js +0 -3
  96. package/dist/node_modules/@flakes/console/.output/public/assets/SandboxesPage-6KHjMeaR.js +0 -1
  97. package/dist/node_modules/@flakes/console/.output/public/assets/TranscriptPage-CHSq0_qN.js +0 -1
  98. package/dist/node_modules/@flakes/console/.output/public/assets/_-CD1Voz9h.js +0 -1
  99. package/dist/node_modules/@flakes/console/.output/public/assets/_runId-_0VPmGYe.js +0 -2
  100. package/dist/node_modules/@flakes/console/.output/public/assets/_taskId-CPAKAp4X.js +0 -2
  101. package/dist/node_modules/@flakes/console/.output/public/assets/device-FqII-S41.js +0 -1
  102. package/dist/node_modules/@flakes/console/.output/public/assets/dist-BEfdrJTS.js +0 -1
  103. package/dist/node_modules/@flakes/console/.output/public/assets/documentation-2C3ncAuV.js +0 -1
  104. package/dist/node_modules/@flakes/console/.output/public/assets/main-LpSDdT-S.js +0 -1215
  105. package/dist/node_modules/@flakes/console/.output/public/assets/projects-CLgKBrCe.js +0 -2
  106. package/dist/node_modules/@flakes/console/.output/public/assets/runs-CFYk2iRh.js +0 -2
  107. package/dist/node_modules/@flakes/console/.output/public/assets/sandboxes-mGsOcVFL.js +0 -2
  108. package/dist/node_modules/@flakes/console/.output/public/assets/styles-DtA2wbW8.css +0 -2
  109. package/dist/node_modules/@flakes/console/.output/server/_chunks/utils.mjs +0 -393
  110. package/dist/node_modules/@flakes/console/.output/server/_ssr/SandboxesPage-DGji_OdB.mjs +0 -187
  111. package/dist/node_modules/@flakes/control-plane/dist/managed-sandbox-provisioner.d.ts +0 -71
  112. package/dist/node_modules/@flakes/control-plane/dist/managed-sandbox-provisioner.d.ts.map +0 -1
  113. package/dist/node_modules/@flakes/control-plane/dist/managed-sandbox-provisioner.js +0 -243
  114. package/dist/node_modules/@flakes/control-plane/dist/managed-sandbox-provisioner.js.map +0 -1
  115. package/dist/node_modules/@flakes/control-plane/dist/routes/sandboxes-admin.d.ts +0 -3
  116. package/dist/node_modules/@flakes/control-plane/dist/routes/sandboxes-admin.d.ts.map +0 -1
  117. package/dist/node_modules/@flakes/control-plane/dist/routes/sandboxes-admin.js +0 -153
  118. package/dist/node_modules/@flakes/control-plane/dist/routes/sandboxes-admin.js.map +0 -1
  119. package/dist/node_modules/@flakes/control-plane/dist/sandbox-drain.d.ts +0 -19
  120. package/dist/node_modules/@flakes/control-plane/dist/sandbox-drain.d.ts.map +0 -1
  121. package/dist/node_modules/@flakes/control-plane/dist/sandbox-drain.js +0 -46
  122. package/dist/node_modules/@flakes/control-plane/dist/sandbox-drain.js.map +0 -1
  123. package/dist/node_modules/@flakes/store/dist/sqlite.d.ts +0 -4
  124. package/dist/node_modules/@flakes/store/dist/sqlite.d.ts.map +0 -1
  125. package/dist/node_modules/@flakes/store/dist/sqlite.js +0 -34
  126. package/dist/node_modules/@flakes/store/dist/sqlite.js.map +0 -1
@@ -4,7 +4,17 @@ import { HOST_PROVISIONING_QUOTAS, HostProvisioningQuotaError, jsonByteLength, }
4
4
  import { addMs, fromJson, toJson } from "./json.js";
5
5
  import { mapHost, mapHostCredential, mapHostPool, mapManagedSandboxInstance, mapProvisioningLease, mapSandboxCredential, mapSandboxIdentity, } from "./mappers.js";
6
6
  const nonTerminalManagedInstanceStatuses = ["pending_launch", "launching", "running", "draining", "stopping"];
7
- const cleanupManagedInstanceStatuses = ["stopped", "failed", "expired", "cancelled"];
7
+ const cleanupManagedInstanceStatuses = ["stopped", "failed", "expired", "cancelled", "deleting"];
8
+ const noProviderDeleteManagedInstanceStatuses = [
9
+ "stopped",
10
+ "failed",
11
+ "expired",
12
+ "cancelled",
13
+ "running",
14
+ "draining",
15
+ "stopping",
16
+ ];
17
+ const providerDeleteManagedInstanceStatuses = [...noProviderDeleteManagedInstanceStatuses, "deleting"];
8
18
  const nonTerminalProvisioningLeaseStates = [
9
19
  "pending",
10
20
  "claimed",
@@ -1061,6 +1071,261 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
1061
1071
  clearedAssignments: cleared.cleared,
1062
1072
  };
1063
1073
  }
1074
+ /** Deletes or queues cleanup for an owner-scoped stale managed sandbox row. */
1075
+ async deleteManagedSandboxInstance(input) {
1076
+ return this.withTransaction(async (trx) => {
1077
+ const liveness = await this.lockManagedSandboxLiveness(trx, input.sandboxId);
1078
+ const instance = await trx
1079
+ .selectFrom("managed_sandbox_instances as instance")
1080
+ .innerJoin("sandbox_identities as identity", "identity.sandbox_id", "instance.sandbox_id")
1081
+ .select([
1082
+ "instance.instance_id as instance_id",
1083
+ "instance.owner_kind as owner_kind",
1084
+ "instance.owner_id as owner_id",
1085
+ "instance.owner_key as owner_key",
1086
+ "instance.project_id as project_id",
1087
+ "instance.host_id as host_id",
1088
+ "instance.host_pool_id as host_pool_id",
1089
+ "instance.sandbox_id as sandbox_id",
1090
+ "instance.status as status",
1091
+ "instance.provider_resource_id as provider_resource_id",
1092
+ "identity.sandbox_identity_id as sandbox_identity_id",
1093
+ "identity.disabled_at as identity_disabled_at",
1094
+ ])
1095
+ .where("instance.owner_key", "=", input.ownerKey)
1096
+ .where("instance.sandbox_id", "=", input.sandboxId)
1097
+ .where("identity.owner_key", "=", input.ownerKey)
1098
+ .forUpdate()
1099
+ .executeTakeFirst();
1100
+ if (!instance) {
1101
+ return { status: "not_found" };
1102
+ }
1103
+ const blockers = [];
1104
+ const providerResourceId = instance.provider_resource_id;
1105
+ const eligibleStatuses = providerResourceId === null
1106
+ ? noProviderDeleteManagedInstanceStatuses
1107
+ : providerDeleteManagedInstanceStatuses;
1108
+ if (!eligibleStatuses.includes(instance.status)) {
1109
+ blockers.push("unsafe_status");
1110
+ }
1111
+ if (liveness?.status === "active") {
1112
+ blockers.push("live_sandbox");
1113
+ }
1114
+ if (blockers.length > 0) {
1115
+ return {
1116
+ status: "blocked",
1117
+ sandboxId: input.sandboxId,
1118
+ blockers,
1119
+ };
1120
+ }
1121
+ const latestLiveness = liveness ??
1122
+ (await this.lockManagedSandboxLiveness(trx, input.sandboxId));
1123
+ if (latestLiveness?.status === "active") {
1124
+ return {
1125
+ status: "blocked",
1126
+ sandboxId: input.sandboxId,
1127
+ blockers: ["live_sandbox"],
1128
+ };
1129
+ }
1130
+ const hostPoolId = instance.host_pool_id;
1131
+ if (providerResourceId !== null) {
1132
+ if (hostPoolId === null) {
1133
+ return {
1134
+ status: "blocked",
1135
+ sandboxId: input.sandboxId,
1136
+ blockers: ["provider_cleanup_required"],
1137
+ };
1138
+ }
1139
+ if (await this.managedSandboxHasAcceptedRunningAssignments(trx, input.sandboxId)) {
1140
+ return {
1141
+ status: "blocked",
1142
+ sandboxId: input.sandboxId,
1143
+ blockers: ["accepted_running_assignments"],
1144
+ };
1145
+ }
1146
+ const clearedAssignments = await this.clearReservedSandboxAssignmentsForDelete(trx, input.sandboxId);
1147
+ const now = this.nowIso();
1148
+ const reason = redactReason(input.reason) ?? "managed sandbox deleted";
1149
+ await this.cancelSupersededProviderStopLeasesForDelete(trx, {
1150
+ ownerKey: instance.owner_key,
1151
+ hostId: instance.host_id,
1152
+ instanceId: instance.instance_id,
1153
+ }, now, reason);
1154
+ const cleanupLease = await this.queueManagedSandboxDeleteCleanup(trx, {
1155
+ instanceId: instance.instance_id,
1156
+ ownerKind: instance.owner_kind,
1157
+ ownerId: instance.owner_id,
1158
+ ownerKey: instance.owner_key,
1159
+ projectId: instance.project_id,
1160
+ hostId: instance.host_id,
1161
+ hostPoolId,
1162
+ sandboxId: instance.sandbox_id,
1163
+ providerResourceId,
1164
+ }, now);
1165
+ await trx
1166
+ .updateTable("managed_sandbox_instances")
1167
+ .set({
1168
+ status: "deleting",
1169
+ last_error: reason,
1170
+ updated_at: now,
1171
+ })
1172
+ .where("instance_id", "=", instance.instance_id)
1173
+ .where("owner_key", "=", input.ownerKey)
1174
+ .where("sandbox_id", "=", input.sandboxId)
1175
+ .where("provider_resource_id", "=", providerResourceId)
1176
+ .where("status", "in", [...providerDeleteManagedInstanceStatuses])
1177
+ .executeTakeFirst();
1178
+ await this.invalidateManagedSandboxRuntimeAuthForDelete(trx, {
1179
+ sandboxIdentityId: instance.sandbox_identity_id,
1180
+ identityDisabledAt: instance.identity_disabled_at,
1181
+ reason,
1182
+ now,
1183
+ });
1184
+ return {
1185
+ status: "cleanup_queued",
1186
+ sandboxId: input.sandboxId,
1187
+ instanceId: instance.instance_id,
1188
+ leaseId: cleanupLease.leaseId,
1189
+ providerResourceId,
1190
+ clearedAssignments,
1191
+ };
1192
+ }
1193
+ if (await this.managedSandboxHasAcceptedRunningAssignments(trx, input.sandboxId)) {
1194
+ return {
1195
+ status: "blocked",
1196
+ sandboxId: input.sandboxId,
1197
+ blockers: ["accepted_running_assignments"],
1198
+ };
1199
+ }
1200
+ const clearedAssignments = await this.clearReservedSandboxAssignmentsForDelete(trx, input.sandboxId);
1201
+ const now = this.nowIso();
1202
+ const reason = redactReason(input.reason) ?? "managed sandbox deleted";
1203
+ await this.invalidateManagedSandboxRuntimeAuthForDelete(trx, {
1204
+ sandboxIdentityId: instance.sandbox_identity_id,
1205
+ identityDisabledAt: instance.identity_disabled_at,
1206
+ reason,
1207
+ now,
1208
+ });
1209
+ await trx
1210
+ .deleteFrom("managed_sandbox_instances")
1211
+ .where("instance_id", "=", instance.instance_id)
1212
+ .where("owner_key", "=", input.ownerKey)
1213
+ .where("sandbox_id", "=", input.sandboxId)
1214
+ .executeTakeFirst();
1215
+ return {
1216
+ status: "deleted",
1217
+ sandboxId: input.sandboxId,
1218
+ instanceId: instance.instance_id,
1219
+ clearedAssignments,
1220
+ };
1221
+ });
1222
+ }
1223
+ async invalidateManagedSandboxRuntimeAuthForDelete(executor, input) {
1224
+ await executor
1225
+ .updateTable("sandbox_credentials")
1226
+ .set({
1227
+ revoked_at: input.now,
1228
+ revoked_reason: input.reason,
1229
+ updated_at: input.now,
1230
+ })
1231
+ .where("sandbox_identity_id", "=", input.sandboxIdentityId)
1232
+ .where("revoked_at", "is", null)
1233
+ .execute();
1234
+ await executor
1235
+ .updateTable("sandbox_identities")
1236
+ .set({
1237
+ status: "disabled",
1238
+ disabled_at: input.identityDisabledAt ?? input.now,
1239
+ updated_at: input.now,
1240
+ })
1241
+ .where("sandbox_identity_id", "=", input.sandboxIdentityId)
1242
+ .execute();
1243
+ }
1244
+ async queueManagedSandboxDeleteCleanup(executor, instance, now) {
1245
+ const existing = await executor
1246
+ .selectFrom("provisioning_leases")
1247
+ .selectAll()
1248
+ .where("owner_key", "=", instance.ownerKey)
1249
+ .where("host_id", "=", instance.hostId)
1250
+ .where("instance_id", "=", instance.instanceId)
1251
+ .where("action", "=", "provider_cleanup")
1252
+ .where("state", "in", [...nonTerminalProvisioningLeaseStates])
1253
+ .forUpdate()
1254
+ .executeTakeFirst();
1255
+ if (existing) {
1256
+ if (existing.state === "pending" && existing.lease_deadline_at <= now) {
1257
+ const refreshed = await executor
1258
+ .updateTable("provisioning_leases")
1259
+ .set({
1260
+ lease_deadline_at: addMs(this.clock.now(), providerCleanupLeaseTtlMs),
1261
+ last_error: null,
1262
+ updated_at: now,
1263
+ })
1264
+ .where("lease_id", "=", existing.lease_id)
1265
+ .where("owner_key", "=", instance.ownerKey)
1266
+ .where("host_id", "=", instance.hostId)
1267
+ .where("action", "=", "provider_cleanup")
1268
+ .where("state", "=", "pending")
1269
+ .returningAll()
1270
+ .executeTakeFirst();
1271
+ return mapProvisioningLease(refreshed ?? existing);
1272
+ }
1273
+ return mapProvisioningLease(existing);
1274
+ }
1275
+ await this.assertProvisioningLeaseQuota(executor, instance.ownerKey);
1276
+ const row = await executor
1277
+ .insertInto("provisioning_leases")
1278
+ .values({
1279
+ lease_id: this.idGenerator.createId("provisioning_lease"),
1280
+ owner_kind: instance.ownerKind,
1281
+ owner_id: instance.ownerId,
1282
+ owner_key: instance.ownerKey,
1283
+ project_id: instance.projectId,
1284
+ host_project_binding_id: null,
1285
+ host_id: instance.hostId,
1286
+ host_pool_id: instance.hostPoolId,
1287
+ instance_id: instance.instanceId,
1288
+ sandbox_id: instance.sandboxId,
1289
+ demand_task_id: null,
1290
+ action: "provider_cleanup",
1291
+ state: "pending",
1292
+ claim_id: null,
1293
+ attempt: 0,
1294
+ max_attempts: providerCleanupLeaseMaxAttempts,
1295
+ lease_deadline_at: addMs(this.clock.now(), providerCleanupLeaseTtlMs),
1296
+ claim_deadline_at: null,
1297
+ hello_deadline_at: null,
1298
+ accepted_at: null,
1299
+ running_at: null,
1300
+ completed_at: null,
1301
+ credential_ref: null,
1302
+ provider_resource_id: instance.providerResourceId,
1303
+ provider_result_json: null,
1304
+ last_error: null,
1305
+ release_audit_json: null,
1306
+ created_at: now,
1307
+ updated_at: now,
1308
+ })
1309
+ .returningAll()
1310
+ .executeTakeFirstOrThrow();
1311
+ return mapProvisioningLease(row);
1312
+ }
1313
+ async cancelSupersededProviderStopLeasesForDelete(executor, input, now, reason) {
1314
+ await executor
1315
+ .updateTable("provisioning_leases")
1316
+ .set({
1317
+ state: "cancelled",
1318
+ completed_at: now,
1319
+ last_error: reason,
1320
+ updated_at: now,
1321
+ })
1322
+ .where("owner_key", "=", input.ownerKey)
1323
+ .where("host_id", "=", input.hostId)
1324
+ .where("instance_id", "=", input.instanceId)
1325
+ .where("action", "=", "provider_stop")
1326
+ .where("state", "in", [...nonTerminalProvisioningLeaseStates])
1327
+ .execute();
1328
+ }
1064
1329
  /** Returns one owner/host-scoped provisioning lease. */
1065
1330
  async getProvisioningLease(input) {
1066
1331
  const row = await this.db
@@ -1675,7 +1940,8 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
1675
1940
  "available") {
1676
1941
  return [];
1677
1942
  }
1678
- const now = this.nowIso();
1943
+ const nowDate = this.clock.now();
1944
+ const now = nowDate.toISOString();
1679
1945
  const candidates = await trx
1680
1946
  .selectFrom("provisioning_leases as lease")
1681
1947
  .innerJoin("host_pools as pool", "pool.host_pool_id", "lease.host_pool_id")
@@ -1775,7 +2041,8 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
1775
2041
  action: "provider_stop",
1776
2042
  limit,
1777
2043
  });
1778
- const now = this.nowIso();
2044
+ const nowDate = this.clock.now();
2045
+ const now = nowDate.toISOString();
1779
2046
  const candidates = await trx
1780
2047
  .selectFrom("provisioning_leases as lease")
1781
2048
  .innerJoin("host_pools as pool", "pool.host_pool_id", "lease.host_pool_id")
@@ -1841,17 +2108,26 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
1841
2108
  action: "provider_cleanup",
1842
2109
  limit,
1843
2110
  });
1844
- const now = this.nowIso();
2111
+ const nowDate = this.clock.now();
2112
+ const now = nowDate.toISOString();
1845
2113
  const candidates = await trx
1846
2114
  .selectFrom("provisioning_leases as lease")
1847
2115
  .innerJoin("host_pools as pool", "pool.host_pool_id", "lease.host_pool_id")
1848
2116
  .innerJoin("managed_sandbox_instances as instance", "instance.instance_id", "lease.instance_id")
1849
- .select(["lease.lease_id", "pool.provider"])
2117
+ .select([
2118
+ "lease.lease_id",
2119
+ "lease.lease_deadline_at",
2120
+ "pool.provider",
2121
+ "instance.status as instance_status",
2122
+ ])
1850
2123
  .where("lease.owner_key", "=", input.ownerKey)
1851
2124
  .where("lease.host_id", "=", input.hostId)
1852
2125
  .where("lease.action", "=", "provider_cleanup")
1853
2126
  .where("lease.state", "=", "pending")
1854
- .where("lease.lease_deadline_at", ">", now)
2127
+ .where((eb) => eb.or([
2128
+ eb("lease.lease_deadline_at", ">", now),
2129
+ eb("instance.status", "=", "deleting"),
2130
+ ]))
1855
2131
  .where("lease.provider_resource_id", "is not", null)
1856
2132
  .where("pool.owner_key", "=", input.ownerKey)
1857
2133
  .where("pool.host_id", "=", input.hostId)
@@ -1868,6 +2144,25 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
1868
2144
  .execute();
1869
2145
  const claimed = [];
1870
2146
  for (const candidate of candidates) {
2147
+ if (candidate.instance_status === "deleting" &&
2148
+ candidate.lease_deadline_at <= now) {
2149
+ const refreshed = await trx
2150
+ .updateTable("provisioning_leases")
2151
+ .set({
2152
+ lease_deadline_at: addMs(nowDate, providerCleanupLeaseTtlMs),
2153
+ last_error: null,
2154
+ updated_at: now,
2155
+ })
2156
+ .where("lease_id", "=", candidate.lease_id)
2157
+ .where("owner_key", "=", input.ownerKey)
2158
+ .where("host_id", "=", input.hostId)
2159
+ .where("action", "=", "provider_cleanup")
2160
+ .where("state", "=", "pending")
2161
+ .executeTakeFirst();
2162
+ if (refreshed.numUpdatedRows === 0n) {
2163
+ continue;
2164
+ }
2165
+ }
1871
2166
  const result = await this.claimLeaseInTransaction(trx, {
1872
2167
  ownerKey: input.ownerKey,
1873
2168
  hostId: input.hostId,
@@ -2360,6 +2655,20 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
2360
2655
  if (!instanceRow) {
2361
2656
  return { status: "rejected" };
2362
2657
  }
2658
+ const deletingIdentity = input.status === "succeeded" && instanceRow.status === "deleting"
2659
+ ? await trx
2660
+ .selectFrom("sandbox_identities")
2661
+ .select(["sandbox_identity_id", "disabled_at"])
2662
+ .where("owner_key", "=", current.owner_key)
2663
+ .where("sandbox_id", "=", current.sandbox_id ?? "")
2664
+ .forUpdate()
2665
+ .executeTakeFirst()
2666
+ : undefined;
2667
+ if (input.status === "succeeded" && instanceRow.status === "deleting") {
2668
+ if (!deletingIdentity) {
2669
+ return { status: "rejected" };
2670
+ }
2671
+ }
2363
2672
  const row = await trx
2364
2673
  .updateTable("provisioning_leases")
2365
2674
  .set({
@@ -2385,6 +2694,22 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
2385
2694
  if (!row) {
2386
2695
  throw new Error("failed to record provider cleanup lease result");
2387
2696
  }
2697
+ if (deletingIdentity) {
2698
+ await this.invalidateManagedSandboxRuntimeAuthForDelete(trx, {
2699
+ sandboxIdentityId: deletingIdentity.sandbox_identity_id,
2700
+ identityDisabledAt: deletingIdentity.disabled_at,
2701
+ reason: redactReason(input.reason) ?? "managed sandbox deleted",
2702
+ now,
2703
+ });
2704
+ await trx
2705
+ .deleteFrom("managed_sandbox_instances")
2706
+ .where("instance_id", "=", current.instance_id)
2707
+ .where("owner_key", "=", current.owner_key)
2708
+ .where("host_id", "=", current.host_id)
2709
+ .where("status", "=", "deleting")
2710
+ .where("provider_resource_id", "is", null)
2711
+ .executeTakeFirst();
2712
+ }
2388
2713
  return {
2389
2714
  status: "recorded",
2390
2715
  lease: mapProvisioningLease(row),
@@ -2836,8 +3161,84 @@ export class ManagedSandboxInstancesStore extends ArtifactsEventsStore {
2836
3161
  if (nonterminalManagedInstances.count > 0) {
2837
3162
  blockers.push("nonterminal_managed_instances");
2838
3163
  }
3164
+ const retryableDeletingProviderInstances = await executor
3165
+ .selectFrom("managed_sandbox_instances")
3166
+ .select(sql `count(*)::int`.as("count"))
3167
+ .where("owner_key", "=", ownerKey)
3168
+ .where("host_id", "=", hostId)
3169
+ .where("status", "=", "deleting")
3170
+ .where("provider_resource_id", "is not", null)
3171
+ .executeTakeFirstOrThrow();
3172
+ if (retryableDeletingProviderInstances.count > 0 &&
3173
+ !blockers.includes("nonterminal_managed_instances")) {
3174
+ blockers.push("nonterminal_managed_instances");
3175
+ }
2839
3176
  return blockers;
2840
3177
  }
3178
+ async lockManagedSandboxLiveness(executor, sandboxId) {
3179
+ return executor
3180
+ .selectFrom("sandboxes")
3181
+ .select(["sandbox_id", "status"])
3182
+ .where("sandbox_id", "=", sandboxId)
3183
+ .forUpdate()
3184
+ .executeTakeFirst();
3185
+ }
3186
+ async managedSandboxHasAcceptedRunningAssignments(executor, sandboxId) {
3187
+ const row = await executor
3188
+ .selectFrom("tasks")
3189
+ .select("task_id")
3190
+ .where("status", "=", "RUNNING")
3191
+ .where("assigned_sandbox_id", "=", sandboxId)
3192
+ .where("assignment_accepted_at", "is not", null)
3193
+ .where("current_assignment_id", "is not", null)
3194
+ .forUpdate()
3195
+ .executeTakeFirst();
3196
+ return row !== undefined;
3197
+ }
3198
+ async clearReservedSandboxAssignmentsForDelete(executor, sandboxId) {
3199
+ const tasks = await executor
3200
+ .selectFrom("tasks")
3201
+ .select(["task_id", "run_id"])
3202
+ .where("status", "=", "RUNNING")
3203
+ .where("assigned_sandbox_id", "=", sandboxId)
3204
+ .where("assignment_accepted_at", "is", null)
3205
+ .where("current_assignment_id", "is not", null)
3206
+ .forUpdate()
3207
+ .execute();
3208
+ if (tasks.length === 0) {
3209
+ return 0;
3210
+ }
3211
+ const now = this.nowIso();
3212
+ const taskIds = tasks.map((task) => task.task_id);
3213
+ const cleared = await executor
3214
+ .updateTable("tasks")
3215
+ .set({
3216
+ status: "QUEUED",
3217
+ current_attempt_number: null,
3218
+ current_assignment_id: null,
3219
+ assigned_sandbox_id: null,
3220
+ assignment_created_at: null,
3221
+ assignment_sent_at: null,
3222
+ assignment_delivery_deadline_at: null,
3223
+ assignment_accepted_at: null,
3224
+ assignment_expires_at: null,
3225
+ assignment_heartbeat_at: null,
3226
+ assignment_delivery_attempt_count: 0,
3227
+ assignment_last_delivery_error: "managed_sandbox_deleted",
3228
+ not_before_at: null,
3229
+ updated_at: now,
3230
+ queued_at: now,
3231
+ })
3232
+ .where("task_id", "in", taskIds)
3233
+ .where("status", "=", "RUNNING")
3234
+ .where("assigned_sandbox_id", "=", sandboxId)
3235
+ .where("assignment_accepted_at", "is", null)
3236
+ .executeTakeFirst();
3237
+ for (const runId of new Set(tasks.map((task) => task.run_id))) {
3238
+ await this.updateRunState(executor, runId);
3239
+ }
3240
+ return Number(cleared.numUpdatedRows);
3241
+ }
2841
3242
  async requireHostPoolOwner(executor, hostPoolId, hostId, ownerKey) {
2842
3243
  const pool = await executor
2843
3244
  .selectFrom("host_pools")