deepline 0.3.16 → 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 (27) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +7 -0
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/shared_libs/integrations/bettercontact-execution-policy.ts +29 -0
  4. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +45 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +19 -3
  6. package/dist/bundling-sources/shared_libs/play-runtime/bettercontact-batching.ts +726 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +45 -24
  8. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +254 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +2 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +6 -2
  11. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +28 -3
  12. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +3 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +5 -2
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +344 -26
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +50 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +378 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runtime-reliability-policy.ts +391 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/runtime-traffic-policy.ts +125 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +21 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +36 -39
  22. package/dist/cli/index.js +265 -1
  23. package/dist/cli/index.mjs +265 -1
  24. package/dist/index.js +265 -1
  25. package/dist/index.mjs +265 -1
  26. package/dist/install-integrity.json +6 -0
  27. package/package.json +1 -1
@@ -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.
@@ -5369,6 +5380,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
5369
5380
  entries: Array<{
5370
5381
  request: ToolCallRequest;
5371
5382
  result: unknown | null;
5383
+ status?: string;
5372
5384
  metadata?: ToolResultMetadataInput | null;
5373
5385
  jobId?: string;
5374
5386
  meta?: Record<string, unknown>;
@@ -5380,7 +5392,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
5380
5392
  ...entry,
5381
5393
  wrapped: await this.wrapToolExecutionResult({
5382
5394
  toolId,
5383
- status: entry.result == null ? 'no_result' : 'completed',
5395
+ status:
5396
+ entry.status ?? (entry.result == null ? 'no_result' : 'completed'),
5384
5397
  jobId: entry.jobId,
5385
5398
  result: entry.result,
5386
5399
  metadata: entry.metadata,
@@ -10352,6 +10365,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10352
10365
  // explicit backend capability; retain the immediate ownership check
10353
10366
  // for those receipts.
10354
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
+ }
10355
10376
  supervisor.start();
10356
10377
  return;
10357
10378
  }
@@ -11282,12 +11303,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11282
11303
  }
11283
11304
  continue;
11284
11305
  }
11306
+ const batchExecution = entry.result;
11285
11307
  try {
11286
11308
  const splitResults =
11287
- entry.result != null
11309
+ batchExecution != null
11288
11310
  ? entry.request.splitResults(
11289
11311
  legacyResultForBatchSplitter(
11290
- entry.result.execution,
11312
+ batchExecution.execution,
11291
11313
  ),
11292
11314
  )
11293
11315
  : entry.request.memberRequests.map(() => null);
@@ -11298,11 +11320,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11298
11320
  (request, index) => ({
11299
11321
  request,
11300
11322
  result: splitResults[index] ?? null,
11323
+ status: batchExecution?.execution.status,
11301
11324
  toolResponse:
11302
- entry.result == null
11325
+ batchExecution == null
11303
11326
  ? undefined
11304
11327
  : publicToolResponseForBatchedItem(
11305
- entry.result.execution,
11328
+ batchExecution.execution,
11306
11329
  splitResults[index] ?? null,
11307
11330
  this.currentToolResponseContract ===
11308
11331
  RAW_V2_TOOL_RESPONSE_CONTRACT,
@@ -11323,7 +11346,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11323
11346
  resolveLiveFollowers(request, resolvedResults[index]);
11324
11347
  }
11325
11348
  } finally {
11326
- entry.result?.releaseToolSlot();
11349
+ batchExecution?.releaseToolSlot();
11327
11350
  }
11328
11351
  }
11329
11352
 
@@ -11340,6 +11363,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11340
11363
  const completionBuffer: Array<{
11341
11364
  request: ToolCallRequest;
11342
11365
  result: unknown | null;
11366
+ status?: string;
11343
11367
  metadata?: ToolResultMetadataInput | null;
11344
11368
  jobId?: string;
11345
11369
  meta?: Record<string, unknown>;
@@ -11359,6 +11383,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11359
11383
  entries.map((entry) => ({
11360
11384
  request: entry.request,
11361
11385
  result: entry.result,
11386
+ status: entry.status,
11362
11387
  metadata: entry.metadata,
11363
11388
  jobId: entry.jobId,
11364
11389
  meta: entry.meta,
@@ -11392,6 +11417,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11392
11417
  completionBuffer.push({
11393
11418
  request,
11394
11419
  result: execution.result ?? null,
11420
+ status: execution.status,
11395
11421
  metadata: execution.metadata ?? null,
11396
11422
  jobId: execution.jobId,
11397
11423
  meta: execution.meta,
@@ -12176,26 +12202,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12176
12202
  if (heartbeatFailure) {
12177
12203
  throw heartbeatFailure;
12178
12204
  }
12179
- if (error instanceof ToolExecuteInvalidJsonError) {
12180
- throw createToolHttpError(
12181
- toolErrorSchemaVersion,
12182
- `Tool ${toolId} returned an invalid JSON response body.`,
12183
- null,
12184
- response?.status ?? 0,
12185
- 'repairable',
12186
- );
12187
- }
12188
12205
  const transportError = abortController?.signal.aborted
12189
12206
  ? abortController.signal.reason instanceof Error
12190
12207
  ? abortController.signal.reason
12191
12208
  : new Error(
12192
12209
  `Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
12193
12210
  )
12194
- : error instanceof ToolExecuteResponseBodyTransportError
12211
+ : error instanceof ToolExecuteResponseBodyTransportError ||
12212
+ error instanceof ToolExecuteInvalidJsonError
12195
12213
  ? error.cause
12196
12214
  : error;
12197
12215
  if (
12198
- error instanceof ToolExecuteResponseBodyTransportError &&
12216
+ (error instanceof ToolExecuteResponseBodyTransportError ||
12217
+ error instanceof ToolExecuteInvalidJsonError) &&
12199
12218
  response?.status === 402
12200
12219
  ) {
12201
12220
  const diagnostic = describeTransportError(transportError);
@@ -12214,7 +12233,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12214
12233
  const ambiguousDispatchedFailure =
12215
12234
  fetchDispatched &&
12216
12235
  (response === null ||
12217
- error instanceof ToolExecuteResponseBodyTransportError);
12236
+ error instanceof ToolExecuteResponseBodyTransportError ||
12237
+ error instanceof ToolExecuteInvalidJsonError);
12218
12238
  const hasDurableInvocationIdentity = Boolean(
12219
12239
  durableCallReceiptKey &&
12220
12240
  providerIdempotencyKey &&
@@ -12270,8 +12290,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12270
12290
  })}`,
12271
12291
  );
12272
12292
  const failureBoundary =
12273
- error instanceof ToolExecuteResponseBodyTransportError
12274
- ? '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'
12275
12296
  : 'request transport failed after dispatch before response headers';
12276
12297
  throw createToolHttpError(
12277
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
+ }
@@ -1,4 +1,5 @@
1
1
  import type { AnyBatchOperationStrategy } from './batching-types';
2
+ import { bettercontactBatchStrategies } from './bettercontact-batching';
2
3
  import { DEFAULT_PLAY_RUNTIME_BATCH_STRATEGIES } from './default-batch-strategies';
3
4
  import { fullenrichBatchStrategies } from './fullenrich-batching';
4
5
  import { opensosdataBatchStrategies } from './opensosdata-batching';
@@ -8,6 +9,7 @@ export const PLAY_RUNTIME_BATCH_OPERATION_REGISTRY: Record<
8
9
  AnyBatchOperationStrategy
9
10
  > = {
10
11
  ...DEFAULT_PLAY_RUNTIME_BATCH_STRATEGIES,
12
+ ...bettercontactBatchStrategies,
11
13
  ...fullenrichBatchStrategies,
12
14
  ...opensosdataBatchStrategies,
13
15
  };
@@ -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;