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
@@ -0,0 +1,391 @@
1
+ /**
2
+ * Runtime availability policy shared by the scheduler, Fly workers, receipt
3
+ * gateway, and sandbox runner. These are platform controls, not Play options:
4
+ * a Play author must never be able to make an unsafe side effect retry longer
5
+ * or more often.
6
+ *
7
+ * Defaults are deliberately compiled into the runtime so a missing deploy
8
+ * variable is safe. A changed value is accepted only when an incident operator
9
+ * also sets `DEEPLINE_RUNTIME_RELIABILITY_SEV_OVERRIDE=enabled`. This makes an
10
+ * emergency tuning change explicit, auditable in deployment configuration, and
11
+ * impossible to smuggle through an ordinary per-run request.
12
+ */
13
+
14
+ export const RUNTIME_RELIABILITY_SEV_OVERRIDE_ENV =
15
+ 'DEEPLINE_RUNTIME_RELIABILITY_SEV_OVERRIDE';
16
+
17
+ export const RUNTIME_RELIABILITY_ENV = {
18
+ engineMaxAttempts: 'DEEPLINE_RUNTIME_ENGINE_MAX_ATTEMPTS',
19
+ engineRetryBaseSeconds: 'DEEPLINE_RUNTIME_ENGINE_RETRY_BASE_SECONDS',
20
+ engineRetryMaxSeconds: 'DEEPLINE_RUNTIME_ENGINE_RETRY_MAX_SECONDS',
21
+ schedulerSubmitCapacityMaxAttempts:
22
+ 'DEEPLINE_RUNTIME_SCHEDULER_SUBMIT_CAPACITY_MAX_ATTEMPTS',
23
+ claimLeaseSeconds: 'DEEPLINE_RUNTIME_CLAIM_LEASE_SECONDS',
24
+ schedulerDbAcquireTimeoutMs:
25
+ 'DEEPLINE_RUNTIME_SCHEDULER_DB_ACQUIRE_TIMEOUT_MS',
26
+ schedulerDbQueryTimeoutMs: 'DEEPLINE_RUNTIME_SCHEDULER_DB_QUERY_TIMEOUT_MS',
27
+ schedulerDbRetryAttempts: 'DEEPLINE_RUNTIME_SCHEDULER_DB_RETRY_ATTEMPTS',
28
+ schedulerDbRetryBaseDelayMs:
29
+ 'DEEPLINE_RUNTIME_SCHEDULER_DB_RETRY_BASE_DELAY_MS',
30
+ schedulerDbRetryMaxDelayMs:
31
+ 'DEEPLINE_RUNTIME_SCHEDULER_DB_RETRY_MAX_DELAY_MS',
32
+ schedulerDbCircuitOpenMs: 'DEEPLINE_RUNTIME_SCHEDULER_DB_CIRCUIT_OPEN_MS',
33
+ recoveryMaxDefers: 'DEEPLINE_RUNTIME_RECOVERY_MAX_DEFERS',
34
+ recoveryHorizonMs: 'DEEPLINE_RUNTIME_RECOVERY_HORIZON_MS',
35
+ recoveryDeferBaseSeconds: 'DEEPLINE_RUNTIME_RECOVERY_DEFER_BASE_SECONDS',
36
+ recoveryDeferMaxSeconds: 'DEEPLINE_RUNTIME_RECOVERY_DEFER_MAX_SECONDS',
37
+ recoveryCircuitOpenMs: 'DEEPLINE_RUNTIME_RECOVERY_CIRCUIT_OPEN_MS',
38
+ recoveryCircuitProbeLeaseMs:
39
+ 'DEEPLINE_RUNTIME_RECOVERY_CIRCUIT_PROBE_LEASE_MS',
40
+ sandboxRunnerReadyTimeoutMs:
41
+ 'DEEPLINE_RUNTIME_SANDBOX_RUNNER_READY_TIMEOUT_MS',
42
+ modalSandboxCreateTimeoutMs:
43
+ 'DEEPLINE_RUNTIME_MODAL_SANDBOX_CREATE_TIMEOUT_MS',
44
+ daytonaCommandRecoveryTimeoutMs:
45
+ 'DEEPLINE_RUNTIME_DAYTONA_COMMAND_RECOVERY_TIMEOUT_MS',
46
+ daytonaCommandRecoveryPollMs:
47
+ 'DEEPLINE_RUNTIME_DAYTONA_COMMAND_RECOVERY_POLL_MS',
48
+ runnerControlRequestTimeoutMs:
49
+ 'DEEPLINE_RUNTIME_RUNNER_CONTROL_REQUEST_TIMEOUT_MS',
50
+ runnerControlMaxAttempts: 'DEEPLINE_RUNTIME_RUNNER_CONTROL_MAX_ATTEMPTS',
51
+ terminalPushRequestTimeoutMs:
52
+ 'DEEPLINE_RUNTIME_TERMINAL_PUSH_REQUEST_TIMEOUT_MS',
53
+ terminalPushMaxAttempts: 'DEEPLINE_RUNTIME_TERMINAL_PUSH_MAX_ATTEMPTS',
54
+ egressFetchMaxAttempts: 'DEEPLINE_RUNTIME_EGRESS_FETCH_MAX_ATTEMPTS',
55
+ egressFetchHeadersTimeoutMs:
56
+ 'DEEPLINE_RUNTIME_EGRESS_FETCH_HEADERS_TIMEOUT_MS',
57
+ egressFetchBodyTimeoutMs: 'DEEPLINE_RUNTIME_EGRESS_FETCH_BODY_TIMEOUT_MS',
58
+ egressFetchTotalTimeoutMs: 'DEEPLINE_RUNTIME_EGRESS_FETCH_TOTAL_TIMEOUT_MS',
59
+ } as const;
60
+
61
+ export type RuntimeReliabilityPolicy = Readonly<{
62
+ sevOverrideEnabled: boolean;
63
+ engine: Readonly<{
64
+ maxAttempts: number;
65
+ retryBaseSeconds: number;
66
+ retryMaxSeconds: number;
67
+ }>;
68
+ schedulerSubmit: Readonly<{ capacityMaxAttempts: number }>;
69
+ worker: Readonly<{ claimLeaseSeconds: number }>;
70
+ schedulerDatabase: Readonly<{
71
+ acquireTimeoutMs: number;
72
+ queryTimeoutMs: number;
73
+ retryAttempts: number;
74
+ retryBaseDelayMs: number;
75
+ retryMaxDelayMs: number;
76
+ circuitOpenMs: number;
77
+ }>;
78
+ recovery: Readonly<{
79
+ maxDefers: number;
80
+ horizonMs: number;
81
+ deferBaseSeconds: number;
82
+ deferMaxSeconds: number;
83
+ circuitOpenMs: number;
84
+ circuitProbeLeaseMs: number;
85
+ }>;
86
+ sandbox: Readonly<{
87
+ modalSandboxCreateTimeoutMs: number;
88
+ runnerReadyTimeoutMs: number;
89
+ daytonaCommandRecoveryTimeoutMs: number;
90
+ daytonaCommandRecoveryPollMs: number;
91
+ }>;
92
+ gateway: Readonly<{
93
+ runnerControlRequestTimeoutMs: number;
94
+ runnerControlMaxAttempts: number;
95
+ terminalPushRequestTimeoutMs: number;
96
+ terminalPushMaxAttempts: number;
97
+ }>;
98
+ egress: Readonly<{
99
+ fetchMaxAttempts: number;
100
+ fetchHeadersTimeoutMs: number;
101
+ fetchBodyTimeoutMs: number;
102
+ fetchTotalTimeoutMs: number;
103
+ }>;
104
+ }>;
105
+
106
+ type Env = Readonly<Record<string, string | undefined>>;
107
+
108
+ type NumberSetting = Readonly<{
109
+ name: string;
110
+ defaultValue: number;
111
+ min: number;
112
+ max: number;
113
+ }>;
114
+
115
+ function readProcessEnvironment(): Env {
116
+ return typeof process === 'undefined' ? {} : (process.env ?? {});
117
+ }
118
+
119
+ function enabled(raw: string | undefined): boolean {
120
+ return raw?.trim().toLowerCase() === 'enabled';
121
+ }
122
+
123
+ function resolveSetting(
124
+ env: Env,
125
+ sevOverrideEnabled: boolean,
126
+ setting: NumberSetting,
127
+ ): number {
128
+ const raw = env[setting.name]?.trim();
129
+ if (!raw) return setting.defaultValue;
130
+ if (!/^\d+$/.test(raw)) {
131
+ throw new Error(
132
+ `${setting.name} must be an integer between ${setting.min} and ${setting.max}.`,
133
+ );
134
+ }
135
+ const value = Number(raw);
136
+ if (
137
+ !Number.isSafeInteger(value) ||
138
+ value < setting.min ||
139
+ value > setting.max
140
+ ) {
141
+ throw new Error(
142
+ `${setting.name} must be an integer between ${setting.min} and ${setting.max}.`,
143
+ );
144
+ }
145
+ if (value !== setting.defaultValue && !sevOverrideEnabled) {
146
+ throw new Error(
147
+ `${setting.name} differs from its safe platform default. Set ${RUNTIME_RELIABILITY_SEV_OVERRIDE_ENV}=enabled only for an active SEV.`,
148
+ );
149
+ }
150
+ return value;
151
+ }
152
+
153
+ function setting(
154
+ name: string,
155
+ defaultValue: number,
156
+ min: number,
157
+ max: number,
158
+ ): NumberSetting {
159
+ return { name, defaultValue, min, max };
160
+ }
161
+
162
+ /** Pure resolver so tests can prove the deployment guard without mutating env. */
163
+ export function resolveRuntimeReliabilityPolicy(
164
+ env: Env = readProcessEnvironment(),
165
+ ): RuntimeReliabilityPolicy {
166
+ const sevOverrideEnabled = enabled(env[RUNTIME_RELIABILITY_SEV_OVERRIDE_ENV]);
167
+ const read = (name: string, defaultValue: number, min: number, max: number) =>
168
+ resolveSetting(
169
+ env,
170
+ sevOverrideEnabled,
171
+ setting(name, defaultValue, min, max),
172
+ );
173
+
174
+ const policy: RuntimeReliabilityPolicy = {
175
+ sevOverrideEnabled,
176
+ engine: {
177
+ maxAttempts: read(RUNTIME_RELIABILITY_ENV.engineMaxAttempts, 5, 1, 8),
178
+ retryBaseSeconds: read(
179
+ RUNTIME_RELIABILITY_ENV.engineRetryBaseSeconds,
180
+ 5,
181
+ 1,
182
+ 60,
183
+ ),
184
+ retryMaxSeconds: read(
185
+ RUNTIME_RELIABILITY_ENV.engineRetryMaxSeconds,
186
+ 60,
187
+ 5,
188
+ 300,
189
+ ),
190
+ },
191
+ schedulerSubmit: {
192
+ capacityMaxAttempts: read(
193
+ RUNTIME_RELIABILITY_ENV.schedulerSubmitCapacityMaxAttempts,
194
+ 8,
195
+ 1,
196
+ 12,
197
+ ),
198
+ },
199
+ worker: {
200
+ claimLeaseSeconds: read(
201
+ RUNTIME_RELIABILITY_ENV.claimLeaseSeconds,
202
+ 120,
203
+ 30,
204
+ 600,
205
+ ),
206
+ },
207
+ schedulerDatabase: {
208
+ acquireTimeoutMs: read(
209
+ RUNTIME_RELIABILITY_ENV.schedulerDbAcquireTimeoutMs,
210
+ 5_000,
211
+ 1_000,
212
+ 30_000,
213
+ ),
214
+ queryTimeoutMs: read(
215
+ RUNTIME_RELIABILITY_ENV.schedulerDbQueryTimeoutMs,
216
+ 30_000,
217
+ 5_000,
218
+ 120_000,
219
+ ),
220
+ retryAttempts: read(
221
+ RUNTIME_RELIABILITY_ENV.schedulerDbRetryAttempts,
222
+ 3,
223
+ 1,
224
+ 6,
225
+ ),
226
+ retryBaseDelayMs: read(
227
+ RUNTIME_RELIABILITY_ENV.schedulerDbRetryBaseDelayMs,
228
+ 50,
229
+ 10,
230
+ 5_000,
231
+ ),
232
+ retryMaxDelayMs: read(
233
+ RUNTIME_RELIABILITY_ENV.schedulerDbRetryMaxDelayMs,
234
+ 1_000,
235
+ 50,
236
+ 30_000,
237
+ ),
238
+ circuitOpenMs: read(
239
+ RUNTIME_RELIABILITY_ENV.schedulerDbCircuitOpenMs,
240
+ 2_000,
241
+ 500,
242
+ 60_000,
243
+ ),
244
+ },
245
+ recovery: {
246
+ maxDefers: read(RUNTIME_RELIABILITY_ENV.recoveryMaxDefers, 8, 1, 12),
247
+ horizonMs: read(
248
+ RUNTIME_RELIABILITY_ENV.recoveryHorizonMs,
249
+ 30 * 60_000,
250
+ 60_000,
251
+ 2 * 60 * 60_000,
252
+ ),
253
+ deferBaseSeconds: read(
254
+ RUNTIME_RELIABILITY_ENV.recoveryDeferBaseSeconds,
255
+ 15,
256
+ 1,
257
+ 300,
258
+ ),
259
+ deferMaxSeconds: read(
260
+ RUNTIME_RELIABILITY_ENV.recoveryDeferMaxSeconds,
261
+ 300,
262
+ 15,
263
+ 900,
264
+ ),
265
+ circuitOpenMs: read(
266
+ RUNTIME_RELIABILITY_ENV.recoveryCircuitOpenMs,
267
+ 60_000,
268
+ 5_000,
269
+ 5 * 60_000,
270
+ ),
271
+ circuitProbeLeaseMs: read(
272
+ RUNTIME_RELIABILITY_ENV.recoveryCircuitProbeLeaseMs,
273
+ 60_000,
274
+ 5_000,
275
+ 5 * 60_000,
276
+ ),
277
+ },
278
+ sandbox: {
279
+ modalSandboxCreateTimeoutMs: read(
280
+ RUNTIME_RELIABILITY_ENV.modalSandboxCreateTimeoutMs,
281
+ 60_000,
282
+ 5_000,
283
+ 5 * 60_000,
284
+ ),
285
+ runnerReadyTimeoutMs: read(
286
+ RUNTIME_RELIABILITY_ENV.sandboxRunnerReadyTimeoutMs,
287
+ 30_000,
288
+ 5_000,
289
+ 5 * 60_000,
290
+ ),
291
+ daytonaCommandRecoveryTimeoutMs: read(
292
+ RUNTIME_RELIABILITY_ENV.daytonaCommandRecoveryTimeoutMs,
293
+ 60_000,
294
+ 5_000,
295
+ 5 * 60_000,
296
+ ),
297
+ daytonaCommandRecoveryPollMs: read(
298
+ RUNTIME_RELIABILITY_ENV.daytonaCommandRecoveryPollMs,
299
+ 2_000,
300
+ 100,
301
+ 30_000,
302
+ ),
303
+ },
304
+ gateway: {
305
+ runnerControlRequestTimeoutMs: read(
306
+ RUNTIME_RELIABILITY_ENV.runnerControlRequestTimeoutMs,
307
+ 20_000,
308
+ 1_000,
309
+ 120_000,
310
+ ),
311
+ runnerControlMaxAttempts: read(
312
+ RUNTIME_RELIABILITY_ENV.runnerControlMaxAttempts,
313
+ 3,
314
+ 1,
315
+ 6,
316
+ ),
317
+ terminalPushRequestTimeoutMs: read(
318
+ RUNTIME_RELIABILITY_ENV.terminalPushRequestTimeoutMs,
319
+ 15_000,
320
+ 1_000,
321
+ 120_000,
322
+ ),
323
+ terminalPushMaxAttempts: read(
324
+ RUNTIME_RELIABILITY_ENV.terminalPushMaxAttempts,
325
+ 5,
326
+ 1,
327
+ 8,
328
+ ),
329
+ },
330
+ egress: {
331
+ fetchMaxAttempts: read(
332
+ RUNTIME_RELIABILITY_ENV.egressFetchMaxAttempts,
333
+ 3,
334
+ 1,
335
+ 5,
336
+ ),
337
+ fetchHeadersTimeoutMs: read(
338
+ RUNTIME_RELIABILITY_ENV.egressFetchHeadersTimeoutMs,
339
+ 30_000,
340
+ 1_000,
341
+ 5 * 60_000,
342
+ ),
343
+ fetchBodyTimeoutMs: read(
344
+ RUNTIME_RELIABILITY_ENV.egressFetchBodyTimeoutMs,
345
+ 30_000,
346
+ 1_000,
347
+ 5 * 60_000,
348
+ ),
349
+ fetchTotalTimeoutMs: read(
350
+ RUNTIME_RELIABILITY_ENV.egressFetchTotalTimeoutMs,
351
+ 120_000,
352
+ 5_000,
353
+ 15 * 60_000,
354
+ ),
355
+ },
356
+ };
357
+
358
+ if (policy.engine.retryMaxSeconds < policy.engine.retryBaseSeconds) {
359
+ throw new Error('Engine retry maximum must be at least the retry base.');
360
+ }
361
+ if (
362
+ policy.schedulerDatabase.retryMaxDelayMs <
363
+ policy.schedulerDatabase.retryBaseDelayMs
364
+ ) {
365
+ throw new Error(
366
+ 'Scheduler DB retry maximum must be at least the retry base.',
367
+ );
368
+ }
369
+ if (policy.recovery.deferMaxSeconds < policy.recovery.deferBaseSeconds) {
370
+ throw new Error('Recovery defer maximum must be at least the defer base.');
371
+ }
372
+ if (
373
+ policy.recovery.circuitProbeLeaseMs <
374
+ policy.sandbox.modalSandboxCreateTimeoutMs
375
+ ) {
376
+ throw new Error(
377
+ 'Recovery circuit probe lease must cover the Modal sandbox create deadline.',
378
+ );
379
+ }
380
+ if (
381
+ policy.egress.fetchTotalTimeoutMs < policy.egress.fetchHeadersTimeoutMs ||
382
+ policy.egress.fetchTotalTimeoutMs < policy.egress.fetchBodyTimeoutMs
383
+ ) {
384
+ throw new Error(
385
+ 'Egress total timeout must cover both headers and body deadlines.',
386
+ );
387
+ }
388
+ return policy;
389
+ }
390
+
391
+ export const RUNTIME_RELIABILITY_POLICY = resolveRuntimeReliabilityPolicy();
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Operator-owned limits for the shared Play Runtime traffic path.
3
+ *
4
+ * The compiled defaults are intentionally permissive and safe. A live
5
+ * override is a short-lived incident control, stored in Convex and read by the
6
+ * worker before it can create a sandbox. It can only reduce new work; it can
7
+ * never make retries, side effects, or provider limits less safe.
8
+ */
9
+
10
+ export const RUNTIME_TRAFFIC_POLICY_SCOPE = 'play_runtime' as const;
11
+
12
+ export const RUNTIME_TRAFFIC_POLICY_LIMITS = {
13
+ /** An operator must renew a mitigation rather than accidentally leave it on. */
14
+ maxOverrideDurationMs: 2 * 60 * 60_000,
15
+ /** A global start throttle may only shed load, never increase it. */
16
+ maxSandboxStartsPerMinute: 600,
17
+ } as const;
18
+
19
+ export type RuntimeTrafficAdmissionMode = 'normal' | 'hold_new_sandbox_starts';
20
+
21
+ export type RuntimeTrafficPolicy = Readonly<{
22
+ scope: typeof RUNTIME_TRAFFIC_POLICY_SCOPE;
23
+ version: number;
24
+ admissionMode: RuntimeTrafficAdmissionMode;
25
+ /** Null means no extra global pacing beyond the compiled runtime policy. */
26
+ sandboxStartsPerMinute: number | null;
27
+ expiresAt: number | null;
28
+ reason: string | null;
29
+ updatedAt: number | null;
30
+ }>;
31
+
32
+ export const DEFAULT_RUNTIME_TRAFFIC_POLICY: RuntimeTrafficPolicy = {
33
+ scope: RUNTIME_TRAFFIC_POLICY_SCOPE,
34
+ version: 0,
35
+ admissionMode: 'normal',
36
+ sandboxStartsPerMinute: null,
37
+ expiresAt: null,
38
+ reason: null,
39
+ updatedAt: null,
40
+ };
41
+
42
+ /**
43
+ * The worker reads this value over an app-runtime boundary. Treat a malformed
44
+ * successful response as an unavailable control plane, rather than allowing
45
+ * JavaScript's `undefined` coercions to accidentally turn it into a throttle.
46
+ */
47
+ export function isRuntimeTrafficPolicy(
48
+ value: unknown,
49
+ ): value is RuntimeTrafficPolicy {
50
+ if (!value || typeof value !== 'object') return false;
51
+ const candidate = value as Record<string, unknown>;
52
+ const sandboxStartsPerMinute = candidate.sandboxStartsPerMinute;
53
+ return (
54
+ candidate.scope === RUNTIME_TRAFFIC_POLICY_SCOPE &&
55
+ typeof candidate.version === 'number' &&
56
+ Number.isSafeInteger(candidate.version) &&
57
+ candidate.version >= 0 &&
58
+ (candidate.admissionMode === 'normal' ||
59
+ candidate.admissionMode === 'hold_new_sandbox_starts') &&
60
+ (sandboxStartsPerMinute === null ||
61
+ (typeof sandboxStartsPerMinute === 'number' &&
62
+ Number.isSafeInteger(sandboxStartsPerMinute) &&
63
+ sandboxStartsPerMinute >= 1 &&
64
+ sandboxStartsPerMinute <=
65
+ RUNTIME_TRAFFIC_POLICY_LIMITS.maxSandboxStartsPerMinute)) &&
66
+ (candidate.expiresAt === null ||
67
+ (typeof candidate.expiresAt === 'number' &&
68
+ Number.isSafeInteger(candidate.expiresAt))) &&
69
+ (candidate.reason === null || typeof candidate.reason === 'string') &&
70
+ (candidate.updatedAt === null ||
71
+ (typeof candidate.updatedAt === 'number' &&
72
+ Number.isSafeInteger(candidate.updatedAt)))
73
+ );
74
+ }
75
+
76
+ export function runtimeTrafficPolicyIsActive(
77
+ policy: Pick<RuntimeTrafficPolicy, 'expiresAt'>,
78
+ nowMs = Date.now(),
79
+ ): boolean {
80
+ return policy.expiresAt === null || policy.expiresAt > nowMs;
81
+ }
82
+
83
+ /**
84
+ * Expired rows remain as audit evidence but must behave exactly like no
85
+ * override. Keeping this pure makes the app, worker, and CLI agree without
86
+ * relying on a best-effort expiry job.
87
+ */
88
+ export function effectiveRuntimeTrafficPolicy(
89
+ policy: RuntimeTrafficPolicy | null | undefined,
90
+ nowMs = Date.now(),
91
+ ): RuntimeTrafficPolicy {
92
+ if (!policy || !runtimeTrafficPolicyIsActive(policy, nowMs)) {
93
+ return DEFAULT_RUNTIME_TRAFFIC_POLICY;
94
+ }
95
+ return policy;
96
+ }
97
+
98
+ export function validateRuntimeTrafficOverride(input: {
99
+ admissionMode: RuntimeTrafficAdmissionMode;
100
+ sandboxStartsPerMinute: number | null;
101
+ expiresAt: number;
102
+ nowMs?: number;
103
+ }): void {
104
+ const nowMs = input.nowMs ?? Date.now();
105
+ if (!Number.isSafeInteger(input.expiresAt) || input.expiresAt <= nowMs) {
106
+ throw new Error('Runtime traffic override expiry must be in the future.');
107
+ }
108
+ if (
109
+ input.expiresAt - nowMs >
110
+ RUNTIME_TRAFFIC_POLICY_LIMITS.maxOverrideDurationMs
111
+ ) {
112
+ throw new Error('Runtime traffic overrides may last at most two hours.');
113
+ }
114
+ if (
115
+ input.sandboxStartsPerMinute !== null &&
116
+ (!Number.isSafeInteger(input.sandboxStartsPerMinute) ||
117
+ input.sandboxStartsPerMinute < 1 ||
118
+ input.sandboxStartsPerMinute >
119
+ RUNTIME_TRAFFIC_POLICY_LIMITS.maxSandboxStartsPerMinute)
120
+ ) {
121
+ throw new Error(
122
+ `sandboxStartsPerMinute must be a whole number from 1 to ${RUNTIME_TRAFFIC_POLICY_LIMITS.maxSandboxStartsPerMinute}.`,
123
+ );
124
+ }
125
+ }
@@ -86,6 +86,27 @@ export function resolveRuntimeSandboxComputeItems(input: {
86
86
  return items;
87
87
  }
88
88
 
89
+ /**
90
+ * A provider create may finish after the original run has terminalized. Those
91
+ * resources cannot be appended to the run's already-finalized billing
92
+ * session, so the scheduler settles each through a stable adjustment session.
93
+ */
94
+ export function resolveLateRuntimeSandboxComputeItems(input: {
95
+ resources: readonly unknown[];
96
+ endedAt: number;
97
+ }): ComputeBillingItem[] {
98
+ return resolveRuntimeSandboxComputeItems({
99
+ resources: input.resources.filter(
100
+ (resource) =>
101
+ Boolean(resource) &&
102
+ typeof resource === 'object' &&
103
+ !Array.isArray(resource) &&
104
+ (resource as Record<string, unknown>).lateAcquired === true,
105
+ ),
106
+ endedAt: input.endedAt,
107
+ });
108
+ }
109
+
89
110
  /** Project executor resource facts into one idempotent item per real sandbox. */
90
111
  export function resolveDaytonaRuntimeComputeItems(input: {
91
112
  resources: readonly unknown[];
@@ -1,37 +1,12 @@
1
1
  import { getRuntimeEnv } from '../runtime-env';
2
2
  import type { PlayRunnerEvent } from './protocol';
3
+ import {
4
+ runtimeTestFaultDrills,
5
+ type RuntimeTestFaultName,
6
+ } from './runtime-incident-drills';
3
7
 
4
8
  export const PLAY_RUNTIME_TEST_FAULT_HEADER = 'x-deepline-test-fault';
5
9
 
6
- export type RuntimeTestFaultName =
7
- | 'receipt_complete_write_fail'
8
- | 'receipt_fail_write_fail'
9
- | 'worker_receipt_complete_write_fail'
10
- | 'invocation_response_delivery_abort'
11
- /**
12
- * Holds an already checked-out receipt-gateway scheduler client for the
13
- * supplied bounded millisecond value. Preview/CI only: this creates a
14
- * deterministic real-Pg-pool pressure window without changing an action's
15
- * SQL or outcome.
16
- */
17
- | 'receipt_gateway_hold_ms'
18
- /**
19
- * Synthetic-only bulk-claim fault. The runner forwards this token on the
20
- * first qualifying physical claim request only. The gateway holds the real
21
- * checked-out scheduler client for the supplied milliseconds, allowing the
22
- * claim-operation deadline and connection destruction path to be exercised
23
- * end to end without making the fault Machine-local.
24
- */
25
- | 'receipt_claim_query_hold_once_ms'
26
- /**
27
- * Synthetic-only response-boundary fault. The runner fully receives a
28
- * successful bulk receipt-claim response, then reports the configured first
29
- * N physical requests as request timeouts. This proves ambiguous-delivery
30
- * replay without depending on which gateway Machine accepted the request.
31
- */
32
- | 'receipt_claim_response_timeout'
33
- | 'runtime_sheet_page_tail_hold_ms';
34
-
35
10
  export type RuntimeTestFaultRegistry = {
36
11
  consume(name: RuntimeTestFaultName): boolean;
37
12
  remaining(): Record<string, number>;
@@ -86,16 +61,9 @@ type ValidatedRuntimeTestPolicyOverrides =
86
61
  | { ok: true; overrides: RuntimeTestPolicyOverrides | null }
87
62
  | { ok: false; status: 400 | 403; error: string };
88
63
 
89
- const SUPPORTED_RUNTIME_TEST_FAULTS = new Set<RuntimeTestFaultName>([
90
- 'receipt_complete_write_fail',
91
- 'receipt_fail_write_fail',
92
- 'worker_receipt_complete_write_fail',
93
- 'invocation_response_delivery_abort',
94
- 'receipt_gateway_hold_ms',
95
- 'receipt_claim_query_hold_once_ms',
96
- 'receipt_claim_response_timeout',
97
- 'runtime_sheet_page_tail_hold_ms',
98
- ]);
64
+ const SUPPORTED_RUNTIME_TEST_FAULTS = new Set<RuntimeTestFaultName>(
65
+ runtimeTestFaultDrills().map(({ id }) => id),
66
+ );
99
67
 
100
68
  const RUNTIME_TEST_POLICY_MS_FIELDS = new Set([
101
69
  'receiptLeaseTtlMs',
@@ -181,6 +149,35 @@ export function recognizedRuntimeTestFaultCount(
181
149
  return parsed.get(name) ?? 0;
182
150
  }
183
151
 
152
+ /**
153
+ * A provider-create fault cannot use process-local "already consumed" state:
154
+ * a deferred Play Run normally resumes on another worker. Instead, the
155
+ * persisted scheduler attempt is its consumption fence. `:1` means inject on
156
+ * generation one only; `:N` intentionally selects generation N for diagnosis.
157
+ *
158
+ * The header itself is admitted only through `parseRuntimeTestFaultCounts` at
159
+ * run admission. This second production guard ensures a persisted historical
160
+ * test header can never synthesize a provider failure in a live runtime.
161
+ */
162
+ export function shouldInjectModalSandboxCreateResourceExhausted(input: {
163
+ runtimeTestFaultHeader: string | null | undefined;
164
+ runAttempt: number | null | undefined;
165
+ }): boolean {
166
+ if (getRuntimeEnv() === 'prod') return false;
167
+ const requestedAttempt = recognizedRuntimeTestFaultCount(
168
+ input.runtimeTestFaultHeader,
169
+ 'modal_sandbox_create_resource_exhausted',
170
+ );
171
+ if (requestedAttempt < 1) return false;
172
+ const runAttempt = input.runAttempt;
173
+ return (
174
+ typeof runAttempt === 'number' &&
175
+ Number.isSafeInteger(runAttempt) &&
176
+ runAttempt >= 1 &&
177
+ runAttempt === requestedAttempt
178
+ );
179
+ }
180
+
184
181
  export function runtimeSheetPageTailRowsContainTarget(
185
182
  rows: readonly unknown[],
186
183
  ): boolean {