deepline 0.3.22 → 0.3.24

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.
@@ -11,10 +11,14 @@ import type {
11
11
  import { daytonaPlayRunnerBackend } from './daytona';
12
12
  import { DaytonaSandboxAcquisitionUnavailableError } from './daytona-lifecycle';
13
13
  import { modalPlayRunnerBackend } from './modal';
14
+ import { isPlayRunRecoveryError } from '@shared_libs/play-runtime/play-run-recovery-policy';
14
15
  import {
15
16
  canTransitionRuntimeSandboxPlacement,
17
+ isRuntimeSandboxCanaryProduction,
16
18
  resolveRuntimeSandboxPlacementPolicy,
17
19
  RUNTIME_SANDBOX_PLACEMENT_POLICIES,
20
+ selectRuntimeSandboxCanary,
21
+ type RuntimeSandboxCanarySelection,
18
22
  type RuntimeSandboxPlacementPolicyId,
19
23
  type RuntimeSandboxPlacementFailureReason,
20
24
  type RuntimeSandboxPlacementPolicy,
@@ -41,6 +45,54 @@ type RuntimeSandboxProviderAdapter = {
41
45
  ) => RuntimeSandboxPlacementFailureReason | null;
42
46
  };
43
47
 
48
+ function selectionForRun(
49
+ policy: RuntimeSandboxPlacementPolicy,
50
+ runId?: string | null,
51
+ runtimeSchedulerSchema?: string | null,
52
+ ): RuntimeSandboxCanarySelection {
53
+ return selectRuntimeSandboxCanary({
54
+ policy,
55
+ runId,
56
+ production: isRuntimeSandboxCanaryProduction({
57
+ nodeEnv: process.env.NODE_ENV,
58
+ runtimeSchedulerSchema,
59
+ }),
60
+ });
61
+ }
62
+
63
+ function logCanaryOutcome(input: {
64
+ config: PlayRunnerExecutionConfig;
65
+ policy: RuntimeSandboxPlacementPolicy;
66
+ selection: RuntimeSandboxCanarySelection;
67
+ outcome: string;
68
+ error?: unknown;
69
+ }): void {
70
+ const recoveryReason = isPlayRunRecoveryError(input.error)
71
+ ? input.error.decision.reason
72
+ : null;
73
+ console.warn(
74
+ '[play-runner.sandbox_provider_canary]',
75
+ JSON.stringify({
76
+ runId: input.config.context.runId ?? null,
77
+ workflowId: input.config.context.workflowId ?? null,
78
+ runAttempt: input.config.context.runAttempt ?? null,
79
+ policyId: input.policy.id,
80
+ provider: input.selection.canary,
81
+ bucket: input.selection.bucket,
82
+ basisPoints: input.selection.basisPoints,
83
+ runtimeEnvironment: 'production',
84
+ outcome: input.outcome,
85
+ phase: 'startup',
86
+ // The Modal Adapter also performs scheduler-owned fencing, staging, and
87
+ // readiness work. Page only for its existing typed provider decisions;
88
+ // an unclassified setup failure remains observable without blaming Modal.
89
+ providerHealthFailure: recoveryReason?.startsWith('modal_') ?? false,
90
+ errorName: input.error instanceof Error ? input.error.name : null,
91
+ recoveryReason,
92
+ }),
93
+ );
94
+ }
95
+
44
96
  function classifyDaytonaAcquisitionFailure(
45
97
  error: unknown,
46
98
  ): RuntimeSandboxPlacementFailureReason | null {
@@ -70,18 +122,23 @@ export function createRuntimeSandboxPlacementBackend(input: {
70
122
  Partial<Record<RuntimeSandboxProvider, RuntimeSandboxProviderAdapter>>
71
123
  >;
72
124
  }): PlayRunnerBackend {
73
- const primaryProvider = input.policy.providers[0];
74
- const primaryAdapter = input.adapters[primaryProvider];
75
- if (!primaryAdapter) {
76
- throw new Error(
77
- `Runtime sandbox placement policy ${input.policy.id} has no Adapter for ${primaryProvider}.`,
78
- );
79
- }
80
125
  return {
81
126
  async prepare(
82
127
  prepareInput: PlayRunnerPrepareInput,
83
128
  callbacks?: PlayRunnerCallbacks,
84
129
  ): Promise<PlayRunnerPreparedExecution> {
130
+ const selection = selectionForRun(
131
+ input.policy,
132
+ prepareInput.context.runId,
133
+ prepareInput.context.runtimeSchedulerSchema,
134
+ );
135
+ const primaryProvider = selection.canary ?? input.policy.providers[0];
136
+ const primaryAdapter = input.adapters[primaryProvider];
137
+ if (!primaryAdapter) {
138
+ throw new Error(
139
+ `Runtime sandbox placement policy ${input.policy.id} has no Adapter for ${primaryProvider}.`,
140
+ );
141
+ }
85
142
  const providerPrepared = await primaryAdapter.backend.prepare?.(
86
143
  prepareInput,
87
144
  callbacks,
@@ -105,7 +162,15 @@ export function createRuntimeSandboxPlacementBackend(input: {
105
162
  isPrepared(prepared) && prepared.policyId === input.policy.id
106
163
  ? prepared
107
164
  : null;
108
- for (const [index, provider] of input.policy.providers.entries()) {
165
+ const selection = selectionForRun(
166
+ input.policy,
167
+ config.context.runId,
168
+ config.context.runtimeSchedulerSchema,
169
+ );
170
+ const providers = selection.canary
171
+ ? ([selection.canary] as const)
172
+ : input.policy.providers;
173
+ for (const [index, provider] of providers.entries()) {
109
174
  const adapter = input.adapters[provider];
110
175
  if (!adapter) {
111
176
  throw new Error(
@@ -113,15 +178,34 @@ export function createRuntimeSandboxPlacementBackend(input: {
113
178
  );
114
179
  }
115
180
  try {
116
- return await adapter.backend.execute(
181
+ const result = await adapter.backend.execute(
117
182
  config,
118
183
  callbacks,
119
184
  index === 0 && policyPrepared?.provider === provider
120
185
  ? policyPrepared.providerPrepared
121
186
  : undefined,
122
187
  );
188
+ if (selection.canary) {
189
+ logCanaryOutcome({
190
+ config,
191
+ policy: input.policy,
192
+ selection,
193
+ outcome: result.status,
194
+ });
195
+ }
196
+ return result;
123
197
  } catch (error) {
124
- const nextProvider = input.policy.providers[index + 1];
198
+ if (selection.canary) {
199
+ logCanaryOutcome({
200
+ config,
201
+ policy: input.policy,
202
+ selection,
203
+ outcome: 'error',
204
+ error,
205
+ });
206
+ throw error;
207
+ }
208
+ const nextProvider = providers[index + 1];
125
209
  const reason = adapter.classifyAcquisitionFailure(error);
126
210
  if (
127
211
  !nextProvider ||
@@ -199,6 +283,11 @@ export function createDaytonaModalFallbackBackend(
199
283
  export const daytonaModalFallbackPlayRunnerBackend =
200
284
  createDaytonaModalFallbackBackend();
201
285
 
286
+ export const daytonaModalCanaryPlayRunnerBackend =
287
+ createRuntimeSandboxPlacementBackendForPolicy(
288
+ RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaModalCanaryV1,
289
+ );
290
+
202
291
  export const daytonaOnlyPlayRunnerBackend =
203
292
  createRuntimeSandboxPlacementBackendForPolicy(
204
293
  RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaOnlyV1,
@@ -216,6 +305,8 @@ const DEFAULT_POLICY_BACKENDS: Readonly<
216
305
  daytonaOnlyPlayRunnerBackend,
217
306
  [RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaThenModalV1]:
218
307
  daytonaModalFallbackPlayRunnerBackend,
308
+ [RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaModalCanaryV1]:
309
+ daytonaModalCanaryPlayRunnerBackend,
219
310
  [RUNTIME_SANDBOX_PLACEMENT_POLICIES.modalOnlyV1]: modalOnlyPlayRunnerBackend,
220
311
  };
221
312
 
@@ -36,10 +36,50 @@ const MODAL_RUNNER_READY_TIMEOUT_MS =
36
36
  RUNTIME_RELIABILITY_POLICY.sandbox.runnerReadyTimeoutMs;
37
37
  const MODAL_SANDBOX_CREATE_TIMEOUT_MS =
38
38
  RUNTIME_RELIABILITY_POLICY.sandbox.modalSandboxCreateTimeoutMs;
39
+ const MODAL_PAYLOAD_UPLOAD_TIMEOUT_MS = 90_000;
40
+ const MODAL_TERMINATION_TIMEOUT_MS = 10_000;
39
41
  const MODAL_CAPACITY_RELEASE_RETRY_DELAYS_MS = [0, 100, 500, 2_000] as const;
40
42
  const MODAL_LATE_CREATE_PERSIST_RETRY_DELAYS_MS = [0, 100, 500, 2_000] as const;
41
43
  type ModalSandbox = Awaited<ReturnType<ModalClient['sandboxes']['create']>>;
42
44
 
45
+ class ModalPayloadUploadTimeoutError extends Error {
46
+ constructor(input: { sandboxId: string; path: string; bytes: number }) {
47
+ super(
48
+ `MODAL_PAYLOAD_UPLOAD_TIMEOUT: Modal payload upload did not complete within ${MODAL_PAYLOAD_UPLOAD_TIMEOUT_MS}ms (sandbox=${input.sandboxId}, path=${input.path}, bytes=${input.bytes}).`,
49
+ );
50
+ this.name = 'ModalPayloadUploadTimeoutError';
51
+ }
52
+ }
53
+
54
+ async function writeModalFileWithDeadline(input: {
55
+ sandbox: ModalSandbox;
56
+ content: Buffer;
57
+ path: string;
58
+ cancellation: Promise<never>;
59
+ }): Promise<void> {
60
+ let timer: ReturnType<typeof setTimeout> | undefined;
61
+ const deadline = new Promise<never>((_resolve, reject) => {
62
+ timer = setTimeout(() => {
63
+ reject(
64
+ new ModalPayloadUploadTimeoutError({
65
+ sandboxId: input.sandbox.sandboxId,
66
+ path: input.path,
67
+ bytes: input.content.byteLength,
68
+ }),
69
+ );
70
+ }, MODAL_PAYLOAD_UPLOAD_TIMEOUT_MS);
71
+ });
72
+ try {
73
+ await Promise.race([
74
+ input.sandbox.filesystem.writeBytes(input.content, input.path),
75
+ input.cancellation,
76
+ deadline,
77
+ ]);
78
+ } finally {
79
+ if (timer) clearTimeout(timer);
80
+ }
81
+ }
82
+
43
83
  /**
44
84
  * A create RPC that outlives its client deadline is fundamentally ambiguous:
45
85
  * Modal may still create the sandbox after the caller stops waiting. It is
@@ -185,10 +225,22 @@ async function confirmDetachedModalRunnerReady(input: {
185
225
  }
186
226
 
187
227
  async function terminateModalSandbox(sandbox: ModalSandbox): Promise<boolean> {
228
+ let timer: ReturnType<typeof setTimeout> | undefined;
188
229
  try {
189
230
  // The default terminate call only acknowledges the request. Capacity may
190
231
  // be released only after Modal confirms the sandbox itself has stopped.
191
- await sandbox.terminate({ wait: true });
232
+ await Promise.race([
233
+ sandbox.terminate({ wait: true }),
234
+ new Promise<never>((_resolve, reject) => {
235
+ timer = setTimeout(() => {
236
+ reject(
237
+ new Error(
238
+ `Modal sandbox termination did not settle within ${MODAL_TERMINATION_TIMEOUT_MS}ms.`,
239
+ ),
240
+ );
241
+ }, MODAL_TERMINATION_TIMEOUT_MS);
242
+ }),
243
+ ]);
192
244
  return true;
193
245
  } catch (error) {
194
246
  console.warn('[play-runner.modal.terminate_failed]', {
@@ -196,6 +248,8 @@ async function terminateModalSandbox(sandbox: ModalSandbox): Promise<boolean> {
196
248
  error: error instanceof Error ? error.message : String(error),
197
249
  });
198
250
  return false;
251
+ } finally {
252
+ if (timer) clearTimeout(timer);
199
253
  }
200
254
  }
201
255
 
@@ -529,7 +583,12 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
529
583
  sandbox: {
530
584
  id: sandbox.sandboxId,
531
585
  uploadFile: (content, path) =>
532
- sandbox.filesystem.writeBytes(content, path),
586
+ writeModalFileWithDeadline({
587
+ sandbox,
588
+ content,
589
+ path,
590
+ cancellation: cancellationPromise,
591
+ }),
533
592
  },
534
593
  bundlePromise: buildPlayRunnerBundle(),
535
594
  config,
@@ -1,6 +1,7 @@
1
1
  import type { PlayRunnerBackend } from './types';
2
2
  import { daytonaPlayRunnerBackend } from './backends/daytona';
3
3
  import {
4
+ daytonaModalCanaryPlayRunnerBackend,
4
5
  daytonaModalFallbackPlayRunnerBackend,
5
6
  daytonaOnlyPlayRunnerBackend,
6
7
  modalOnlyPlayRunnerBackend,
@@ -14,6 +15,8 @@ import {
14
15
  type PlayRuntimeBackendId,
15
16
  } from '@shared_libs/play-runtime/backend';
16
17
  import {
18
+ isRuntimeSandboxCanaryProduction,
19
+ RUNTIME_SANDBOX_PLACEMENT_POLICIES,
17
20
  runtimeSandboxPlacementPolicyForBackend,
18
21
  type RuntimeSandboxPlacementPolicyId,
19
22
  } from '@shared_libs/play-runtime/runtime-sandbox-placement-policy';
@@ -32,6 +35,13 @@ export function resolvePlayRunnerBackend(
32
35
  );
33
36
  const placementPolicy = runtimeSandboxPlacementPolicyForBackend(backend);
34
37
  if (placementPolicy) {
38
+ if (
39
+ placementPolicy.id ===
40
+ RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaModalCanaryV1 &&
41
+ !isRuntimeSandboxCanaryProduction({ nodeEnv: process.env.NODE_ENV })
42
+ ) {
43
+ return daytonaModalFallbackPlayRunnerBackend;
44
+ }
35
45
  return resolveRuntimeSandboxPlacementBackend(placementPolicy.id);
36
46
  }
37
47
  if (backend === PLAY_RUNTIME_BACKENDS.localProcess) {
@@ -42,6 +52,7 @@ export function resolvePlayRunnerBackend(
42
52
 
43
53
  export {
44
54
  daytonaPlayRunnerBackend,
55
+ daytonaModalCanaryPlayRunnerBackend,
45
56
  daytonaModalFallbackPlayRunnerBackend,
46
57
  daytonaOnlyPlayRunnerBackend,
47
58
  localProcessPlayRunnerBackend,
@@ -1,4 +1,6 @@
1
1
  import { PLAY_RUNTIME_BACKENDS, type PlayRuntimeBackendId } from './backend';
2
+ import { createHash } from 'node:crypto';
3
+ import { isIsolatedRuntimeSchedulerSchema } from './runtime-scheduler-topology';
2
4
 
3
5
  export const RUNTIME_SANDBOX_PROVIDERS = {
4
6
  daytona: 'daytona',
@@ -15,6 +17,7 @@ export type RuntimeSandboxProvider =
15
17
  export const RUNTIME_SANDBOX_PLACEMENT_POLICIES = {
16
18
  daytonaOnlyV1: 'daytona_only@1',
17
19
  daytonaThenModalV1: 'daytona_then_modal@1',
20
+ daytonaModalCanaryV1: 'daytona_modal_canary@1',
18
21
  modalOnlyV1: 'modal_only@1',
19
22
  } as const;
20
23
 
@@ -41,8 +44,59 @@ export type RuntimeSandboxPlacementPolicy = Readonly<{
41
44
  transitions: readonly RuntimeSandboxPlacementTransition[];
42
45
  requiredCredentialEnv: readonly string[];
43
46
  cleanupProviders: readonly RuntimeSandboxProvider[];
47
+ canary?: Readonly<{
48
+ provider: RuntimeSandboxProvider;
49
+ basisPoints: number;
50
+ selector: 'sha256_run_id_basis_points@1';
51
+ environment: 'production';
52
+ }>;
44
53
  }>;
45
54
 
55
+ const CANARY_BUCKET_COUNT = 10_000;
56
+
57
+ export type RuntimeSandboxCanarySelection = Readonly<{
58
+ canary: RuntimeSandboxProvider | null;
59
+ bucket: number | null;
60
+ basisPoints: number;
61
+ }>;
62
+
63
+ export function isRuntimeSandboxCanaryProduction(input: {
64
+ nodeEnv?: string | null;
65
+ runtimeSchedulerSchema?: string | null;
66
+ }): boolean {
67
+ return (
68
+ input.nodeEnv === 'production' &&
69
+ !isIsolatedRuntimeSchedulerSchema(input.runtimeSchedulerSchema)
70
+ );
71
+ }
72
+
73
+ /** Stable across scheduler attempts and worker processes for a durable run id. */
74
+ export function selectRuntimeSandboxCanary(input: {
75
+ policy: RuntimeSandboxPlacementPolicy;
76
+ runId?: string | null;
77
+ production: boolean;
78
+ }): RuntimeSandboxCanarySelection {
79
+ const canary = input.policy.canary;
80
+ const runId = input.runId?.trim();
81
+ if (!canary || !runId || !input.production) {
82
+ return {
83
+ canary: null,
84
+ bucket: null,
85
+ basisPoints: canary?.basisPoints ?? 0,
86
+ };
87
+ }
88
+ const bucket =
89
+ createHash('sha256')
90
+ .update(`runtime-sandbox-placement@1:${runId}`)
91
+ .digest()
92
+ .readUInt32BE(0) % CANARY_BUCKET_COUNT;
93
+ return {
94
+ canary: bucket < canary.basisPoints ? canary.provider : null,
95
+ bucket,
96
+ basisPoints: canary.basisPoints,
97
+ };
98
+ }
99
+
46
100
  const DAYTONA_THEN_MODAL_V1: RuntimeSandboxPlacementPolicy = {
47
101
  id: RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaThenModalV1,
48
102
  compatibilityRuntimeBackend: PLAY_RUNTIME_BACKENDS.daytona,
@@ -72,6 +126,23 @@ const DAYTONA_THEN_MODAL_V1: RuntimeSandboxPlacementPolicy = {
72
126
  ],
73
127
  };
74
128
 
129
+ /**
130
+ * Staged Daytona-compatible policy. Workers learn this policy before launch
131
+ * writers start persisting it, so an app-first deploy cannot strand runs on an
132
+ * unsupported policy id. One percent of production runs enter the same Modal
133
+ * Adapter used by DAYTONA_THEN_MODAL_V1 after a capacity failure.
134
+ */
135
+ const DAYTONA_MODAL_CANARY_V1: RuntimeSandboxPlacementPolicy = {
136
+ ...DAYTONA_THEN_MODAL_V1,
137
+ id: RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaModalCanaryV1,
138
+ canary: {
139
+ provider: RUNTIME_SANDBOX_PROVIDERS.modal,
140
+ basisPoints: 100,
141
+ selector: 'sha256_run_id_basis_points@1',
142
+ environment: 'production',
143
+ },
144
+ };
145
+
75
146
  /**
76
147
  * Frozen compatibility policy for launches written before placement policy ids
77
148
  * existed. It is retained until every pre-policy release lane has drained.
@@ -100,6 +171,8 @@ export const RUNTIME_SANDBOX_PLACEMENT_POLICY_CATALOG: Readonly<
100
171
  [RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaOnlyV1]: DAYTONA_ONLY_V1,
101
172
  [RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaThenModalV1]:
102
173
  DAYTONA_THEN_MODAL_V1,
174
+ [RUNTIME_SANDBOX_PLACEMENT_POLICIES.daytonaModalCanaryV1]:
175
+ DAYTONA_MODAL_CANARY_V1,
103
176
  [RUNTIME_SANDBOX_PLACEMENT_POLICIES.modalOnlyV1]: MODAL_ONLY_V1,
104
177
  };
105
178
 
@@ -129,7 +202,7 @@ export function runtimeSandboxPlacementPolicyForBackend(
129
202
  backend: PlayRuntimeBackendId,
130
203
  ): RuntimeSandboxPlacementPolicy | null {
131
204
  if (backend === PLAY_RUNTIME_BACKENDS.daytona) {
132
- return DAYTONA_THEN_MODAL_V1;
205
+ return DAYTONA_MODAL_CANARY_V1;
133
206
  }
134
207
  if (backend === PLAY_RUNTIME_BACKENDS.modal) {
135
208
  return MODAL_ONLY_V1;
@@ -42,6 +42,11 @@ export const TOOL_CATEGORY_DESCRIPTIONS: Readonly<Record<string, string>> = {
42
42
  free: 'Free tools that do not spend Deepline credits.',
43
43
  };
44
44
 
45
+ /** Stable UI/CLI enumeration of the well-known canonical category slugs. */
46
+ export const WELL_KNOWN_TOOL_CATEGORIES = Object.freeze(
47
+ Object.keys(TOOL_CATEGORY_DESCRIPTIONS),
48
+ );
49
+
45
50
  /**
46
51
  * One-line definition for a tool category slug, or null when none is declared.
47
52
  * Never fabricates a definition for unknown slugs.