deepline 0.3.17 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +45 -0
  3. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +32 -18
  4. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +254 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +6 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +28 -3
  7. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +3 -1
  8. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +5 -2
  9. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +344 -26
  10. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +50 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1 -1
  12. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +378 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/runtime-reliability-policy.ts +391 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/runtime-traffic-policy.ts +125 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +21 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +36 -39
  17. package/dist/cli/index.js +252 -1
  18. package/dist/cli/index.mjs +252 -1
  19. package/dist/index.js +252 -1
  20. package/dist/index.mjs +252 -1
  21. package/dist/install-integrity.json +4 -0
  22. package/package.json +1 -1
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
192
192
  // 0.3.0 introduces raw-v2: complete scrubbed provider responses are
193
193
  // available at toolResponse.rawV2 while toolResponse.raw and all declared
194
194
  // getters keep their established compatibility behavior.
195
- version: '0.3.17',
195
+ version: '0.3.18',
196
196
  updateSummary:
197
197
  'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
198
198
  contracts: {
@@ -52,6 +52,11 @@ import {
52
52
  import { vercelProtectionBypassHeaders } from '@shared_libs/play-runtime/vercel-protection';
53
53
  import type { RuntimeReceiptAction } from '@shared_libs/play-runtime/runtime-actions';
54
54
  import { RUNTIME_CAPACITY_POLICY } from '@shared_libs/play-runtime/runtime-capacity-policy';
55
+ import {
56
+ DEFAULT_RUNTIME_TRAFFIC_POLICY,
57
+ isRuntimeTrafficPolicy,
58
+ type RuntimeTrafficPolicy,
59
+ } from '@shared_libs/play-runtime/runtime-traffic-policy';
55
60
 
56
61
  export type StoredPlayArtifactPayload = {
57
62
  sourceCode: string;
@@ -217,6 +222,7 @@ type RuntimeApiRequest =
217
222
  | ({
218
223
  action: 'governor_budget_charge';
219
224
  } & PlayRunnerBudgetChargeInput)
225
+ | { action: 'get_runtime_traffic_policy' }
220
226
  | RuntimeReceiptAction;
221
227
 
222
228
  export type WorkerRuntimeApiContext = {
@@ -1472,6 +1478,45 @@ export async function chargeGovernorBudgetViaAppRuntime(
1472
1478
  });
1473
1479
  }
1474
1480
 
1481
+ export async function getRuntimeTrafficPolicyViaAppRuntime(
1482
+ context: WorkerRuntimeApiContext,
1483
+ ): Promise<RuntimeTrafficPolicy> {
1484
+ const response = await postAppRuntimeApi<unknown>(context, {
1485
+ action: 'get_runtime_traffic_policy',
1486
+ });
1487
+ if (isRuntimeTrafficPolicy(response)) return response;
1488
+ // A 200 response is not enough to activate an incident control. A rollout
1489
+ // mismatch or proxy body must behave as a control-plane outage (and use the
1490
+ // caller's visible compiled-default fallback), never as an implicit limit.
1491
+ throw new AppRuntimeApiResponseError({
1492
+ action: 'get_runtime_traffic_policy',
1493
+ status: 502,
1494
+ code: 'runtime_traffic_policy_invalid_response',
1495
+ retryable: true,
1496
+ detail:
1497
+ 'Runtime traffic policy response did not match the versioned contract.',
1498
+ });
1499
+ }
1500
+
1501
+ /**
1502
+ * The incident policy is a load-reduction overlay, never an execution
1503
+ * prerequisite. During an app/Convex rolling deploy or a control-plane outage
1504
+ * it is safer to use the compiled normal policy than to strand every new Play
1505
+ * before sandbox creation. Callers must emit the returned diagnostic; this is
1506
+ * intentionally a visible fail-open, not a silent fallback.
1507
+ */
1508
+ export function fallbackRuntimeTrafficPolicyForUnavailableControlPlane(
1509
+ error: unknown,
1510
+ ): RuntimeTrafficPolicy | null {
1511
+ if (
1512
+ error instanceof AppRuntimeApiTransportError ||
1513
+ (error instanceof AppRuntimeApiResponseError && error.status >= 500)
1514
+ ) {
1515
+ return DEFAULT_RUNTIME_TRAFFIC_POLICY;
1516
+ }
1517
+ return null;
1518
+ }
1519
+
1475
1520
  export async function writeStagedFileFromAppRuntime(
1476
1521
  context: WorkerRuntimeApiContext,
1477
1522
  file: Pick<PlayStagedFileRef, 'storageKey'>,
@@ -76,6 +76,7 @@ import {
76
76
  } from './run-execution-scope';
77
77
  import { DEFAULT_RUNTIME_EXECUTION_CAPABILITIES } from './execution-capabilities';
78
78
  import { vercelProtectionBypassHeader } from './vercel-protection';
79
+ import { RUNTIME_RELIABILITY_POLICY } from './runtime-reliability-policy';
79
80
  export {
80
81
  RuntimeSheetRowsBlockedError,
81
82
  type RuntimeSheetBlockedRowDetail,
@@ -418,11 +419,15 @@ const DEEPLINEAGENT_TOOL_RUNTIME_TIMEOUT_MS = 15 * 60 * 1000;
418
419
  // a bounded, repairable transport failure that row-isolation settles per row
419
420
  // instead of hanging the whole run.
420
421
  const DEFAULT_TOOL_RUNTIME_TIMEOUT_MS = 15 * 60 * 1000 + 30_000;
421
- const FETCH_TRANSPORT_MAX_ATTEMPTS = 3;
422
+ const FETCH_TRANSPORT_MAX_ATTEMPTS =
423
+ RUNTIME_RELIABILITY_POLICY.egress.fetchMaxAttempts;
422
424
  const FETCH_TRANSPORT_RETRY_DELAY_MS = 100;
423
- const CTX_FETCH_HEADERS_TIMEOUT_MS = 30_000;
424
- const CTX_FETCH_BODY_TIMEOUT_MS = 30_000;
425
- const CTX_FETCH_TOTAL_TIMEOUT_MS = 120_000;
425
+ const CTX_FETCH_HEADERS_TIMEOUT_MS =
426
+ RUNTIME_RELIABILITY_POLICY.egress.fetchHeadersTimeoutMs;
427
+ const CTX_FETCH_BODY_TIMEOUT_MS =
428
+ RUNTIME_RELIABILITY_POLICY.egress.fetchBodyTimeoutMs;
429
+ const CTX_FETCH_TOTAL_TIMEOUT_MS =
430
+ RUNTIME_RELIABILITY_POLICY.egress.fetchTotalTimeoutMs;
426
431
  // cloudflared returns this branded HTML when its connection to the local app
427
432
  // resets. It is a transport failure before the app can reach a provider, not a
428
433
  // provider 502; require both the branded page and our development tunnel host.
@@ -430,6 +435,12 @@ const DEEPLINE_DEVELOPER_TUNNEL_ORIGIN_502_PATTERN =
430
435
  /\bdeeplinedeveloper\.com\s*\|\s*502\s*:\s*bad gateway\b/i;
431
436
  const NODE_RUNTIME_MAP_VISIBILITY_MAX_ATTEMPTS = 100;
432
437
  const NODE_RUNTIME_MAP_VISIBILITY_RETRY_MS = 25;
438
+ // A newly claimed receipt should normally use its scheduled heartbeat. When a
439
+ // test/SEV policy deliberately gives it only a moment of lease life, however,
440
+ // scheduler setup and the first provider-dispatch turn can consume that entire
441
+ // window before the timer fires. Renew once before fan-out in that narrow case.
442
+ // Production's normal multi-minute lease remains on the zero-extra-query path.
443
+ const IMMEDIATE_PENDING_RECEIPT_HEARTBEAT_THRESHOLD_MS = 100;
433
444
  // Per-row sanity cap on distinct inline child invocations. `maxPlayCallDepth`
434
445
  // (governor) already bounds nesting; this bounds fan-WIDTH from one active row
435
446
  // resolver. The dedupe set is row-local, never retained for the full run.
@@ -10354,6 +10365,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10354
10365
  // explicit backend capability; retain the immediate ownership check
10355
10366
  // for those receipts.
10356
10367
  if (claimsEstablishedExecutionFence) {
10368
+ if (
10369
+ ownershipConfirmedUntilMs - Date.now() <=
10370
+ IMMEDIATE_PENDING_RECEIPT_HEARTBEAT_THRESHOLD_MS
10371
+ ) {
10372
+ const outcome = await heartbeatOwnedRequests();
10373
+ if (outcome === 'active') supervisor.start();
10374
+ return;
10375
+ }
10357
10376
  supervisor.start();
10358
10377
  return;
10359
10378
  }
@@ -12183,26 +12202,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12183
12202
  if (heartbeatFailure) {
12184
12203
  throw heartbeatFailure;
12185
12204
  }
12186
- if (error instanceof ToolExecuteInvalidJsonError) {
12187
- throw createToolHttpError(
12188
- toolErrorSchemaVersion,
12189
- `Tool ${toolId} returned an invalid JSON response body.`,
12190
- null,
12191
- response?.status ?? 0,
12192
- 'repairable',
12193
- );
12194
- }
12195
12205
  const transportError = abortController?.signal.aborted
12196
12206
  ? abortController.signal.reason instanceof Error
12197
12207
  ? abortController.signal.reason
12198
12208
  : new Error(
12199
12209
  `Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
12200
12210
  )
12201
- : error instanceof ToolExecuteResponseBodyTransportError
12211
+ : error instanceof ToolExecuteResponseBodyTransportError ||
12212
+ error instanceof ToolExecuteInvalidJsonError
12202
12213
  ? error.cause
12203
12214
  : error;
12204
12215
  if (
12205
- error instanceof ToolExecuteResponseBodyTransportError &&
12216
+ (error instanceof ToolExecuteResponseBodyTransportError ||
12217
+ error instanceof ToolExecuteInvalidJsonError) &&
12206
12218
  response?.status === 402
12207
12219
  ) {
12208
12220
  const diagnostic = describeTransportError(transportError);
@@ -12221,7 +12233,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12221
12233
  const ambiguousDispatchedFailure =
12222
12234
  fetchDispatched &&
12223
12235
  (response === null ||
12224
- error instanceof ToolExecuteResponseBodyTransportError);
12236
+ error instanceof ToolExecuteResponseBodyTransportError ||
12237
+ error instanceof ToolExecuteInvalidJsonError);
12225
12238
  const hasDurableInvocationIdentity = Boolean(
12226
12239
  durableCallReceiptKey &&
12227
12240
  providerIdempotencyKey &&
@@ -12277,8 +12290,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12277
12290
  })}`,
12278
12291
  );
12279
12292
  const failureBoundary =
12280
- error instanceof ToolExecuteResponseBodyTransportError
12281
- ? 'response body transport failed after response headers'
12293
+ error instanceof ToolExecuteResponseBodyTransportError ||
12294
+ error instanceof ToolExecuteInvalidJsonError
12295
+ ? 'response body could not be verified after response headers'
12282
12296
  : 'request transport failed after dispatch before response headers';
12283
12297
  throw createToolHttpError(
12284
12298
  toolErrorSchemaVersion,
@@ -0,0 +1,254 @@
1
+ import { RUNTIME_RELIABILITY_POLICY } from './runtime-reliability-policy';
2
+
3
+ /**
4
+ * Typed recovery facts that cross the provider-backend seam.
5
+ *
6
+ * A runner backend is allowed to say only that customer code has not begun
7
+ * and name the provider-side condition it observed. The scheduler owns the
8
+ * durable attempt/defer counters, deadline, and circuit state; keeping those
9
+ * controls out of provider adapters prevents every adapter from inventing a
10
+ * subtly different retry loop.
11
+ */
12
+
13
+ export const PLAY_RUN_RECOVERY_ERROR_NAME = 'PlayRunRecoveryError';
14
+
15
+ /**
16
+ * Hard platform defaults for a dependency-wide pre-code outage. They are
17
+ * deliberately finite; scheduler persistence records every defer so an
18
+ * operator can inspect or redrive exhausted work instead of inheriting an
19
+ * invisible infinite loop. A later control-plane policy may narrow these
20
+ * values, never exceed these ceilings.
21
+ */
22
+ export const PLAY_RUN_RECOVERY_DEFER_POLICY = {
23
+ maxDefers: RUNTIME_RELIABILITY_POLICY.recovery.maxDefers,
24
+ horizonMs: RUNTIME_RELIABILITY_POLICY.recovery.horizonMs,
25
+ deferBaseSeconds: RUNTIME_RELIABILITY_POLICY.recovery.deferBaseSeconds,
26
+ deferMaxSeconds: RUNTIME_RELIABILITY_POLICY.recovery.deferMaxSeconds,
27
+ } as const;
28
+
29
+ export function recoveryDeferDelaySeconds(deferCount: number): number {
30
+ const index = Math.max(0, Math.floor(deferCount) - 1);
31
+ return Math.min(
32
+ PLAY_RUN_RECOVERY_DEFER_POLICY.deferMaxSeconds,
33
+ PLAY_RUN_RECOVERY_DEFER_POLICY.deferBaseSeconds * 2 ** index,
34
+ );
35
+ }
36
+
37
+ export type PlayRunRecoveryAction = 'engine_retry' | 'defer';
38
+
39
+ export type PlayRunRecoveryReason =
40
+ | 'modal_capacity_exhausted_before_execution'
41
+ | 'modal_circuit_open_before_execution'
42
+ | 'modal_sandbox_unavailable_before_execution'
43
+ | 'modal_runner_start_failed_before_execution'
44
+ | 'runtime_initialization_transport_before_execution'
45
+ | 'runtime_traffic_hold_before_execution'
46
+ | 'runtime_traffic_throttled_before_execution';
47
+
48
+ export type PlayRunRecoveryDecision = Readonly<{
49
+ /** A fresh sandbox is safe only before customer code can have started. */
50
+ stage: 'pre_code';
51
+ /**
52
+ * `engine_retry` consumes the scheduler engine's bounded attempt ladder.
53
+ * `defer` is for dependency-wide pressure and MUST consume the scheduler's
54
+ * durable defer budget/deadline instead of looping in an adapter.
55
+ */
56
+ action: PlayRunRecoveryAction;
57
+ reason: PlayRunRecoveryReason;
58
+ /** Non-secret scope used by the scheduler's dependency circuit breaker. */
59
+ dependency: 'modal' | 'runtime';
60
+ }>;
61
+
62
+ /**
63
+ * The scheduler must interpret this error. It is intentionally not a
64
+ * user-facing failure and must not be converted into a PlayRunnerResult:
65
+ * doing so would terminalize work which has not begun executing customer code.
66
+ */
67
+ export class PlayRunRecoveryError extends Error {
68
+ readonly decision: PlayRunRecoveryDecision;
69
+ readonly cause?: unknown;
70
+
71
+ constructor(input: {
72
+ decision: PlayRunRecoveryDecision;
73
+ message: string;
74
+ cause?: unknown;
75
+ }) {
76
+ super(input.message);
77
+ this.name = PLAY_RUN_RECOVERY_ERROR_NAME;
78
+ this.decision = input.decision;
79
+ if (input.cause !== undefined) {
80
+ this.cause = input.cause;
81
+ }
82
+ }
83
+ }
84
+
85
+ export function isPlayRunRecoveryError(
86
+ error: unknown,
87
+ ): error is PlayRunRecoveryError {
88
+ return (
89
+ error instanceof Error &&
90
+ error.name === PLAY_RUN_RECOVERY_ERROR_NAME &&
91
+ 'decision' in error
92
+ );
93
+ }
94
+
95
+ function errorCode(error: unknown): string | number | null {
96
+ if (!error || typeof error !== 'object') return null;
97
+ const code = (error as { code?: unknown }).code;
98
+ return typeof code === 'string' || typeof code === 'number' ? code : null;
99
+ }
100
+
101
+ function errorStatus(error: unknown): number | null {
102
+ if (!error || typeof error !== 'object') return null;
103
+ const status = (error as { status?: unknown }).status;
104
+ if (typeof status === 'number' && Number.isFinite(status)) return status;
105
+ const responseStatus = (error as { response?: { status?: unknown } }).response
106
+ ?.status;
107
+ return typeof responseStatus === 'number' && Number.isFinite(responseStatus)
108
+ ? responseStatus
109
+ : null;
110
+ }
111
+
112
+ function messageOf(error: unknown): string {
113
+ return error instanceof Error ? error.message : String(error);
114
+ }
115
+
116
+ /**
117
+ * Classify Modal's sandbox-create response at the provider edge, while it is
118
+ * still known that no sandbox/runner/customer code exists. Do not use this
119
+ * classifier after `sandboxes.create` succeeds: a later failure may be
120
+ * ambiguous and must not receive a blind fresh replay.
121
+ */
122
+ export function recoveryForModalSandboxCreateFailure(
123
+ error: unknown,
124
+ ): PlayRunRecoveryError | null {
125
+ const code = errorCode(error);
126
+ const status = errorStatus(error);
127
+ const message = messageOf(error);
128
+ const resourceExhausted =
129
+ code === 8 ||
130
+ (typeof code === 'string' && /RESOURCE_EXHAUSTED/i.test(code)) ||
131
+ /\bRESOURCE_EXHAUSTED\b/i.test(message);
132
+ if (resourceExhausted) {
133
+ return new PlayRunRecoveryError({
134
+ decision: {
135
+ stage: 'pre_code',
136
+ action: 'defer',
137
+ reason: 'modal_capacity_exhausted_before_execution',
138
+ dependency: 'modal',
139
+ },
140
+ message:
141
+ 'Modal sandbox capacity is unavailable before customer execution began.',
142
+ cause: error,
143
+ });
144
+ }
145
+
146
+ const unavailable =
147
+ code === 14 ||
148
+ code === 4 ||
149
+ (typeof code === 'string' &&
150
+ /(?:UNAVAILABLE|DEADLINE_EXCEEDED)/i.test(code)) ||
151
+ status === 429 ||
152
+ (status !== null && status >= 500 && status <= 599) ||
153
+ /\b(?:UNAVAILABLE|DEADLINE_EXCEEDED)\b/i.test(message);
154
+ if (!unavailable) return null;
155
+ return new PlayRunRecoveryError({
156
+ decision: {
157
+ stage: 'pre_code',
158
+ action: 'engine_retry',
159
+ reason: 'modal_sandbox_unavailable_before_execution',
160
+ dependency: 'modal',
161
+ },
162
+ message:
163
+ 'Modal sandbox creation was temporarily unavailable before customer execution began.',
164
+ cause: error,
165
+ });
166
+ }
167
+
168
+ /**
169
+ * The scheduler circuit blocked provider acquisition before the backend made a
170
+ * Modal API call. This is equivalent to a proven pre-code capacity rejection,
171
+ * but avoids turning a shared-account outage into one create request per run.
172
+ */
173
+ export function recoveryForOpenModalCircuit(): PlayRunRecoveryError {
174
+ return new PlayRunRecoveryError({
175
+ decision: {
176
+ stage: 'pre_code',
177
+ action: 'defer',
178
+ reason: 'modal_circuit_open_before_execution',
179
+ dependency: 'modal',
180
+ },
181
+ message:
182
+ 'Modal sandbox acquisition is deferred while the shared dependency circuit is open.',
183
+ });
184
+ }
185
+
186
+ /**
187
+ * An operator traffic policy is evaluated before a first managed sandbox is
188
+ * created. It becomes a bounded durable recovery defer, never a generic task
189
+ * retry, so a forgotten SEV mitigation cannot spin accepted webhooks forever.
190
+ */
191
+ export function recoveryForRuntimeTrafficPolicy(input: {
192
+ mode: 'hold' | 'throttle';
193
+ }): PlayRunRecoveryError {
194
+ const reason =
195
+ input.mode === 'hold'
196
+ ? 'runtime_traffic_hold_before_execution'
197
+ : 'runtime_traffic_throttled_before_execution';
198
+ return new PlayRunRecoveryError({
199
+ decision: {
200
+ stage: 'pre_code',
201
+ action: 'defer',
202
+ reason,
203
+ dependency: 'runtime',
204
+ },
205
+ message:
206
+ input.mode === 'hold'
207
+ ? 'New sandbox starts are temporarily held by the runtime incident policy.'
208
+ : 'New sandbox starts are temporarily paced by the runtime incident policy.',
209
+ });
210
+ }
211
+
212
+ /**
213
+ * This is constructed only by the detached-runner readiness check. That
214
+ * check has not parked the runner or observed scheduler liveness, so it is a
215
+ * pre-code failure for recovery purposes. The scheduler's bounded engine
216
+ * ladder, not this module, chooses how many fresh starts are allowed.
217
+ */
218
+ export function recoveryForModalRunnerStartupFailure(
219
+ error: unknown,
220
+ ): PlayRunRecoveryError {
221
+ return new PlayRunRecoveryError({
222
+ decision: {
223
+ stage: 'pre_code',
224
+ action: 'engine_retry',
225
+ reason: 'modal_runner_start_failed_before_execution',
226
+ dependency: 'modal',
227
+ },
228
+ message:
229
+ 'Modal sandbox did not become ready before customer execution began.',
230
+ cause: error,
231
+ });
232
+ }
233
+
234
+ /**
235
+ * A runner transport classifier has already proved that the result has no
236
+ * customer rows or steps. Keep its engine retry typed so an ordinary failed
237
+ * result cannot accidentally bypass the engine's bounded retry ladder.
238
+ */
239
+ export function recoveryForRuntimeInitializationTransportFailure(
240
+ reason:
241
+ | 'runtime_postgres_connect'
242
+ | 'runtime_api_transport'
243
+ | 'runtime_receipt_gateway_transport',
244
+ ): PlayRunRecoveryError {
245
+ return new PlayRunRecoveryError({
246
+ decision: {
247
+ stage: 'pre_code',
248
+ action: 'engine_retry',
249
+ reason: 'runtime_initialization_transport_before_execution',
250
+ dependency: 'runtime',
251
+ },
252
+ message: `Runtime initialization transport failed before customer execution began (${reason}).`,
253
+ });
254
+ }
@@ -32,7 +32,7 @@ export const INTERNAL_RUNTIME_STORAGE_ERROR_MESSAGE =
32
32
  export const RUNTIME_SANDBOX_LOST_MESSAGE =
33
33
  'The execution sandbox became unreachable before returning a terminal result. Re-run the same command; if this keeps happening, contact Deepline support with the run ID.';
34
34
  export const RUNTIME_SANDBOX_OOM_MESSAGE =
35
- 'The execution sandbox ran out of memory. Completed work is durably recorded. Retry the same command safely; Deepline reuses completed steps and does not repeat their provider calls. If it happens again, reduce the batch size or row payload.';
35
+ 'The execution sandbox ran out of memory. Completed work is durably recorded. Deepline does not automatically retry this run because the same resource profile is unlikely to succeed. Reduce the batch size or row payload before starting a new run.';
36
36
  export const RUNTIME_SANDBOX_KILLED_MESSAGE =
37
37
  'The execution sandbox was killed before it could return a result, and the kill reason is unavailable. Completed work is durably recorded. Retry the same command safely; Deepline reuses completed steps and does not repeat their provider calls.';
38
38
  export const RUNTIME_LIMIT_EXCEEDED_MESSAGE =
@@ -385,7 +385,11 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
385
385
  code: 'RUNTIME_SANDBOX_OOM',
386
386
  phase: 'infrastructure',
387
387
  message: RUNTIME_SANDBOX_OOM_MESSAGE,
388
- retryable: true,
388
+ // A confirmed runtime OOM is poison work for the current resource
389
+ // profile. Retrying it automatically spends capacity without changing
390
+ // the cause; completed receipts remain available to a deliberate run
391
+ // after the input or limits are changed.
392
+ retryable: false,
389
393
  cause,
390
394
  };
391
395
  }
@@ -14,6 +14,10 @@ import {
14
14
  PLAY_RUNNER_STARTUP_GRACE_SECONDS,
15
15
  STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
16
16
  } from '@shared_libs/play-runtime/runtime-constants';
17
+ import {
18
+ RUNTIME_RELIABILITY_ENV,
19
+ RUNTIME_RELIABILITY_SEV_OVERRIDE_ENV,
20
+ } from '@shared_libs/play-runtime/runtime-reliability-policy';
17
21
  import {
18
22
  STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS,
19
23
  validatePlaySandboxRuntimeLimits,
@@ -80,6 +84,26 @@ function shellQuote(value: string): string {
80
84
  return `'${value.replace(/'/g, `'\\''`)}'`;
81
85
  }
82
86
 
87
+ /**
88
+ * Sandboxes intentionally receive no general worker environment. The bounded,
89
+ * non-secret reliability controls are the exception: copy only their declared
90
+ * names so an incident override affects the runner's gateway/egress deadlines
91
+ * too, without ever widening this into an environment pass-through.
92
+ */
93
+ function runtimeReliabilityEnvironmentPrefix(): string {
94
+ const names = [
95
+ RUNTIME_RELIABILITY_SEV_OVERRIDE_ENV,
96
+ ...Object.values(RUNTIME_RELIABILITY_ENV),
97
+ ];
98
+ const entries = names.flatMap((name) => {
99
+ const value = process.env[name];
100
+ return value === undefined || value.trim() === ''
101
+ ? []
102
+ : [`${name}=${shellQuote(value)}`];
103
+ });
104
+ return entries.length > 0 ? `${entries.join(' ')} ` : '';
105
+ }
106
+
83
107
  function gzipUtf8(value: string): Buffer {
84
108
  return gzipSync(Buffer.from(value, 'utf-8'));
85
109
  }
@@ -326,7 +350,7 @@ async function main() {
326
350
  code: 'RUNTIME_SANDBOX_OOM',
327
351
  phase: 'infrastructure',
328
352
  message: ${JSON.stringify(RUNTIME_SANDBOX_OOM_MESSAGE)},
329
- retryable: true,
353
+ retryable: false,
330
354
  cause: synthesizedError,
331
355
  }] }
332
356
  : {}),
@@ -579,6 +603,7 @@ export async function stageRunnerPayload(input: {
579
603
  process.env.DEEPLINE_RUNTIME_RECEIPT_TRACE === '1'
580
604
  ? 'DEEPLINE_RUNTIME_RECEIPT_TRACE=1 '
581
605
  : '';
606
+ const runtimeReliabilityEnv = runtimeReliabilityEnvironmentPrefix();
582
607
  const watchedRunnerCommand = [
583
608
  'node',
584
609
  '-e',
@@ -600,7 +625,7 @@ export async function stageRunnerPayload(input: {
600
625
  artifactCodePath,
601
626
  artifactSourceMapPath,
602
627
  crashPusherPath,
603
- })} && DEEPLINE_PLAY_RUNNER_RUNTIME_STARTED_PATH=${shellQuote(runtimeStartedPath)} DEEPLINE_PLAY_RUNNER_RUNTIME_COMPLETED_PATH=${shellQuote(runtimeCompletedPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STARTUP_DIAGNOSTIC_PATH=${shellQuote(startupDiagnosticPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact ${runnerTraceEnv}${watchedRunnerCommand}`;
628
+ })} && DEEPLINE_PLAY_RUNNER_RUNTIME_STARTED_PATH=${shellQuote(runtimeStartedPath)} DEEPLINE_PLAY_RUNNER_RUNTIME_COMPLETED_PATH=${shellQuote(runtimeCompletedPath)} DEEPLINE_PLAY_RUNNER_PROGRESS_EVENT_PATH=${shellQuote(progressEventPath)} DEEPLINE_PLAY_RUNNER_STARTUP_DIAGNOSTIC_PATH=${shellQuote(startupDiagnosticPath)} DEEPLINE_PLAY_RUNNER_STDOUT_EVENT_MODE=compact ${runtimeReliabilityEnv}${runnerTraceEnv}${watchedRunnerCommand}`;
604
629
  // Crash-containment epilogue: runs UNCONDITIONALLY after the runner exits and
605
630
  // pushes the parsed (or synthesized) terminal to the gateway so the parked
606
631
  // worker wakes within seconds of ANY runner death — process.exit abuse, OOM
@@ -609,7 +634,7 @@ export async function stageRunnerPayload(input: {
609
634
  // Its own retry diagnostics are kept in a separate log: appending them to
610
635
  // `outputPath` after a near-gateway-sized result could hide that result from
611
636
  // a later bounded-tail recovery read.
612
- const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(crashPusherLogPath)} ${shellQuote(exitCodePath)} ${shellQuote(oomKillBaselinePath)} ${shellQuote(runtimeStartedPath)} ${shellQuote(runtimeCompletedPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(terminationDiagnosticPath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)} ${shellQuote(startupDiagnosticPath)}; ( awk '$1 == "oom_kill" { print $2 }' /sys/fs/cgroup/memory.events 2>/dev/null || true ) > ${shellQuote(oomKillBaselinePath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(oomKillBaselinePath)} /sys/fs/cgroup/memory.events ${shellQuote(terminationDiagnosticPath)} > ${shellQuote(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
637
+ const command = `rm -f ${shellQuote(outputPath)} ${shellQuote(crashPusherLogPath)} ${shellQuote(exitCodePath)} ${shellQuote(oomKillBaselinePath)} ${shellQuote(runtimeStartedPath)} ${shellQuote(runtimeCompletedPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(terminationDiagnosticPath)} ${shellQuote(progressEventPath)} ${shellQuote(`${progressEventPath}.*`)} ${shellQuote(startupDiagnosticPath)}; ( awk '$1 == "oom_kill" { print $2 }' /sys/fs/cgroup/memory.events 2>/dev/null || true ) > ${shellQuote(oomKillBaselinePath)}; ( ${runnerCommand} ) > ${shellQuote(outputPath)} 2>&1; code=$?; printf '%s' "$code" > ${shellQuote(exitCodePath)}; ${runtimeReliabilityEnv}node ${shellQuote(crashPusherPath)} ${shellQuote(configPath)} "$code" ${shellQuote(outputPath)} ${shellQuote(runtimeLimitMarkerPath)} ${shellQuote(oomKillBaselinePath)} /sys/fs/cgroup/memory.events ${shellQuote(terminationDiagnosticPath)} > ${shellQuote(crashPusherLogPath)} 2>&1 || true; printf 'deepline runner output captured: %s\\n' ${shellQuote(outputPath)}; exit "$code"`;
613
638
 
614
639
  return {
615
640
  workDir: input.workDir,
@@ -1,4 +1,5 @@
1
1
  import type { DaytonaSandbox } from './daytona-lifecycle';
2
+ import { RUNTIME_RELIABILITY_POLICY } from '@shared_libs/play-runtime/runtime-reliability-policy';
2
3
 
3
4
  export type DetachedDaytonaStartupDiagnostic = {
4
5
  schedulerReadinessObserved: boolean;
@@ -229,7 +230,8 @@ export type DetachedDaytonaRunnerSupervisor = {
229
230
 
230
231
  // The durable heartbeat proves runner-process liveness plus gateway reachability.
231
232
  // Keep the handoff bounded while allowing a cold gateway to recover.
232
- export const DAYTONA_RUNNER_READY_TIMEOUT_MS = 30_000;
233
+ export const DAYTONA_RUNNER_READY_TIMEOUT_MS =
234
+ RUNTIME_RELIABILITY_POLICY.sandbox.runnerReadyTimeoutMs;
233
235
  const DAYTONA_RUNNER_READY_POLL_MS = 50;
234
236
 
235
237
  export type DetachedRunnerReadinessPort = (
@@ -41,9 +41,12 @@ import {
41
41
  DaytonaRunnerInitializationError,
42
42
  prepareDetachedDaytonaRunner,
43
43
  } from './daytona-session-execution';
44
+ import { RUNTIME_RELIABILITY_POLICY } from '@shared_libs/play-runtime/runtime-reliability-policy';
44
45
 
45
- const DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS = 60_000;
46
- const DAYTONA_COMMAND_RECOVERY_POLL_MS = 2_000;
46
+ const DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS =
47
+ RUNTIME_RELIABILITY_POLICY.sandbox.daytonaCommandRecoveryTimeoutMs;
48
+ const DAYTONA_COMMAND_RECOVERY_POLL_MS =
49
+ RUNTIME_RELIABILITY_POLICY.sandbox.daytonaCommandRecoveryPollMs;
47
50
  const DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS = 2;
48
51
  const DAYTONA_UPLOAD_MAX_ATTEMPTS = 2;
49
52
  const DAYTONA_UPLOAD_ATTEMPT_DEADLINE_MS = 90_000;