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,725 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Fleet-wide Daytona sandbox janitor for the absurd play runtime.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists
|
|
6
|
+
* ---------------
|
|
7
|
+
* One-shot play sandboxes are created with the label `source:
|
|
8
|
+
* 'deepline-play-runner'` plus `orgId/workflowId/playId/runId`, `ephemeral:
|
|
9
|
+
* true`, and `autoStopInterval: 5` (see
|
|
10
|
+
* `packages/play-runtime/runner-backends/backends/daytona-lifecycle.ts`).
|
|
11
|
+
* Per-run deletion happens through the durable Sandbox Cleanup state machine,
|
|
12
|
+
* but a worker crash can lose the cleanup enqueue after the physical resource
|
|
13
|
+
* was recorded. This janitor is the inventory backstop: Daytona labels discover
|
|
14
|
+
* candidates, while the exact Runtime Sandbox Operation ledger alone decides
|
|
15
|
+
* ownership and deletion eligibility. The janitor restores cleanup obligations;
|
|
16
|
+
* it never performs an unledgered provider deletion.
|
|
17
|
+
*
|
|
18
|
+
* A candidate is eligible only when the durable ledger has one exact physical
|
|
19
|
+
* owner at `(daytona, sandboxId, routingDomain)` and either the owning Run is
|
|
20
|
+
* terminal, deletion was already authorized, or the creation-time configured
|
|
21
|
+
* execution ceiling plus terminal/grace windows has elapsed. Daytona's runId
|
|
22
|
+
* label is intentionally excluded from authority: Daytona truncates label
|
|
23
|
+
* values to 63 characters, so it is only a correlation hint.
|
|
24
|
+
*
|
|
25
|
+
* Fleet scoping (IMPORTANT): the sandbox label scope is `source`-only and the
|
|
26
|
+
* Daytona account is SHARED across every lane (prod, every preview PR fleet,
|
|
27
|
+
* worktree dev — all use the same DAYTONA_API_KEY and stamp the identical
|
|
28
|
+
* `source` label; nothing on the sandbox names its lane/schema). A `list` by
|
|
29
|
+
* `source` therefore returns EVERY lane's sandboxes. The janitor may run only
|
|
30
|
+
* against the prod-default schema, and even there it may authorize deletion
|
|
31
|
+
* only for a resource exactly bound in that schema's operation ledger. A
|
|
32
|
+
* sandbox owned by another lane is therefore unresolved and spared. The worker
|
|
33
|
+
* wiring and CLI both refuse to sweep on an isolated schema (see
|
|
34
|
+
* `isIsolatedRuntimeSchedulerSchema`). This module's core assumes that gate has
|
|
35
|
+
* already been applied by its caller.
|
|
36
|
+
*
|
|
37
|
+
* Everything here is best-effort by design. A single ownership read or cleanup
|
|
38
|
+
* authorization failure is logged and the sweep continues — one wedged sandbox
|
|
39
|
+
* must never abort the fleet-wide pass. The only hard failure is an inability
|
|
40
|
+
* to enumerate at all.
|
|
41
|
+
*/
|
|
42
|
+
import { PLAY_RUNNER_TERMINAL_GRACE_SECONDS } from './/runtime-constants';
|
|
43
|
+
import { DAYTONA_PLAY_RUNNER_LABEL_SOURCE } from './/runner-backends/backends/daytona-labels';
|
|
44
|
+
|
|
45
|
+
export { DAYTONA_PLAY_RUNNER_LABEL_SOURCE };
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Slack added on top of the play runtime ceiling before a sandbox is eligible
|
|
49
|
+
* for reaping. The ceiling is the max user-code runtime; the grace covers
|
|
50
|
+
* setup, cleanup, and clock skew so we never race a sandbox that is legitimately
|
|
51
|
+
* still finishing. Constant, not env — this is a tuning number, not a secret.
|
|
52
|
+
*/
|
|
53
|
+
export const REAP_AGE_GRACE_MS = 5 * 60 * 1000;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A single sandbox as seen by the janitor. Structurally a subset of the Daytona
|
|
57
|
+
* SDK `Sandbox`, so a real `Sandbox` satisfies it directly.
|
|
58
|
+
*/
|
|
59
|
+
export type ReapableSandbox = {
|
|
60
|
+
id: string;
|
|
61
|
+
labels: Record<string, string>;
|
|
62
|
+
/** Physical Daytona organization returned by the provider, not a label. */
|
|
63
|
+
organizationId?: string;
|
|
64
|
+
/** ISO-8601 creation timestamp, per the Daytona SDK `Sandbox.createdAt`. */
|
|
65
|
+
createdAt?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Injected Daytona port. `list` yields our label-scoped sandboxes (the SDK
|
|
70
|
+
* `list` returns an async iterator that auto-pages, so we consume it lazily).
|
|
71
|
+
* Inventory is the only Daytona surface the janitor touches.
|
|
72
|
+
*/
|
|
73
|
+
export type DaytonaJanitorClientPort = {
|
|
74
|
+
list(query: {
|
|
75
|
+
labels: Record<string, string>;
|
|
76
|
+
}): AsyncIterable<ReapableSandbox>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type DaytonaJanitorOwnershipAlertCode =
|
|
80
|
+
| 'missing_routing_domain'
|
|
81
|
+
| 'ownership_missing'
|
|
82
|
+
| 'routing_domain_conflict'
|
|
83
|
+
| 'legacy_unbound_ownership'
|
|
84
|
+
| 'ownership_ambiguous'
|
|
85
|
+
| 'invalid_execution_ceiling'
|
|
86
|
+
| 'provider_inventory_conflicts_deleted_ledger'
|
|
87
|
+
| 'cleanup_ownership_conflict';
|
|
88
|
+
|
|
89
|
+
export type DaytonaJanitorOwnership =
|
|
90
|
+
| {
|
|
91
|
+
kind: 'owned';
|
|
92
|
+
operationKey: string;
|
|
93
|
+
runId: string;
|
|
94
|
+
attempt: number;
|
|
95
|
+
orgId: string;
|
|
96
|
+
operationState: 'live' | 'deleting' | 'deleted';
|
|
97
|
+
runStatus:
|
|
98
|
+
| 'queued'
|
|
99
|
+
| 'running'
|
|
100
|
+
| 'waiting'
|
|
101
|
+
| 'completed'
|
|
102
|
+
| 'failed'
|
|
103
|
+
| 'cancelled';
|
|
104
|
+
providerCreatedAtMs: number;
|
|
105
|
+
waitDeadlineMs: number | null;
|
|
106
|
+
runtimeLimitSeconds: number | null;
|
|
107
|
+
cleanupStatus: 'pending' | 'deleting' | 'deleted' | null;
|
|
108
|
+
}
|
|
109
|
+
| {
|
|
110
|
+
kind: 'unresolved';
|
|
111
|
+
code: DaytonaJanitorOwnershipAlertCode;
|
|
112
|
+
candidateRunIds: string[];
|
|
113
|
+
candidateRoutingDomains: Array<string | null>;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type DaytonaJanitorCleanupAuthorization =
|
|
117
|
+
| 'scheduled'
|
|
118
|
+
| 'already_authorized'
|
|
119
|
+
| 'already_deleted'
|
|
120
|
+
| 'ownership_conflict'
|
|
121
|
+
| 'ownership_missing';
|
|
122
|
+
|
|
123
|
+
/** Durable exact-ownership authority. Provider labels are never inputs. */
|
|
124
|
+
export type DaytonaJanitorOwnershipPort = {
|
|
125
|
+
inspect(input: {
|
|
126
|
+
sandboxId: string;
|
|
127
|
+
routingDomain: string | null;
|
|
128
|
+
}): Promise<DaytonaJanitorOwnership>;
|
|
129
|
+
authorizeCleanup(input: {
|
|
130
|
+
operationKey: string;
|
|
131
|
+
sandboxId: string;
|
|
132
|
+
routingDomain: string;
|
|
133
|
+
runId: string;
|
|
134
|
+
attempt: number;
|
|
135
|
+
}): Promise<DaytonaJanitorCleanupAuthorization>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type ReapOptions = {
|
|
139
|
+
/** Injected clock for testability; defaults to Date.now. */
|
|
140
|
+
now?: () => number;
|
|
141
|
+
/** When true, decide without restoring a durable cleanup obligation. */
|
|
142
|
+
dryRun?: boolean;
|
|
143
|
+
/** Structured logger; defaults to console. */
|
|
144
|
+
logger?: Pick<Console, 'info' | 'warn'>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export type ReapSummary = {
|
|
148
|
+
scanned: number;
|
|
149
|
+
cleanupEligible: number;
|
|
150
|
+
cleanupScheduled: number;
|
|
151
|
+
alreadyAuthorized: number;
|
|
152
|
+
spared: number;
|
|
153
|
+
alerts: number;
|
|
154
|
+
errors: number;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
type ReapDecision =
|
|
158
|
+
| { authorizeCleanup: false; reason: string }
|
|
159
|
+
| { authorizeCleanup: true; reason: string };
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pure per-sandbox decision. Returns whether to authorize durable cleanup and a
|
|
163
|
+
* short reason for the structured log line.
|
|
164
|
+
*/
|
|
165
|
+
async function decideSandbox(input: {
|
|
166
|
+
ownership: Extract<DaytonaJanitorOwnership, { kind: 'owned' }>;
|
|
167
|
+
nowMs: number;
|
|
168
|
+
}): Promise<ReapDecision> {
|
|
169
|
+
const { ownership, nowMs } = input;
|
|
170
|
+
if (ownership.operationState === 'deleting') {
|
|
171
|
+
return { authorizeCleanup: true, reason: 'deletion_already_authorized' };
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
ownership.runStatus === 'completed' ||
|
|
175
|
+
ownership.runStatus === 'failed' ||
|
|
176
|
+
ownership.runStatus === 'cancelled'
|
|
177
|
+
) {
|
|
178
|
+
return { authorizeCleanup: true, reason: 'run_terminal' };
|
|
179
|
+
}
|
|
180
|
+
if (
|
|
181
|
+
ownership.runtimeLimitSeconds === null ||
|
|
182
|
+
!Number.isSafeInteger(ownership.runtimeLimitSeconds) ||
|
|
183
|
+
ownership.runtimeLimitSeconds <= 0 ||
|
|
184
|
+
!Number.isFinite(ownership.providerCreatedAtMs)
|
|
185
|
+
) {
|
|
186
|
+
return { authorizeCleanup: false, reason: 'invalid_execution_ceiling' };
|
|
187
|
+
}
|
|
188
|
+
const configuredFloorMs =
|
|
189
|
+
ownership.providerCreatedAtMs +
|
|
190
|
+
(ownership.runtimeLimitSeconds + PLAY_RUNNER_TERMINAL_GRACE_SECONDS) *
|
|
191
|
+
1_000 +
|
|
192
|
+
REAP_AGE_GRACE_MS;
|
|
193
|
+
const deletionNotBeforeMs = Math.max(
|
|
194
|
+
configuredFloorMs,
|
|
195
|
+
ownership.waitDeadlineMs === null
|
|
196
|
+
? Number.NEGATIVE_INFINITY
|
|
197
|
+
: ownership.waitDeadlineMs + REAP_AGE_GRACE_MS,
|
|
198
|
+
);
|
|
199
|
+
return nowMs < deletionNotBeforeMs
|
|
200
|
+
? { authorizeCleanup: false, reason: 'before_deletion_not_before' }
|
|
201
|
+
: { authorizeCleanup: true, reason: 'execution_ceiling_elapsed' };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseCreatedAtMs(createdAt: string | undefined): number | null {
|
|
205
|
+
if (!createdAt) return null;
|
|
206
|
+
const parsed = Date.parse(createdAt);
|
|
207
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Pure, testable core. Enumerate our label-scoped sandboxes, decide each
|
|
212
|
+
* against the scheduler store, and best-effort restore cleanup obligations for
|
|
213
|
+
* resources that should be dead. Never throws past a single sandbox failure:
|
|
214
|
+
* an authorization or ownership-read error is logged and the sweep continues.
|
|
215
|
+
* Enumeration failure is the one hard error — with nothing to iterate there is
|
|
216
|
+
* no sweep to salvage.
|
|
217
|
+
*/
|
|
218
|
+
export async function reapOrphanDaytonaSandboxes(input: {
|
|
219
|
+
daytona: DaytonaJanitorClientPort;
|
|
220
|
+
ownership: DaytonaJanitorOwnershipPort;
|
|
221
|
+
options?: ReapOptions;
|
|
222
|
+
}): Promise<ReapSummary> {
|
|
223
|
+
const now = input.options?.now ?? Date.now;
|
|
224
|
+
const dryRun = input.options?.dryRun ?? false;
|
|
225
|
+
const logger = input.options?.logger ?? console;
|
|
226
|
+
|
|
227
|
+
const summary: ReapSummary = {
|
|
228
|
+
scanned: 0,
|
|
229
|
+
cleanupEligible: 0,
|
|
230
|
+
cleanupScheduled: 0,
|
|
231
|
+
alreadyAuthorized: 0,
|
|
232
|
+
spared: 0,
|
|
233
|
+
alerts: 0,
|
|
234
|
+
errors: 0,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const iterator = input.daytona.list({
|
|
238
|
+
labels: { source: DAYTONA_PLAY_RUNNER_LABEL_SOURCE },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
for await (const sandbox of iterator) {
|
|
242
|
+
summary.scanned += 1;
|
|
243
|
+
const createdAtMs = parseCreatedAtMs(sandbox.createdAt);
|
|
244
|
+
const ageMs =
|
|
245
|
+
createdAtMs === null ? null : Math.max(0, now() - createdAtMs);
|
|
246
|
+
const labelRunId = sandbox.labels.runId?.trim() || null;
|
|
247
|
+
const routingDomain = sandbox.organizationId?.trim() || null;
|
|
248
|
+
|
|
249
|
+
let ownership: DaytonaJanitorOwnership;
|
|
250
|
+
try {
|
|
251
|
+
ownership = await input.ownership.inspect({
|
|
252
|
+
sandboxId: sandbox.id,
|
|
253
|
+
routingDomain,
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
summary.errors += 1;
|
|
257
|
+
summary.spared += 1;
|
|
258
|
+
logger.warn('[daytona-janitor.ownership_read_failed]', {
|
|
259
|
+
sandboxId: sandbox.id,
|
|
260
|
+
routingDomain,
|
|
261
|
+
labelRunId,
|
|
262
|
+
error: error instanceof Error ? error.message : String(error),
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (ownership.kind === 'unresolved') {
|
|
268
|
+
summary.alerts += 1;
|
|
269
|
+
summary.spared += 1;
|
|
270
|
+
logger.warn('[daytona-janitor.ownership_alert]', {
|
|
271
|
+
provider: 'daytona',
|
|
272
|
+
code: ownership.code,
|
|
273
|
+
sandboxId: sandbox.id,
|
|
274
|
+
routingDomain,
|
|
275
|
+
labelRunId,
|
|
276
|
+
candidateRunIds: ownership.candidateRunIds,
|
|
277
|
+
candidateRoutingDomains: ownership.candidateRoutingDomains,
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (labelRunId && labelRunId !== ownership.runId) {
|
|
283
|
+
logger.info('[daytona-janitor.label_correlation_mismatch]', {
|
|
284
|
+
sandboxId: sandbox.id,
|
|
285
|
+
routingDomain,
|
|
286
|
+
labelRunId,
|
|
287
|
+
ownerRunId: ownership.runId,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (ownership.operationState === 'deleted') {
|
|
292
|
+
summary.alerts += 1;
|
|
293
|
+
summary.spared += 1;
|
|
294
|
+
logger.warn('[daytona-janitor.ownership_alert]', {
|
|
295
|
+
provider: 'daytona',
|
|
296
|
+
code: 'provider_inventory_conflicts_deleted_ledger',
|
|
297
|
+
sandboxId: sandbox.id,
|
|
298
|
+
routingDomain,
|
|
299
|
+
labelRunId,
|
|
300
|
+
ownerRunId: ownership.runId,
|
|
301
|
+
operationKey: ownership.operationKey,
|
|
302
|
+
});
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const decision = await decideSandbox({ ownership, nowMs: now() });
|
|
307
|
+
if (!decision.authorizeCleanup) {
|
|
308
|
+
summary.spared += 1;
|
|
309
|
+
if (decision.reason === 'invalid_execution_ceiling') {
|
|
310
|
+
summary.alerts += 1;
|
|
311
|
+
logger.warn('[daytona-janitor.ownership_alert]', {
|
|
312
|
+
provider: 'daytona',
|
|
313
|
+
code: 'invalid_execution_ceiling',
|
|
314
|
+
sandboxId: sandbox.id,
|
|
315
|
+
routingDomain,
|
|
316
|
+
labelRunId,
|
|
317
|
+
ownerRunId: ownership.runId,
|
|
318
|
+
operationKey: ownership.operationKey,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
summary.cleanupEligible += 1;
|
|
325
|
+
if (dryRun) {
|
|
326
|
+
logger.info('[daytona-janitor.would_schedule_cleanup]', {
|
|
327
|
+
sandboxId: sandbox.id,
|
|
328
|
+
routingDomain,
|
|
329
|
+
runId: ownership.runId,
|
|
330
|
+
ageMs,
|
|
331
|
+
reason: decision.reason,
|
|
332
|
+
dryRun: true,
|
|
333
|
+
});
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
const authorization = await input.ownership.authorizeCleanup({
|
|
339
|
+
operationKey: ownership.operationKey,
|
|
340
|
+
sandboxId: sandbox.id,
|
|
341
|
+
routingDomain: routingDomain!,
|
|
342
|
+
runId: ownership.runId,
|
|
343
|
+
attempt: ownership.attempt,
|
|
344
|
+
});
|
|
345
|
+
if (authorization === 'scheduled') {
|
|
346
|
+
summary.cleanupScheduled += 1;
|
|
347
|
+
} else if (
|
|
348
|
+
authorization === 'already_authorized' ||
|
|
349
|
+
authorization === 'already_deleted'
|
|
350
|
+
) {
|
|
351
|
+
summary.alreadyAuthorized += 1;
|
|
352
|
+
} else {
|
|
353
|
+
summary.alerts += 1;
|
|
354
|
+
summary.spared += 1;
|
|
355
|
+
logger.warn('[daytona-janitor.ownership_alert]', {
|
|
356
|
+
provider: 'daytona',
|
|
357
|
+
code:
|
|
358
|
+
authorization === 'ownership_conflict'
|
|
359
|
+
? 'cleanup_ownership_conflict'
|
|
360
|
+
: 'ownership_missing',
|
|
361
|
+
sandboxId: sandbox.id,
|
|
362
|
+
routingDomain,
|
|
363
|
+
labelRunId,
|
|
364
|
+
ownerRunId: ownership.runId,
|
|
365
|
+
operationKey: ownership.operationKey,
|
|
366
|
+
});
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
logger.info('[daytona-janitor.cleanup_authorized]', {
|
|
370
|
+
sandboxId: sandbox.id,
|
|
371
|
+
routingDomain,
|
|
372
|
+
runId: ownership.runId,
|
|
373
|
+
ageMs,
|
|
374
|
+
reason: decision.reason,
|
|
375
|
+
authorization,
|
|
376
|
+
});
|
|
377
|
+
} catch (error) {
|
|
378
|
+
summary.errors += 1;
|
|
379
|
+
summary.spared += 1;
|
|
380
|
+
logger.warn('[daytona-janitor.cleanup_authorization_failed]', {
|
|
381
|
+
sandboxId: sandbox.id,
|
|
382
|
+
routingDomain,
|
|
383
|
+
runId: ownership.runId,
|
|
384
|
+
ageMs,
|
|
385
|
+
reason: decision.reason,
|
|
386
|
+
error: error instanceof Error ? error.message : String(error),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
logger.info('[daytona-janitor.summary]', { ...summary, dryRun });
|
|
392
|
+
return summary;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ---------------------------------------------------------------------------
|
|
396
|
+
// Production adapters (thin, imported by the CLI entry and the worker wiring).
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
type SchedulerQueryClient = {
|
|
400
|
+
query<Row extends Record<string, unknown> = Record<string, unknown>>(
|
|
401
|
+
sql: string,
|
|
402
|
+
params?: unknown[],
|
|
403
|
+
): Promise<{ rows: Row[] }>;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
function quoteIdent(value: string): string {
|
|
407
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
type JanitorOwnershipRow = {
|
|
411
|
+
operation_key: string;
|
|
412
|
+
run_id: string;
|
|
413
|
+
attempt: number;
|
|
414
|
+
org_id: string;
|
|
415
|
+
routing_domain: string | null;
|
|
416
|
+
state: 'reserved' | 'live' | 'deleting' | 'deleted' | 'abandoned';
|
|
417
|
+
provider_created_at_ms: string | null;
|
|
418
|
+
run_status: Extract<DaytonaJanitorOwnership, { kind: 'owned' }>['runStatus'];
|
|
419
|
+
wait_deadline_ms: string | null;
|
|
420
|
+
launch_json: unknown;
|
|
421
|
+
cleanup_status: 'pending' | 'deleting' | 'deleted' | null;
|
|
422
|
+
cleanup_run_id: string | null;
|
|
423
|
+
cleanup_attempt: number | null;
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
function runtimeLimitSecondsFromLaunch(launch: unknown): number | null {
|
|
427
|
+
if (!launch || typeof launch !== 'object' || Array.isArray(launch)) {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
const contract = (launch as Record<string, unknown>).contractSnapshot;
|
|
431
|
+
if (!contract || typeof contract !== 'object' || Array.isArray(contract)) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
const runtimeLimit = (contract as Record<string, unknown>).runtimeLimit;
|
|
435
|
+
if (
|
|
436
|
+
!runtimeLimit ||
|
|
437
|
+
typeof runtimeLimit !== 'object' ||
|
|
438
|
+
Array.isArray(runtimeLimit)
|
|
439
|
+
) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
const seconds = (runtimeLimit as Record<string, unknown>).timeoutSeconds;
|
|
443
|
+
return typeof seconds === 'number' &&
|
|
444
|
+
Number.isSafeInteger(seconds) &&
|
|
445
|
+
seconds > 0
|
|
446
|
+
? seconds
|
|
447
|
+
: null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Runtime Postgres Adapter for the janitor's exact ownership authority. */
|
|
451
|
+
export function createPostgresDaytonaJanitorOwnershipPort(input: {
|
|
452
|
+
client: SchedulerQueryClient;
|
|
453
|
+
schema: string;
|
|
454
|
+
}): DaytonaJanitorOwnershipPort {
|
|
455
|
+
const runs = `${quoteIdent(input.schema)}.${quoteIdent('runs')}`;
|
|
456
|
+
const operations = `${quoteIdent(input.schema)}.${quoteIdent(
|
|
457
|
+
'runtime_sandbox_operations',
|
|
458
|
+
)}`;
|
|
459
|
+
const cleanup = `${quoteIdent(input.schema)}.${quoteIdent(
|
|
460
|
+
'sandbox_cleanup_jobs_v2',
|
|
461
|
+
)}`;
|
|
462
|
+
return {
|
|
463
|
+
async inspect({ sandboxId, routingDomain }) {
|
|
464
|
+
if (!routingDomain) {
|
|
465
|
+
return {
|
|
466
|
+
kind: 'unresolved',
|
|
467
|
+
code: 'missing_routing_domain',
|
|
468
|
+
candidateRunIds: [],
|
|
469
|
+
candidateRoutingDomains: [],
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const result = await input.client.query<JanitorOwnershipRow>(
|
|
473
|
+
`
|
|
474
|
+
SELECT
|
|
475
|
+
operation.operation_key,
|
|
476
|
+
operation.run_id,
|
|
477
|
+
operation.attempt,
|
|
478
|
+
operation.org_id,
|
|
479
|
+
operation.routing_domain,
|
|
480
|
+
operation.state,
|
|
481
|
+
(extract(epoch FROM operation.provider_created_at) * 1000)::bigint::text
|
|
482
|
+
AS provider_created_at_ms,
|
|
483
|
+
run.status AS run_status,
|
|
484
|
+
(extract(epoch FROM run.wait_deadline) * 1000)::bigint::text
|
|
485
|
+
AS wait_deadline_ms,
|
|
486
|
+
run.launch_json,
|
|
487
|
+
cleanup.status AS cleanup_status,
|
|
488
|
+
cleanup.run_id AS cleanup_run_id,
|
|
489
|
+
cleanup.attempt AS cleanup_attempt
|
|
490
|
+
FROM ${operations} AS operation
|
|
491
|
+
JOIN ${runs} AS run ON run.run_id = operation.run_id
|
|
492
|
+
LEFT JOIN ${cleanup} AS cleanup
|
|
493
|
+
ON cleanup.provider = operation.provider
|
|
494
|
+
AND cleanup.sandbox_id = operation.sandbox_id
|
|
495
|
+
AND cleanup.routing_domain IS NOT DISTINCT FROM operation.routing_domain
|
|
496
|
+
WHERE operation.provider = 'daytona'
|
|
497
|
+
AND operation.sandbox_id = $1
|
|
498
|
+
ORDER BY operation.routing_domain_key, operation.operation_key
|
|
499
|
+
`,
|
|
500
|
+
[sandboxId],
|
|
501
|
+
);
|
|
502
|
+
const exact = result.rows.filter(
|
|
503
|
+
(row) => row.routing_domain === routingDomain,
|
|
504
|
+
);
|
|
505
|
+
if (exact.length !== 1) {
|
|
506
|
+
const code: DaytonaJanitorOwnershipAlertCode =
|
|
507
|
+
exact.length > 1
|
|
508
|
+
? 'ownership_ambiguous'
|
|
509
|
+
: result.rows.length === 0
|
|
510
|
+
? 'ownership_missing'
|
|
511
|
+
: result.rows.some((row) => row.routing_domain === null)
|
|
512
|
+
? 'legacy_unbound_ownership'
|
|
513
|
+
: 'routing_domain_conflict';
|
|
514
|
+
return {
|
|
515
|
+
kind: 'unresolved',
|
|
516
|
+
code,
|
|
517
|
+
candidateRunIds: result.rows.map((row) => row.run_id),
|
|
518
|
+
candidateRoutingDomains: result.rows.map((row) => row.routing_domain),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const row = exact[0]!;
|
|
522
|
+
if (
|
|
523
|
+
row.state !== 'live' &&
|
|
524
|
+
row.state !== 'deleting' &&
|
|
525
|
+
row.state !== 'deleted'
|
|
526
|
+
) {
|
|
527
|
+
return {
|
|
528
|
+
kind: 'unresolved',
|
|
529
|
+
code: 'legacy_unbound_ownership',
|
|
530
|
+
candidateRunIds: [row.run_id],
|
|
531
|
+
candidateRoutingDomains: [row.routing_domain],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
if (
|
|
535
|
+
row.cleanup_status !== null &&
|
|
536
|
+
(row.cleanup_run_id !== row.run_id ||
|
|
537
|
+
Number(row.cleanup_attempt) !== Number(row.attempt))
|
|
538
|
+
) {
|
|
539
|
+
return {
|
|
540
|
+
kind: 'unresolved',
|
|
541
|
+
code: 'cleanup_ownership_conflict',
|
|
542
|
+
candidateRunIds: [row.run_id, row.cleanup_run_id ?? ''].filter(
|
|
543
|
+
Boolean,
|
|
544
|
+
),
|
|
545
|
+
candidateRoutingDomains: [row.routing_domain],
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
kind: 'owned',
|
|
550
|
+
operationKey: row.operation_key,
|
|
551
|
+
runId: row.run_id,
|
|
552
|
+
attempt: Number(row.attempt),
|
|
553
|
+
orgId: row.org_id,
|
|
554
|
+
operationState: row.state,
|
|
555
|
+
runStatus: row.run_status,
|
|
556
|
+
providerCreatedAtMs:
|
|
557
|
+
row.provider_created_at_ms === null
|
|
558
|
+
? Number.NaN
|
|
559
|
+
: Number(row.provider_created_at_ms),
|
|
560
|
+
waitDeadlineMs:
|
|
561
|
+
row.wait_deadline_ms === null ? null : Number(row.wait_deadline_ms),
|
|
562
|
+
runtimeLimitSeconds: runtimeLimitSecondsFromLaunch(row.launch_json),
|
|
563
|
+
cleanupStatus: row.cleanup_status,
|
|
564
|
+
};
|
|
565
|
+
},
|
|
566
|
+
async authorizeCleanup({
|
|
567
|
+
operationKey,
|
|
568
|
+
sandboxId,
|
|
569
|
+
routingDomain,
|
|
570
|
+
runId,
|
|
571
|
+
attempt,
|
|
572
|
+
}) {
|
|
573
|
+
const result = await input.client.query<{
|
|
574
|
+
operation_state: string;
|
|
575
|
+
cleanup_status: string | null;
|
|
576
|
+
cleanup_run_id: string | null;
|
|
577
|
+
cleanup_attempt: number | null;
|
|
578
|
+
inserted: boolean;
|
|
579
|
+
}>(
|
|
580
|
+
`
|
|
581
|
+
WITH owned AS MATERIALIZED (
|
|
582
|
+
SELECT operation_key, provider, sandbox_id, routing_domain,
|
|
583
|
+
run_id, attempt, environment, state
|
|
584
|
+
FROM ${operations}
|
|
585
|
+
WHERE operation_key = $1
|
|
586
|
+
AND provider = 'daytona'
|
|
587
|
+
AND sandbox_id = $2
|
|
588
|
+
AND routing_domain = $3
|
|
589
|
+
AND run_id = $4
|
|
590
|
+
AND attempt = $5
|
|
591
|
+
FOR UPDATE
|
|
592
|
+
), inserted AS (
|
|
593
|
+
INSERT INTO ${cleanup} (
|
|
594
|
+
provider, sandbox_id, run_id, attempt, environment, routing_domain
|
|
595
|
+
)
|
|
596
|
+
SELECT provider, sandbox_id, run_id, attempt, environment, routing_domain
|
|
597
|
+
FROM owned
|
|
598
|
+
WHERE state IN ('live', 'deleting')
|
|
599
|
+
ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
|
|
600
|
+
RETURNING provider, sandbox_id, routing_domain,
|
|
601
|
+
status, run_id, attempt
|
|
602
|
+
), current_cleanup AS MATERIALIZED (
|
|
603
|
+
SELECT inserted.status, inserted.run_id, inserted.attempt
|
|
604
|
+
FROM inserted
|
|
605
|
+
UNION ALL
|
|
606
|
+
SELECT cleanup.status, cleanup.run_id, cleanup.attempt
|
|
607
|
+
FROM ${cleanup} AS cleanup
|
|
608
|
+
JOIN owned
|
|
609
|
+
ON cleanup.provider = owned.provider
|
|
610
|
+
AND cleanup.sandbox_id = owned.sandbox_id
|
|
611
|
+
AND cleanup.routing_domain IS NOT DISTINCT FROM owned.routing_domain
|
|
612
|
+
WHERE NOT EXISTS (SELECT 1 FROM inserted)
|
|
613
|
+
), transitioned AS (
|
|
614
|
+
UPDATE ${operations} AS operation
|
|
615
|
+
SET state = 'deleting', updated_at = clock_timestamp()
|
|
616
|
+
FROM owned
|
|
617
|
+
WHERE operation.operation_key = owned.operation_key
|
|
618
|
+
AND operation.state = 'live'
|
|
619
|
+
AND EXISTS (
|
|
620
|
+
SELECT 1 FROM current_cleanup
|
|
621
|
+
WHERE current_cleanup.run_id = owned.run_id
|
|
622
|
+
AND current_cleanup.attempt = owned.attempt
|
|
623
|
+
AND current_cleanup.status <> 'deleted'
|
|
624
|
+
)
|
|
625
|
+
RETURNING operation.operation_key
|
|
626
|
+
)
|
|
627
|
+
SELECT
|
|
628
|
+
owned.state AS operation_state,
|
|
629
|
+
current_cleanup.status AS cleanup_status,
|
|
630
|
+
current_cleanup.run_id AS cleanup_run_id,
|
|
631
|
+
current_cleanup.attempt AS cleanup_attempt,
|
|
632
|
+
EXISTS (SELECT 1 FROM inserted) AS inserted
|
|
633
|
+
FROM owned
|
|
634
|
+
LEFT JOIN current_cleanup ON TRUE
|
|
635
|
+
`,
|
|
636
|
+
[operationKey, sandboxId, routingDomain, runId, attempt],
|
|
637
|
+
);
|
|
638
|
+
const row = result.rows[0];
|
|
639
|
+
if (!row) return 'ownership_missing';
|
|
640
|
+
if (
|
|
641
|
+
row.cleanup_status !== null &&
|
|
642
|
+
(row.cleanup_run_id !== runId ||
|
|
643
|
+
Number(row.cleanup_attempt) !== attempt)
|
|
644
|
+
) {
|
|
645
|
+
return 'ownership_conflict';
|
|
646
|
+
}
|
|
647
|
+
if (
|
|
648
|
+
row.operation_state === 'deleted' ||
|
|
649
|
+
row.cleanup_status === 'deleted'
|
|
650
|
+
) {
|
|
651
|
+
return 'already_deleted';
|
|
652
|
+
}
|
|
653
|
+
return row.inserted ? 'scheduled' : 'already_authorized';
|
|
654
|
+
},
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// ---------------------------------------------------------------------------
|
|
659
|
+
// CLI entry.
|
|
660
|
+
// ---------------------------------------------------------------------------
|
|
661
|
+
|
|
662
|
+
export async function runDaytonaSandboxJanitorCli(argv: string[]): Promise<void> {
|
|
663
|
+
const dryRun = argv.includes('--dry-run');
|
|
664
|
+
// Escape hatch for deliberate debugging only: reaping against an isolated
|
|
665
|
+
// schema is unsafe because the `source`-scoped fleet is shared across lanes
|
|
666
|
+
// (see startDaytonaSandboxJanitor). Combine with --dry-run to inspect.
|
|
667
|
+
const allowIsolated = argv.includes('--allow-isolated-schema');
|
|
668
|
+
|
|
669
|
+
// Lazy imports so the pure core above stays free of heavy runtime deps and
|
|
670
|
+
// remains trivially unit-testable with fake ports.
|
|
671
|
+
const [
|
|
672
|
+
{ Daytona },
|
|
673
|
+
{ loadDaytonaRequiredConfig },
|
|
674
|
+
poolModule,
|
|
675
|
+
schemaModule,
|
|
676
|
+
{ isIsolatedRuntimeSchedulerSchema },
|
|
677
|
+
] = await Promise.all([
|
|
678
|
+
import('@daytonaio/sdk'),
|
|
679
|
+
import('.//daytona-runtime-config'),
|
|
680
|
+
import('.//scheduler-backends/postgres-pool'),
|
|
681
|
+
import('.//scheduler-backends/runtime-scheduler-schema'),
|
|
682
|
+
import('.//runtime-scheduler-topology'),
|
|
683
|
+
]);
|
|
684
|
+
|
|
685
|
+
const url = poolModule.resolveWorkflowRuntimePostgresUrl();
|
|
686
|
+
if (!url) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
'WORKFLOW_RUNTIME_POSTGRES is required for the Daytona sandbox janitor.',
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const schema =
|
|
692
|
+
schemaModule.resolveRuntimeSchedulerSchemaEnv() || 'runtime_scheduler';
|
|
693
|
+
|
|
694
|
+
if (isIsolatedRuntimeSchedulerSchema(schema) && !allowIsolated) {
|
|
695
|
+
// The label scope is shared across every lane on this Daytona account, so a
|
|
696
|
+
// non-prod schema cannot authoritatively resolve the fleet. Refuse loudly
|
|
697
|
+
// rather than reap sandboxes owned by prod or another PR.
|
|
698
|
+
throw new Error(
|
|
699
|
+
`Refusing to reap on isolated schema "${schema}": the Daytona ` +
|
|
700
|
+
`source-label scope is shared across lanes, so runs absent from this ` +
|
|
701
|
+
`store may belong to prod or another PR. Pass --allow-isolated-schema ` +
|
|
702
|
+
`--dry-run only for deliberate debugging.`,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const { clientOptions } = loadDaytonaRequiredConfig();
|
|
707
|
+
const daytona = new Daytona(clientOptions);
|
|
708
|
+
|
|
709
|
+
const summary = await poolModule.withRuntimeSchedulerClient(
|
|
710
|
+
{ url, applicationName: 'deepline-daytona-janitor' },
|
|
711
|
+
(client) =>
|
|
712
|
+
reapOrphanDaytonaSandboxes({
|
|
713
|
+
daytona: {
|
|
714
|
+
list: (query) => daytona.list(query),
|
|
715
|
+
},
|
|
716
|
+
ownership: createPostgresDaytonaJanitorOwnershipPort({
|
|
717
|
+
client,
|
|
718
|
+
schema,
|
|
719
|
+
}),
|
|
720
|
+
options: { dryRun },
|
|
721
|
+
}),
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
console.info('[daytona-janitor.cli_done]', { ...summary, schema, dryRun });
|
|
725
|
+
}
|