deepline 0.3.89 → 0.3.90

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 (211) hide show
  1. package/contracts/install-commands.json +29 -0
  2. package/dist/bundling-sources/sdk/src/client.ts +120 -37
  3. package/dist/bundling-sources/sdk/src/index.ts +4 -0
  4. package/dist/bundling-sources/sdk/src/pagination.ts +25 -0
  5. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  6. package/dist/bundling-sources/sdk/src/release.ts +16 -9
  7. package/dist/bundling-sources/sdk/src/types.ts +23 -0
  8. package/dist/bundling-sources/shared_libs/monitors/analytics-public.ts +10 -0
  9. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +3 -0
  10. package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +4 -4
  11. package/dist/bundling-sources/shared_libs/monitors/inventory-reconciliation.ts +59 -0
  12. package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +7 -9
  13. package/dist/bundling-sources/shared_libs/monitors/package.json +7 -0
  14. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +44 -2
  15. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +2 -2
  16. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +2 -2
  17. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +1 -1
  18. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +245 -0
  19. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-message.ts +38 -0
  20. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-ack-probe.ts +94 -0
  21. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-delivery.ts +60 -0
  22. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue.ts +60 -0
  23. package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +1 -1
  24. package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +2 -2
  25. package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +1 -1
  26. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +1 -1
  27. package/dist/bundling-sources/shared_libs/observability/build-context.ts +13 -0
  28. package/dist/bundling-sources/shared_libs/observability/log-metadata.ts +160 -0
  29. package/dist/bundling-sources/shared_libs/observability/request-context.ts +68 -0
  30. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +654 -0
  31. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +35 -0
  32. package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +5 -0
  33. package/dist/bundling-sources/shared_libs/play-data-plane/neon-constants.ts +9 -0
  34. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +272 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +45 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +66 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +0 -1
  38. package/dist/bundling-sources/shared_libs/play-runtime/bounceban-batching.ts +137 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +779 -140
  40. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +46 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/daytona-payload-contract.ts +68 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +103 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +725 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +29 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +23 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +442 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +3 -3
  48. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +18 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +9 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +1 -1
  51. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +1 -1
  52. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +66 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +145 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/ports.ts +21 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/postgres-store.ts +170 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +1322 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +2 -2
  58. package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +1 -1
  59. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +112 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +8 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/perf-trace.ts +127 -0
  62. package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +5 -0
  63. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +24 -1
  64. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +56 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +13 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/job.ts +41 -0
  67. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/ports.ts +17 -0
  68. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +54 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/contracts.ts +114 -0
  70. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/engine-suspension.ts +108 -0
  71. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/index.ts +36 -0
  72. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/job.ts +97 -0
  73. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/ports.ts +104 -0
  74. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/postgres-fence-adapter.ts +263 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +64 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/contracts.ts +29 -0
  77. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +56 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/index.ts +15 -0
  79. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/job.ts +65 -0
  80. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/ports.ts +17 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +42 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/index.ts +17 -0
  83. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +142 -0
  84. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/ports.ts +42 -0
  85. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/postgres-adapter.ts +102 -0
  86. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +60 -0
  87. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/index.ts +16 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +78 -0
  89. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +54 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +71 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +4 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +5 -8
  94. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +3 -5
  95. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +168 -0
  96. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +72 -9
  97. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +6 -12
  98. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +19 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +0 -1
  100. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +14 -44
  101. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +57 -205
  102. package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +201 -0
  103. package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
  104. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +67 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/runtime-release-environment.ts +2 -0
  106. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-contract.ts +68 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +14 -2
  108. package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +108 -0
  109. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +4 -0
  110. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +3 -0
  111. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +7 -0
  112. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +88 -0
  113. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +124 -0
  114. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-run-task.ts +68 -0
  115. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +193 -0
  116. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-lifecycle.ts +269 -0
  117. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-suffix.ts +73 -0
  118. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +6642 -0
  119. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +897 -0
  120. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-metrics.ts +120 -0
  121. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +76 -0
  122. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +476 -0
  123. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +47 -0
  124. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +147 -0
  125. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/index.ts +27 -0
  126. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/lazy.ts +50 -0
  127. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts +98 -0
  128. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +260 -0
  129. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1416 -0
  130. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +1255 -0
  131. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +942 -0
  132. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +14636 -0
  133. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +888 -0
  134. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +355 -0
  135. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +46 -0
  136. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runner-push.ts +60 -0
  137. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-api-origin.ts +33 -0
  138. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-gateway-health.ts +64 -0
  139. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +835 -0
  140. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +1218 -0
  141. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-operation.ts +207 -0
  142. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-scheduler-schema.ts +10 -0
  143. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +123 -0
  144. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-launch-read-observability.ts +78 -0
  145. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +169 -0
  146. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/terminal-contract.ts +20 -0
  147. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-queue-metrics.ts +34 -0
  148. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +249 -0
  149. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +7 -0
  150. package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +296 -0
  151. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +3 -3
  152. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +20 -1
  153. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +60 -15
  154. package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +20 -0
  155. package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +12 -0
  156. package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +14 -1
  157. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +165 -0
  158. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +15 -23
  159. package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +169 -0
  160. package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +122 -0
  161. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +2 -0
  162. package/dist/bundling-sources/shared_libs/plays/contracts.ts +2 -0
  163. package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +1 -1
  164. package/dist/bundling-sources/shared_libs/plays/docflow.ts +1 -1
  165. package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +39 -0
  166. package/dist/bundling-sources/shared_libs/plays/share/copy.ts +271 -0
  167. package/dist/bundling-sources/shared_libs/plays/share/metrics.ts +140 -0
  168. package/dist/bundling-sources/shared_libs/plays/share/public-read-model.ts +86 -0
  169. package/dist/bundling-sources/shared_libs/plays/share/seo-quality.ts +181 -0
  170. package/dist/bundling-sources/shared_libs/plays/share/static-pipeline-summary.ts +658 -0
  171. package/dist/bundling-sources/shared_libs/plays/share/types.ts +197 -0
  172. package/dist/bundling-sources/shared_libs/plays/slack-hitl-timeout-error.ts +37 -0
  173. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +6 -1
  174. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
  175. package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +7 -1
  176. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +3 -2
  177. package/dist/bundling-sources/shared_libs/product-notifications/overflow.ts +18 -0
  178. package/dist/bundling-sources/shared_libs/product-notifications/rolling-limit.ts +24 -0
  179. package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +624 -0
  180. package/dist/bundling-sources/shared_libs/product-notifications/slack-payload.ts +140 -0
  181. package/dist/bundling-sources/shared_libs/product-notifications/slack-transport.ts +75 -0
  182. package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
  183. package/dist/bundling-sources/shared_libs/security/email-blacklist.ts +42 -0
  184. package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +10 -3
  185. package/dist/bundling-sources/shared_libs/security/slack-sanitize.ts +133 -0
  186. package/dist/cli/getting-started.d.mts +52 -0
  187. package/dist/cli/getting-started.d.ts +52 -0
  188. package/dist/cli/getting-started.js +65 -0
  189. package/dist/cli/getting-started.mjs +36 -0
  190. package/dist/cli/index.d.mts +9 -0
  191. package/dist/cli/index.d.ts +9 -0
  192. package/dist/cli/index.js +545 -264
  193. package/dist/cli/index.mjs +543 -271
  194. package/dist/{compiler-manifest-DsLbQaew.d.mts → compiler-manifest-BaJfE-5H.d.mts} +5 -3
  195. package/dist/{compiler-manifest-DsLbQaew.d.ts → compiler-manifest-BaJfE-5H.d.ts} +5 -3
  196. package/dist/index.d.mts +81 -148
  197. package/dist/index.d.ts +81 -148
  198. package/dist/index.js +83 -26
  199. package/dist/index.mjs +83 -26
  200. package/dist/install-integrity.json +130 -2
  201. package/dist/monitor-fleet-contract.d.mts +138 -0
  202. package/dist/monitor-fleet-contract.d.ts +138 -0
  203. package/dist/monitor-fleet-contract.js +404 -0
  204. package/dist/monitor-fleet-contract.mjs +367 -0
  205. package/dist/plays/bundle-play-file.d.mts +2 -2
  206. package/dist/plays/bundle-play-file.d.ts +2 -2
  207. package/dist/release.d.mts +255 -0
  208. package/dist/release.d.ts +255 -0
  209. package/dist/release.js +215 -0
  210. package/dist/release.mjs +188 -0
  211. package/package.json +21 -4
@@ -0,0 +1,835 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ withRuntimeSchedulerClient,
4
+ type RuntimeSchedulerPoolOptions,
5
+ } from './postgres-pool';
6
+ import type {
7
+ PostgresSchedulerOptions,
8
+ PostgresSchedulerQueryClient,
9
+ } from './postgres';
10
+ import { RuntimeSchedulerCapacityError } from './scheduler-capacity';
11
+ import { RUNTIME_SANDBOX_CAPACITY_LIMIT_ERROR_NAME } from '../runner-backends/types';
12
+ import {
13
+ RuntimeSandboxOperationState,
14
+ RuntimeSandboxProvider,
15
+ } from './runtime-sandbox-operation';
16
+
17
+ /** The safe baseline when an organization has no current override. */
18
+ export const DEFAULT_PLAY_SANDBOX_CONCURRENCY = 4;
19
+ export const MAX_PLAY_SANDBOX_CONCURRENCY = 250;
20
+ /** A crashed worker cannot retain capacity forever. */
21
+ export const RUNTIME_SANDBOX_CAPACITY_LEASE_MS = 50 * 60 * 1_000;
22
+
23
+ /**
24
+ * Ship the mirror and resource accounting before allowing it to defer work.
25
+ * This gives operators a safe seed/drain window for pre-existing sandboxes.
26
+ */
27
+ export type RuntimeSandboxCapacityAdmissionMode = 'observe' | 'enforce';
28
+
29
+ export function runtimeSandboxCapacityAdmissionMode(
30
+ env: Record<string, string | undefined> = process.env,
31
+ ): RuntimeSandboxCapacityAdmissionMode {
32
+ const value = env.DEEPLINE_RUNTIME_SANDBOX_CAPACITY_ADMISSION?.trim();
33
+ if (!value || value === 'observe') return 'observe';
34
+ if (value === 'enforce') return 'enforce';
35
+ throw new Error(
36
+ 'DEEPLINE_RUNTIME_SANDBOX_CAPACITY_ADMISSION must be observe or enforce.',
37
+ );
38
+ }
39
+
40
+ /** Compatibility boundary for existing callback implementations. New lifecycle
41
+ * code uses `RuntimeSandboxProvider`; this accepts the equivalent serialized
42
+ * provider id without widening to arbitrary text. */
43
+ export type RuntimeSandboxProviderId = `${RuntimeSandboxProvider}`;
44
+
45
+ /** Stable <=63-character Daytona label for a durable operation key. */
46
+ export function runtimeSandboxProviderOperationToken(
47
+ operationKey: string,
48
+ ): string {
49
+ const key = assertId(operationKey, 'operationKey');
50
+ return `dlop-${createHash('sha256').update(key).digest('base64url')}`;
51
+ }
52
+
53
+ function quoteIdent(value: string): string {
54
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
55
+ throw new Error(`Invalid runtime scheduler schema: ${value}`);
56
+ }
57
+ return `"${value}"`;
58
+ }
59
+
60
+ function schemaTables(options?: Pick<PostgresSchedulerOptions, 'schema'>) {
61
+ const schema = quoteIdent(options?.schema ?? 'runtime_scheduler');
62
+ return {
63
+ cache: `${schema}."runtime_org_sandbox_limit_cache"`,
64
+ leases: `${schema}."runtime_sandbox_capacity_leases"`,
65
+ operations: `${schema}."runtime_sandbox_operations"`,
66
+ cleanup: `${schema}."sandbox_cleanup_jobs_v2"`,
67
+ };
68
+ }
69
+
70
+ function assertId(value: string, name: string): string {
71
+ const normalized = value.trim();
72
+ if (!normalized || normalized.length > 256) {
73
+ throw new Error(`${name} is required and must be at most 256 characters.`);
74
+ }
75
+ return normalized;
76
+ }
77
+
78
+ function assertLimit(value: number): number {
79
+ if (
80
+ !Number.isSafeInteger(value) ||
81
+ value < 1 ||
82
+ value > MAX_PLAY_SANDBOX_CONCURRENCY
83
+ ) {
84
+ throw new Error(
85
+ `Sandbox concurrency must be an integer from 1 through ${MAX_PLAY_SANDBOX_CONCURRENCY}.`,
86
+ );
87
+ }
88
+ return value;
89
+ }
90
+
91
+ export class RuntimeSandboxCapacityLimitError extends RuntimeSchedulerCapacityError {
92
+ readonly orgId: string;
93
+ readonly limit: number;
94
+ readonly activeLeases: number;
95
+
96
+ constructor(input: { orgId: string; limit: number; activeLeases: number }) {
97
+ super(
98
+ `Runtime scheduler sandbox capacity is full for organization ${input.orgId} (${input.activeLeases}/${input.limit} active leases).`,
99
+ 15_000,
100
+ );
101
+ this.name = RUNTIME_SANDBOX_CAPACITY_LIMIT_ERROR_NAME;
102
+ this.orgId = input.orgId;
103
+ this.limit = input.limit;
104
+ this.activeLeases = input.activeLeases;
105
+ }
106
+ }
107
+
108
+ export class RuntimeSandboxOperationNotRetryableError extends Error {
109
+ constructor(readonly state: RuntimeSandboxOperationState) {
110
+ super(`Sandbox operation is ${state}; it cannot resume provider create.`);
111
+ this.name = 'RuntimeSandboxOperationNotRetryableError';
112
+ }
113
+ }
114
+
115
+ export async function syncRuntimeOrgSandboxLimitCache(
116
+ client: PostgresSchedulerQueryClient,
117
+ input: {
118
+ orgId: string;
119
+ configuredLimit: number | null;
120
+ expiresAt: number | null;
121
+ sourceRevision: number;
122
+ },
123
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
124
+ ): Promise<{ syncedAtMs: number; effectiveLimit: number }> {
125
+ const orgId = assertId(input.orgId, 'orgId');
126
+ const limit =
127
+ input.configuredLimit === null
128
+ ? DEFAULT_PLAY_SANDBOX_CONCURRENCY
129
+ : assertLimit(input.configuredLimit);
130
+ if (!Number.isSafeInteger(input.sourceRevision) || input.sourceRevision < 0) {
131
+ throw new Error('sourceRevision must be a non-negative safe integer.');
132
+ }
133
+ if (
134
+ input.expiresAt !== null &&
135
+ (!Number.isFinite(input.expiresAt) || input.expiresAt <= 0)
136
+ ) {
137
+ throw new Error(
138
+ 'expiresAt must be a positive epoch milliseconds value or null.',
139
+ );
140
+ }
141
+ const { cache } = schemaTables(options);
142
+ const result = await client.query<{ synced_at_ms: number }>(
143
+ `
144
+ INSERT INTO ${cache} (
145
+ org_id, "limit", expires_at, source_revision, synced_at
146
+ )
147
+ VALUES (
148
+ $1,
149
+ $2,
150
+ CASE WHEN $3::double precision IS NULL THEN NULL
151
+ ELSE to_timestamp($3::double precision / 1000.0) END,
152
+ $4,
153
+ clock_timestamp()
154
+ )
155
+ ON CONFLICT (org_id) DO UPDATE
156
+ SET "limit" = EXCLUDED."limit",
157
+ expires_at = EXCLUDED.expires_at,
158
+ source_revision = EXCLUDED.source_revision,
159
+ synced_at = clock_timestamp()
160
+ WHERE ${cache}.source_revision <= EXCLUDED.source_revision
161
+ RETURNING floor(extract(epoch FROM synced_at) * 1000)::bigint AS synced_at_ms
162
+ `,
163
+ [orgId, limit, input.expiresAt, input.sourceRevision],
164
+ );
165
+ return {
166
+ // A stale reconciler write is intentionally a no-op; the newer cache row
167
+ // remains the source used by workers.
168
+ syncedAtMs: Number(result.rows[0]?.synced_at_ms ?? Date.now()),
169
+ effectiveLimit: limit,
170
+ };
171
+ }
172
+
173
+ export async function syncRuntimeOrgSandboxLimitCacheViaRuntimeScheduler(input: {
174
+ poolOptions: RuntimeSchedulerPoolOptions;
175
+ orgId: string;
176
+ configuredLimit: number | null;
177
+ expiresAt: number | null;
178
+ sourceRevision: number;
179
+ schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
180
+ }): Promise<{ syncedAtMs: number; effectiveLimit: number }> {
181
+ return await withRuntimeSchedulerClient(input.poolOptions, (client) =>
182
+ syncRuntimeOrgSandboxLimitCache(client, input, input.schedulerOptions),
183
+ );
184
+ }
185
+
186
+ export type RuntimeOrgSandboxLimitCacheRow = {
187
+ limit: number;
188
+ expiresAt: number | null;
189
+ sourceRevision: number;
190
+ syncedAtMs: number;
191
+ };
192
+
193
+ export async function readRuntimeOrgSandboxLimitCache(
194
+ client: PostgresSchedulerQueryClient,
195
+ input: { orgId: string },
196
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
197
+ ): Promise<RuntimeOrgSandboxLimitCacheRow | null> {
198
+ const { cache } = schemaTables(options);
199
+ const result = await client.query<{
200
+ limit: number;
201
+ expires_at_ms: number | null;
202
+ source_revision: number;
203
+ synced_at_ms: number;
204
+ }>(
205
+ `
206
+ SELECT
207
+ "limit",
208
+ CASE WHEN expires_at IS NULL THEN NULL
209
+ ELSE floor(extract(epoch FROM expires_at) * 1000)::bigint END
210
+ AS expires_at_ms,
211
+ source_revision,
212
+ floor(extract(epoch FROM synced_at) * 1000)::bigint AS synced_at_ms
213
+ FROM ${cache}
214
+ WHERE org_id = $1
215
+ `,
216
+ [assertId(input.orgId, 'orgId')],
217
+ );
218
+ const row = result.rows[0];
219
+ return row
220
+ ? {
221
+ limit: Number(row.limit),
222
+ expiresAt:
223
+ row.expires_at_ms === null ? null : Number(row.expires_at_ms),
224
+ sourceRevision: Number(row.source_revision),
225
+ syncedAtMs: Number(row.synced_at_ms),
226
+ }
227
+ : null;
228
+ }
229
+
230
+ export async function readRuntimeOrgSandboxLimitCacheViaRuntimeScheduler(input: {
231
+ poolOptions: RuntimeSchedulerPoolOptions;
232
+ orgId: string;
233
+ schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
234
+ }): Promise<RuntimeOrgSandboxLimitCacheRow | null> {
235
+ return await withRuntimeSchedulerClient(input.poolOptions, (client) =>
236
+ readRuntimeOrgSandboxLimitCache(client, input, input.schedulerOptions),
237
+ );
238
+ }
239
+
240
+ export async function reserveRuntimeSandboxCapacityLease(
241
+ client: PostgresSchedulerQueryClient,
242
+ input: {
243
+ orgId: string;
244
+ runId: string;
245
+ provider: RuntimeSandboxProviderId;
246
+ leaseId?: string;
247
+ /** Required by current workers: the operation intent precedes provider I/O. */
248
+ attempt?: number;
249
+ environment?: 'preview' | 'production';
250
+ mode?: RuntimeSandboxCapacityAdmissionMode;
251
+ /** Resume the one unresolved Daytona create for this run, if present. */
252
+ resumePendingDaytonaOperation?: boolean;
253
+ },
254
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
255
+ ): Promise<
256
+ | {
257
+ granted: true;
258
+ leaseId: string;
259
+ providerOperationToken: string | null;
260
+ limit: number;
261
+ activeLeases: number;
262
+ wouldDefer: boolean;
263
+ resumeExistingProviderOperation: boolean;
264
+ }
265
+ | { granted: false; limit: number; activeLeases: number }
266
+ > {
267
+ const orgId = assertId(input.orgId, 'orgId');
268
+ const runId = assertId(input.runId, 'runId');
269
+ const { cache, leases, operations } = schemaTables(options);
270
+ // This is deliberately a soft admission check: concurrent workers may both
271
+ // observe space and insert. Avoid locks and long transactions; the provider
272
+ // has its own global safety cap and the 50-minute lease bounds crashes.
273
+ const observed = await client.query<{ limit: number; active_leases: number }>(
274
+ `
275
+ SELECT
276
+ coalesce(
277
+ (
278
+ SELECT CASE
279
+ WHEN expires_at IS NOT NULL AND expires_at <= clock_timestamp()
280
+ THEN $2::integer
281
+ ELSE "limit"
282
+ END
283
+ FROM ${cache}
284
+ WHERE org_id = $1
285
+ ),
286
+ $2::integer
287
+ ) AS "limit",
288
+ (
289
+ SELECT count(*)::integer
290
+ FROM ${leases}
291
+ WHERE org_id = $1
292
+ AND released_at IS NULL
293
+ -- A bound lease represents a provider resource that must remain
294
+ -- counted until provider-confirmed deletion. The 50-minute TTL
295
+ -- is only the crash backstop for a create that never became
296
+ -- durable enough to bind to a sandbox id.
297
+ AND (sandbox_id IS NOT NULL OR expires_at > clock_timestamp())
298
+ ) AS active_leases
299
+ `,
300
+ [orgId, DEFAULT_PLAY_SANDBOX_CONCURRENCY],
301
+ );
302
+ const limit = Number(
303
+ observed.rows[0]?.limit ?? DEFAULT_PLAY_SANDBOX_CONCURRENCY,
304
+ );
305
+ const activeLeases = Number(observed.rows[0]?.active_leases ?? 0);
306
+ const wouldDefer = activeLeases >= limit;
307
+ const leaseId = input.leaseId?.trim() || randomUUID();
308
+ const providerOperationToken =
309
+ input.attempt === undefined
310
+ ? null
311
+ : runtimeSandboxProviderOperationToken(leaseId);
312
+ if (
313
+ input.resumePendingDaytonaOperation === true &&
314
+ input.provider === RuntimeSandboxProvider.Daytona &&
315
+ input.attempt !== undefined
316
+ ) {
317
+ const pending = await client.query<{
318
+ lease_id: string;
319
+ provider_operation_token: string | null;
320
+ attempt: number;
321
+ environment: 'preview' | 'production';
322
+ }>(
323
+ `SELECT lease.lease_id, operation.provider_operation_token,
324
+ operation.attempt, operation.environment
325
+ FROM ${operations} AS operation
326
+ JOIN ${leases} AS lease ON lease.lease_id = operation.operation_key
327
+ WHERE operation.org_id = $1
328
+ AND operation.run_id = $2
329
+ AND operation.provider = $3
330
+ AND operation.state = $4
331
+ AND lease.released_at IS NULL
332
+ AND lease.sandbox_id IS NULL
333
+ AND lease.expires_at > clock_timestamp()
334
+ ORDER BY operation.created_at ASC
335
+ LIMIT 2`,
336
+ [orgId, runId, input.provider, RuntimeSandboxOperationState.Reserved],
337
+ );
338
+ if (pending.rows.length > 1) {
339
+ throw new Error(
340
+ 'More than one Daytona sandbox operation is pending for this run.',
341
+ );
342
+ }
343
+ const pendingOperation = pending.rows[0];
344
+ if (pendingOperation) {
345
+ const pendingToken =
346
+ pendingOperation.provider_operation_token ??
347
+ (await backfillReservedProviderOperationToken(
348
+ client,
349
+ {
350
+ leaseId: pendingOperation.lease_id,
351
+ orgId,
352
+ runId,
353
+ provider: input.provider,
354
+ attempt: pendingOperation.attempt,
355
+ environment: pendingOperation.environment,
356
+ providerOperationToken: runtimeSandboxProviderOperationToken(
357
+ pendingOperation.lease_id,
358
+ ),
359
+ },
360
+ options,
361
+ ));
362
+ return {
363
+ granted: true,
364
+ leaseId: pendingOperation.lease_id,
365
+ providerOperationToken: pendingToken,
366
+ limit,
367
+ activeLeases,
368
+ wouldDefer,
369
+ resumeExistingProviderOperation: true,
370
+ };
371
+ }
372
+ }
373
+ const existing = await client.query<{
374
+ org_id: string;
375
+ run_id: string;
376
+ provider: RuntimeSandboxProviderId;
377
+ operation_attempt: number | null;
378
+ operation_environment: 'preview' | 'production' | null;
379
+ operation_provider: RuntimeSandboxProviderId | null;
380
+ provider_operation_token: string | null;
381
+ operation_state: RuntimeSandboxOperationState | null;
382
+ released_at: Date | null;
383
+ sandbox_id: string | null;
384
+ }>(
385
+ `SELECT lease.org_id, lease.run_id, lease.provider,
386
+ operation.attempt AS operation_attempt,
387
+ operation.environment AS operation_environment,
388
+ operation.provider AS operation_provider,
389
+ operation.provider_operation_token,
390
+ operation.state AS operation_state,
391
+ lease.released_at, lease.sandbox_id
392
+ FROM ${leases} AS lease
393
+ LEFT JOIN ${operations} AS operation
394
+ ON operation.operation_key = lease.lease_id
395
+ WHERE lease.lease_id = $1`,
396
+ [leaseId],
397
+ );
398
+ if (existing.rows[0]) {
399
+ const row = existing.rows[0];
400
+ if (
401
+ row.org_id !== orgId ||
402
+ row.run_id !== runId ||
403
+ row.provider !== input.provider ||
404
+ (input.attempt !== undefined &&
405
+ (row.operation_attempt !== input.attempt ||
406
+ row.operation_environment !== (input.environment ?? 'production') ||
407
+ row.operation_provider !== input.provider))
408
+ ) {
409
+ throw new Error(
410
+ 'operationKey is already owned by another sandbox operation.',
411
+ );
412
+ }
413
+ if (
414
+ input.attempt !== undefined &&
415
+ (row.operation_state !== RuntimeSandboxOperationState.Reserved ||
416
+ row.released_at !== null ||
417
+ row.sandbox_id !== null)
418
+ ) {
419
+ throw new RuntimeSandboxOperationNotRetryableError(
420
+ row.operation_state ?? RuntimeSandboxOperationState.Abandoned,
421
+ );
422
+ }
423
+ if (
424
+ input.attempt !== undefined &&
425
+ row.provider_operation_token !== null &&
426
+ row.provider_operation_token !== providerOperationToken
427
+ ) {
428
+ throw new Error(
429
+ 'operationKey reservation has an incompatible provider operation token.',
430
+ );
431
+ }
432
+ let resolvedProviderOperationToken = row.provider_operation_token;
433
+ if (
434
+ input.attempt !== undefined &&
435
+ resolvedProviderOperationToken === null
436
+ ) {
437
+ if (providerOperationToken === null) {
438
+ throw new Error(
439
+ 'Current sandbox operation reservation is missing its provider operation token.',
440
+ );
441
+ }
442
+ resolvedProviderOperationToken =
443
+ await backfillReservedProviderOperationToken(
444
+ client,
445
+ {
446
+ leaseId,
447
+ orgId,
448
+ runId,
449
+ provider: input.provider,
450
+ attempt: input.attempt,
451
+ environment: input.environment ?? 'production',
452
+ providerOperationToken,
453
+ },
454
+ options,
455
+ );
456
+ }
457
+ return {
458
+ granted: true,
459
+ leaseId,
460
+ providerOperationToken: resolvedProviderOperationToken,
461
+ limit,
462
+ activeLeases,
463
+ wouldDefer,
464
+ resumeExistingProviderOperation: false,
465
+ };
466
+ }
467
+ if (wouldDefer && (input.mode ?? 'enforce') === 'enforce') {
468
+ return { granted: false, limit, activeLeases };
469
+ }
470
+ await client.query(
471
+ `
472
+ WITH reserved_lease AS (
473
+ INSERT INTO ${leases} (
474
+ lease_id, org_id, run_id, provider, expires_at
475
+ ) VALUES (
476
+ $1, $2, $3, $4,
477
+ clock_timestamp() + ($5::integer * interval '1 millisecond')
478
+ )
479
+ ON CONFLICT (lease_id) DO NOTHING
480
+ RETURNING lease_id
481
+ )
482
+ INSERT INTO ${operations} (
483
+ operation_key, provider_operation_token, provider, run_id, attempt, org_id, environment, state
484
+ )
485
+ SELECT
486
+ reserved_lease.lease_id, $9, $4, $3, $6, $2, $7, $8
487
+ FROM reserved_lease
488
+ WHERE $6::integer IS NOT NULL
489
+ ON CONFLICT (operation_key) DO NOTHING
490
+ `,
491
+ [
492
+ leaseId,
493
+ orgId,
494
+ runId,
495
+ input.provider,
496
+ RUNTIME_SANDBOX_CAPACITY_LEASE_MS,
497
+ input.attempt ?? null,
498
+ input.environment ?? 'production',
499
+ RuntimeSandboxOperationState.Reserved,
500
+ providerOperationToken,
501
+ ],
502
+ );
503
+ // The caller may have lost the INSERT response after PostgreSQL committed,
504
+ // or raced another identical retry. A second read is mandatory: conflict
505
+ // swallowing alone would manufacture a lease without proving whose intent
506
+ // it resumes.
507
+ const persisted = await client.query<{
508
+ org_id: string;
509
+ run_id: string;
510
+ provider: RuntimeSandboxProviderId;
511
+ operation_attempt: number | null;
512
+ operation_environment: 'preview' | 'production' | null;
513
+ provider_operation_token: string | null;
514
+ }>(
515
+ `SELECT lease.org_id, lease.run_id, lease.provider,
516
+ operation.attempt AS operation_attempt,
517
+ operation.environment AS operation_environment,
518
+ operation.provider_operation_token
519
+ FROM ${leases} AS lease
520
+ LEFT JOIN ${operations} AS operation
521
+ ON operation.operation_key = lease.lease_id
522
+ WHERE lease.lease_id = $1`,
523
+ [leaseId],
524
+ );
525
+ const row = persisted.rows[0];
526
+ if (
527
+ !row ||
528
+ row.org_id !== orgId ||
529
+ row.run_id !== runId ||
530
+ row.provider !== input.provider ||
531
+ (input.attempt !== undefined &&
532
+ (row.operation_attempt !== input.attempt ||
533
+ row.operation_environment !== (input.environment ?? 'production')))
534
+ ) {
535
+ throw new Error(
536
+ 'operationKey reservation did not persist the requested sandbox operation.',
537
+ );
538
+ }
539
+ return {
540
+ granted: true,
541
+ leaseId,
542
+ providerOperationToken: row.provider_operation_token,
543
+ limit,
544
+ activeLeases,
545
+ wouldDefer,
546
+ resumeExistingProviderOperation: false,
547
+ };
548
+ }
549
+
550
+ async function backfillReservedProviderOperationToken(
551
+ client: PostgresSchedulerQueryClient,
552
+ input: {
553
+ leaseId: string;
554
+ orgId: string;
555
+ runId: string;
556
+ provider: RuntimeSandboxProviderId;
557
+ attempt: number;
558
+ environment: 'preview' | 'production';
559
+ providerOperationToken: string;
560
+ },
561
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
562
+ ): Promise<string> {
563
+ const { leases, operations } = schemaTables(options);
564
+ const restored = await client.query<{ provider_operation_token: string }>(
565
+ `
566
+ UPDATE ${operations} AS operation
567
+ SET provider_operation_token = $2,
568
+ updated_at = clock_timestamp()
569
+ FROM ${leases} AS lease
570
+ WHERE operation.operation_key = $1
571
+ AND operation.provider = $3
572
+ AND operation.run_id = $4
573
+ AND operation.attempt = $5
574
+ AND operation.org_id = $6
575
+ AND operation.environment = $7
576
+ AND operation.state = $8
577
+ AND operation.provider_operation_token IS NULL
578
+ AND lease.lease_id = operation.operation_key
579
+ AND lease.released_at IS NULL
580
+ AND lease.sandbox_id IS NULL
581
+ AND lease.expires_at > clock_timestamp()
582
+ RETURNING operation.provider_operation_token
583
+ `,
584
+ [
585
+ input.leaseId,
586
+ input.providerOperationToken,
587
+ input.provider,
588
+ input.runId,
589
+ input.attempt,
590
+ input.orgId,
591
+ input.environment,
592
+ RuntimeSandboxOperationState.Reserved,
593
+ ],
594
+ );
595
+ const token = restored.rows[0]?.provider_operation_token;
596
+ if (token !== input.providerOperationToken) {
597
+ throw new Error(
598
+ 'Reserved sandbox operation could not be atomically backfilled with its provider operation token.',
599
+ );
600
+ }
601
+ return token;
602
+ }
603
+
604
+ /**
605
+ * Strict operation-first admission API for current managed sandbox workers.
606
+ * The caller owns the deterministic key (run + attempt + provider create
607
+ * attempt), so retrying after an ambiguous process death resumes one durable
608
+ * operation instead of manufacturing another reservation.
609
+ */
610
+ export async function reserveRuntimeSandboxOperation(
611
+ client: PostgresSchedulerQueryClient,
612
+ input: {
613
+ operationKey: string;
614
+ orgId: string;
615
+ runId: string;
616
+ attempt: number;
617
+ provider: RuntimeSandboxProviderId;
618
+ environment: 'preview' | 'production';
619
+ mode?: RuntimeSandboxCapacityAdmissionMode;
620
+ resumePendingDaytonaOperation?: boolean;
621
+ },
622
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
623
+ ): ReturnType<typeof reserveRuntimeSandboxCapacityLease> {
624
+ if (!Number.isSafeInteger(input.attempt) || input.attempt < 1) {
625
+ throw new Error('attempt must be a positive safe integer.');
626
+ }
627
+ return reserveRuntimeSandboxCapacityLease(
628
+ client,
629
+ {
630
+ orgId: input.orgId,
631
+ runId: input.runId,
632
+ provider: input.provider,
633
+ leaseId: assertId(input.operationKey, 'operationKey'),
634
+ attempt: input.attempt,
635
+ environment: input.environment,
636
+ mode: input.mode,
637
+ resumePendingDaytonaOperation: input.resumePendingDaytonaOperation,
638
+ },
639
+ options,
640
+ );
641
+ }
642
+
643
+ export async function bindRuntimeSandboxCapacityLease(
644
+ client: PostgresSchedulerQueryClient,
645
+ input: {
646
+ leaseId: string;
647
+ provider: RuntimeSandboxProviderId;
648
+ sandboxId: string;
649
+ /** Provider routing identity is part of a physical sandbox identity. */
650
+ routingDomain: string | null;
651
+ },
652
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
653
+ ): Promise<boolean> {
654
+ const { leases } = schemaTables(options);
655
+ const result = await client.query<{ lease_id: string }>(
656
+ `
657
+ UPDATE ${leases}
658
+ SET provider = $2, sandbox_id = $3, routing_domain = $4
659
+ WHERE lease_id = $1 AND released_at IS NULL AND expires_at > clock_timestamp()
660
+ RETURNING lease_id
661
+ `,
662
+ [
663
+ assertId(input.leaseId, 'leaseId'),
664
+ input.provider,
665
+ assertId(input.sandboxId, 'sandboxId'),
666
+ input.routingDomain?.trim() || null,
667
+ ],
668
+ );
669
+ return result.rows.length === 1;
670
+ }
671
+
672
+ export async function releaseRuntimeSandboxCapacityLease(
673
+ client: PostgresSchedulerQueryClient,
674
+ input: { leaseId: string },
675
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
676
+ ): Promise<boolean> {
677
+ const { leases } = schemaTables(options);
678
+ const result = await client.query<{ lease_id: string }>(
679
+ `
680
+ UPDATE ${leases}
681
+ SET released_at = clock_timestamp()
682
+ WHERE lease_id = $1 AND released_at IS NULL
683
+ RETURNING lease_id
684
+ `,
685
+ [assertId(input.leaseId, 'leaseId')],
686
+ );
687
+ return result.rows.length === 1;
688
+ }
689
+
690
+ /**
691
+ * Confirmed provider rejection/no-resource outcome. This is deliberately not
692
+ * the generic release helper: a reserved operation must become terminal so a
693
+ * late callback cannot accidentally promote a known rejected create.
694
+ */
695
+ export async function abandonRuntimeSandboxOperationReservation(
696
+ client: PostgresSchedulerQueryClient,
697
+ input: { operationKey: string },
698
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
699
+ ): Promise<boolean> {
700
+ const { leases, operations } = schemaTables(options);
701
+ const result = await client.query<{ operation_key: string }>(
702
+ `
703
+ WITH abandoned AS (
704
+ UPDATE ${operations} AS operation
705
+ SET state = $2,
706
+ updated_at = clock_timestamp()
707
+ WHERE operation.operation_key = $1
708
+ AND operation.state = $3
709
+ RETURNING operation.operation_key
710
+ ), released AS (
711
+ UPDATE ${leases} AS lease
712
+ SET released_at = clock_timestamp()
713
+ FROM abandoned
714
+ WHERE lease.lease_id = abandoned.operation_key
715
+ AND lease.released_at IS NULL
716
+ RETURNING lease.lease_id
717
+ )
718
+ SELECT operation_key FROM abandoned
719
+ `,
720
+ [
721
+ assertId(input.operationKey, 'operationKey'),
722
+ RuntimeSandboxOperationState.Abandoned,
723
+ RuntimeSandboxOperationState.Reserved,
724
+ ],
725
+ );
726
+ return result.rows.length === 1;
727
+ }
728
+
729
+ /** Release only after the provider confirms deletion (or already absent). */
730
+ export async function releaseRuntimeSandboxCapacityLeaseForSandbox(
731
+ client: PostgresSchedulerQueryClient,
732
+ input: {
733
+ runId: string;
734
+ provider: RuntimeSandboxProviderId;
735
+ sandboxId: string;
736
+ routingDomain: string | null;
737
+ },
738
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
739
+ ): Promise<number> {
740
+ const { leases } = schemaTables(options);
741
+ const result = await client.query<{ lease_id: string }>(
742
+ `
743
+ UPDATE ${leases}
744
+ SET released_at = clock_timestamp()
745
+ WHERE run_id = $1 AND provider = $2 AND sandbox_id = $3
746
+ AND routing_domain IS NOT DISTINCT FROM $4
747
+ AND released_at IS NULL
748
+ RETURNING lease_id
749
+ `,
750
+ [
751
+ assertId(input.runId, 'runId'),
752
+ input.provider,
753
+ assertId(input.sandboxId, 'sandboxId'),
754
+ input.routingDomain?.trim() || null,
755
+ ],
756
+ );
757
+ return result.rows.length;
758
+ }
759
+
760
+ export type RuntimeSandboxCapacitySummary = {
761
+ provider: RuntimeSandboxProviderId;
762
+ activeSandboxes: number;
763
+ pendingCleanup: number;
764
+ deletingCleanup: number;
765
+ oldestActiveAgeMs: number | null;
766
+ };
767
+
768
+ /**
769
+ * Read-only soft-lease occupancy diagnostic. This is intentionally not the
770
+ * durable live-sandbox operator view: an expired crash-fallback lease does
771
+ * not prove that the provider resource was deleted.
772
+ */
773
+ export async function readRuntimeSandboxCapacitySummary(
774
+ client: PostgresSchedulerQueryClient,
775
+ input: { provider: RuntimeSandboxProviderId },
776
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
777
+ ): Promise<RuntimeSandboxCapacitySummary> {
778
+ const { leases, cleanup } = schemaTables(options);
779
+ const result = await client.query<{
780
+ active_sandboxes: number;
781
+ pending_cleanup: number;
782
+ deleting_cleanup: number;
783
+ oldest_active_age_ms: number | null;
784
+ }>(
785
+ `
786
+ SELECT
787
+ (
788
+ SELECT count(*)::integer FROM ${leases}
789
+ WHERE provider = $1 AND sandbox_id IS NOT NULL
790
+ AND released_at IS NULL
791
+ ) AS active_sandboxes,
792
+ (
793
+ SELECT count(*)::integer FROM ${cleanup}
794
+ WHERE provider = $1 AND status = 'pending'
795
+ ) AS pending_cleanup,
796
+ (
797
+ SELECT count(*)::integer FROM ${cleanup}
798
+ WHERE provider = $1 AND status = 'deleting'
799
+ ) AS deleting_cleanup,
800
+ (
801
+ SELECT floor(extract(epoch FROM (clock_timestamp() - min(created_at))) * 1000)::bigint
802
+ FROM ${leases}
803
+ WHERE provider = $1 AND sandbox_id IS NOT NULL
804
+ AND released_at IS NULL
805
+ ) AS oldest_active_age_ms
806
+ `,
807
+ [input.provider],
808
+ );
809
+ const row = result.rows[0];
810
+ return {
811
+ provider: input.provider,
812
+ activeSandboxes: Number(row?.active_sandboxes ?? 0),
813
+ pendingCleanup: Number(row?.pending_cleanup ?? 0),
814
+ deletingCleanup: Number(row?.deleting_cleanup ?? 0),
815
+ oldestActiveAgeMs:
816
+ row?.oldest_active_age_ms === null ||
817
+ row?.oldest_active_age_ms === undefined
818
+ ? null
819
+ : Number(row.oldest_active_age_ms),
820
+ };
821
+ }
822
+
823
+ export async function readRuntimeSandboxCapacitySummaryViaRuntimeScheduler(input: {
824
+ poolOptions: RuntimeSchedulerPoolOptions;
825
+ provider: RuntimeSandboxProviderId;
826
+ schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
827
+ }): Promise<RuntimeSandboxCapacitySummary> {
828
+ return await withRuntimeSchedulerClient(input.poolOptions, (client) =>
829
+ readRuntimeSandboxCapacitySummary(
830
+ client,
831
+ { provider: input.provider },
832
+ input.schedulerOptions,
833
+ ),
834
+ );
835
+ }