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.
- package/contracts/install-commands.json +29 -0
- package/dist/bundling-sources/sdk/src/client.ts +120 -37
- package/dist/bundling-sources/sdk/src/index.ts +4 -0
- package/dist/bundling-sources/sdk/src/pagination.ts +25 -0
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +16 -9
- package/dist/bundling-sources/sdk/src/types.ts +23 -0
- package/dist/bundling-sources/shared_libs/monitors/analytics-public.ts +10 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +3 -0
- package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +4 -4
- package/dist/bundling-sources/shared_libs/monitors/inventory-reconciliation.ts +59 -0
- package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +7 -9
- package/dist/bundling-sources/shared_libs/monitors/package.json +7 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +44 -2
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +2 -2
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +2 -2
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +245 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-message.ts +38 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-ack-probe.ts +94 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-delivery.ts +60 -0
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue.ts +60 -0
- package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +2 -2
- package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/build-context.ts +13 -0
- package/dist/bundling-sources/shared_libs/observability/log-metadata.ts +160 -0
- package/dist/bundling-sources/shared_libs/observability/request-context.ts +68 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +654 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +35 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/neon-constants.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +272 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +45 -0
- package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/bounceban-batching.ts +137 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +779 -140
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +46 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-payload-contract.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +725 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +442 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +3 -3
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +18 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +66 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +145 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/ports.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/postgres-store.ts +170 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +1322 -0
- package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +112 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/perf-trace.ts +127 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +24 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +13 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/job.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/ports.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/contracts.ts +114 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/engine-suspension.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/index.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/job.ts +97 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/ports.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/postgres-fence-adapter.ts +263 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/contracts.ts +29 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/index.ts +15 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/job.ts +65 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/ports.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/index.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +142 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/ports.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/postgres-adapter.ts +102 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +60 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/index.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +5 -8
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +3 -5
- package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +168 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +72 -9
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +6 -12
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +14 -44
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +57 -205
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +201 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +67 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-release-environment.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-contract.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +14 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +124 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-run-task.ts +68 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-lifecycle.ts +269 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-suffix.ts +73 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +6642 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +897 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-metrics.ts +120 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +76 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +476 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +147 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/index.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/lazy.ts +50 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts +98 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +260 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1416 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +1255 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +942 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +14636 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +888 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +355 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +46 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runner-push.ts +60 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-api-origin.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-gateway-health.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +835 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +1218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-operation.ts +207 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-scheduler-schema.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +123 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-launch-read-observability.ts +78 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +169 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/terminal-contract.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-queue-metrics.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +249 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +296 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +3 -3
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +20 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +60 -15
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +20 -0
- package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +14 -1
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +165 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +15 -23
- package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +169 -0
- package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +122 -0
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +39 -0
- package/dist/bundling-sources/shared_libs/plays/share/copy.ts +271 -0
- package/dist/bundling-sources/shared_libs/plays/share/metrics.ts +140 -0
- package/dist/bundling-sources/shared_libs/plays/share/public-read-model.ts +86 -0
- package/dist/bundling-sources/shared_libs/plays/share/seo-quality.ts +181 -0
- package/dist/bundling-sources/shared_libs/plays/share/static-pipeline-summary.ts +658 -0
- package/dist/bundling-sources/shared_libs/plays/share/types.ts +197 -0
- package/dist/bundling-sources/shared_libs/plays/slack-hitl-timeout-error.ts +37 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +6 -1
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
- package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +7 -1
- package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +3 -2
- package/dist/bundling-sources/shared_libs/product-notifications/overflow.ts +18 -0
- package/dist/bundling-sources/shared_libs/product-notifications/rolling-limit.ts +24 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +624 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-payload.ts +140 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-transport.ts +75 -0
- package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
- package/dist/bundling-sources/shared_libs/security/email-blacklist.ts +42 -0
- package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +10 -3
- package/dist/bundling-sources/shared_libs/security/slack-sanitize.ts +133 -0
- package/dist/cli/getting-started.d.mts +52 -0
- package/dist/cli/getting-started.d.ts +52 -0
- package/dist/cli/getting-started.js +65 -0
- package/dist/cli/getting-started.mjs +36 -0
- package/dist/cli/index.d.mts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.js +545 -264
- package/dist/cli/index.mjs +543 -271
- package/dist/{compiler-manifest-DsLbQaew.d.mts → compiler-manifest-BaJfE-5H.d.mts} +5 -3
- package/dist/{compiler-manifest-DsLbQaew.d.ts → compiler-manifest-BaJfE-5H.d.ts} +5 -3
- package/dist/index.d.mts +81 -148
- package/dist/index.d.ts +81 -148
- package/dist/index.js +83 -26
- package/dist/index.mjs +83 -26
- package/dist/install-integrity.json +130 -2
- package/dist/monitor-fleet-contract.d.mts +138 -0
- package/dist/monitor-fleet-contract.d.ts +138 -0
- package/dist/monitor-fleet-contract.js +404 -0
- package/dist/monitor-fleet-contract.mjs +367 -0
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/release.d.mts +255 -0
- package/dist/release.d.ts +255 -0
- package/dist/release.js +215 -0
- package/dist/release.mjs +188 -0
- 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
|
+
}
|