deepline 0.3.17 → 0.3.19

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 +76 -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
@@ -24,11 +24,92 @@ import type { PlaySandboxRuntimeLimits } from '@shared_libs/play-runtime/sandbox
24
24
  import { validateDaytonaExecutionContext } from './daytona-lifecycle';
25
25
  import { stageRunnerPayload } from './daytona-payload-transport';
26
26
  import { captureDetachedDaytonaRunnerReadinessBaseline } from './daytona-session-execution';
27
+ import {
28
+ isPlayRunRecoveryError,
29
+ recoveryForOpenModalCircuit,
30
+ recoveryForModalSandboxCreateFailure,
31
+ } from '@shared_libs/play-runtime/play-run-recovery-policy';
32
+ import { shouldInjectModalSandboxCreateResourceExhausted } from '@shared_libs/play-runtime/test-runtime-seams';
33
+ import { RUNTIME_RELIABILITY_POLICY } from '@shared_libs/play-runtime/runtime-reliability-policy';
27
34
 
28
- const MODAL_RUNNER_READY_TIMEOUT_MS = 30_000;
35
+ const MODAL_RUNNER_READY_TIMEOUT_MS =
36
+ RUNTIME_RELIABILITY_POLICY.sandbox.runnerReadyTimeoutMs;
37
+ const MODAL_SANDBOX_CREATE_TIMEOUT_MS =
38
+ RUNTIME_RELIABILITY_POLICY.sandbox.modalSandboxCreateTimeoutMs;
29
39
  const MODAL_CAPACITY_RELEASE_RETRY_DELAYS_MS = [0, 100, 500, 2_000] as const;
40
+ const MODAL_LATE_CREATE_PERSIST_RETRY_DELAYS_MS = [0, 100, 500, 2_000] as const;
30
41
  type ModalSandbox = Awaited<ReturnType<ModalClient['sandboxes']['create']>>;
31
42
 
43
+ /**
44
+ * A create RPC that outlives its client deadline is fundamentally ambiguous:
45
+ * Modal may still create the sandbox after the caller stops waiting. It is
46
+ * therefore neither a safe fresh retry nor a capacity rejection. The caller
47
+ * must terminalize/reconcile it and retain any local capacity reservation
48
+ * until a later provider-confirmed cleanup can release it.
49
+ */
50
+ export class ModalSandboxCreateOutcomeUnknownError extends Error {
51
+ constructor(readonly timeoutMs: number) {
52
+ super(
53
+ `RUNTIME_MODAL_SANDBOX_CREATE_OUTCOME_UNKNOWN: Modal SandboxCreate did not settle within ${timeoutMs}ms. ` +
54
+ 'No fresh sandbox retry is safe because the provider may still create the requested sandbox.',
55
+ );
56
+ this.name = 'ModalSandboxCreateOutcomeUnknownError';
57
+ }
58
+ }
59
+
60
+ export async function awaitModalSandboxCreate<T>(input: {
61
+ create: Promise<T>;
62
+ timeoutMs?: number;
63
+ onLateSuccess?: (value: T) => Promise<void> | void;
64
+ onLateFailure?: (error: unknown) => Promise<void> | void;
65
+ }): Promise<T> {
66
+ const timeoutMs = input.timeoutMs ?? MODAL_SANDBOX_CREATE_TIMEOUT_MS;
67
+ let settled = false;
68
+ let timer: ReturnType<typeof setTimeout> | undefined;
69
+ return new Promise<T>((resolve, reject) => {
70
+ timer = setTimeout(() => {
71
+ settled = true;
72
+ reject(new ModalSandboxCreateOutcomeUnknownError(timeoutMs));
73
+ }, timeoutMs);
74
+ void input.create.then(
75
+ (value) => {
76
+ if (!settled) {
77
+ settled = true;
78
+ if (timer) clearTimeout(timer);
79
+ resolve(value);
80
+ return;
81
+ }
82
+ void Promise.resolve(input.onLateSuccess?.(value)).catch((error) => {
83
+ console.error('[play-runner.modal] late_create_cleanup_failed', {
84
+ error: error instanceof Error ? error.message : String(error),
85
+ });
86
+ });
87
+ },
88
+ (error) => {
89
+ if (!settled) {
90
+ settled = true;
91
+ if (timer) clearTimeout(timer);
92
+ reject(error);
93
+ return;
94
+ }
95
+ void Promise.resolve(input.onLateFailure?.(error)).catch(
96
+ (callbackError) => {
97
+ console.error(
98
+ '[play-runner.modal] late_create_failure_cleanup_failed',
99
+ {
100
+ error:
101
+ callbackError instanceof Error
102
+ ? callbackError.message
103
+ : String(callbackError),
104
+ },
105
+ );
106
+ },
107
+ );
108
+ },
109
+ );
110
+ });
111
+ }
112
+
32
113
  export function modalSandboxLifetimeMs(
33
114
  limits: PlaySandboxRuntimeLimits,
34
115
  ): number {
@@ -137,6 +218,37 @@ async function releaseModalSandboxCapacityAfterConfirmedTermination(input: {
137
218
  throw lastError;
138
219
  }
139
220
 
221
+ /**
222
+ * A create that settles after its caller timed out is already an external
223
+ * fact. Persist it before attempting any further provider operation: the
224
+ * durable cleanup controller then owns deletion, capacity release, and the
225
+ * late billing adjustment. A short retry absorbs a transient scheduler-store
226
+ * outage without turning this into a fresh Play attempt.
227
+ */
228
+ async function persistLateModalSandboxResource(input: {
229
+ resource: Parameters<
230
+ NonNullable<PlayRunnerCallbacks['onLateRuntimeResourceAcquired']>
231
+ >[0];
232
+ record: NonNullable<PlayRunnerCallbacks['onLateRuntimeResourceAcquired']>;
233
+ }): Promise<void> {
234
+ let lastError: unknown;
235
+ for (const delayMs of MODAL_LATE_CREATE_PERSIST_RETRY_DELAYS_MS) {
236
+ if (delayMs > 0) {
237
+ await new Promise<void>((resolve) => setTimeout(resolve, delayMs));
238
+ }
239
+ try {
240
+ await input.record(input.resource);
241
+ return;
242
+ } catch (error) {
243
+ lastError = error;
244
+ }
245
+ }
246
+ throw new AggregateError(
247
+ [lastError],
248
+ 'RUNTIME_MODAL_LATE_CREATE_PERSIST_FAILED: Modal created a sandbox after the caller deadline, but its durable cleanup obligation could not be recorded.',
249
+ );
250
+ }
251
+
140
252
  export const modalPlayRunnerBackend: PlayRunnerBackend = {
141
253
  async execute(config, callbacks) {
142
254
  let runtimeResourceRegistrationError: unknown;
@@ -160,6 +272,11 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
160
272
  const startedAt = Date.now();
161
273
  const runtimeTiming: PlayRunnerRuntimeTiming = { backend: 'modal' };
162
274
  emitModalStage(config.context, 'create:start');
275
+ const providerCircuitClaim =
276
+ await callbacks?.shouldAttemptSandboxProvider?.('modal');
277
+ if (providerCircuitClaim && !providerCircuitClaim.allowed) {
278
+ throw recoveryForOpenModalCircuit();
279
+ }
163
280
  const modalConfig = await loadModalRequiredConfig({
164
281
  runtimeSchedulerSchema: config.context.runtimeSchedulerSchema,
165
282
  limits: config.context.sandboxRuntimeLimits,
@@ -170,33 +287,157 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
170
287
  const image = modalConfig.client.images.fromRegistry(modalConfig.image);
171
288
  const capacityReservation =
172
289
  await callbacks?.reserveSandboxCapacity?.('modal');
173
- const sandbox = await modalConfig.client.sandboxes
174
- .create(app, image, {
175
- cpu: MODAL_SANDBOX_CPU_CORES,
176
- cpuLimit: MODAL_SANDBOX_CPU_CORES,
177
- memoryMiB: MODAL_SANDBOX_MEMORY_MIB,
178
- memoryLimitMiB: MODAL_SANDBOX_MEMORY_MIB,
179
- timeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
180
- idleTimeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
181
- workdir: modalConfig.workdir,
182
- tags: {
183
- source: 'deepline-play-runner',
184
- orgId: config.context.orgId ?? 'unknown',
185
- workflowId: config.context.workflowId ?? 'unknown',
186
- runId: config.context.runId ?? 'unknown',
187
- },
188
- ...(modalConfig.outboundCidrAllowlist
189
- ? { outboundCidrAllowlist: modalConfig.outboundCidrAllowlist }
290
+ const createTags = {
291
+ source: 'deepline-play-runner',
292
+ orgId: config.context.orgId ?? 'unknown',
293
+ workflowId: config.context.workflowId ?? 'unknown',
294
+ runId: config.context.runId ?? 'unknown',
295
+ attempt: String(config.context.runAttempt ?? 0),
296
+ };
297
+ let sandbox: ModalSandbox;
298
+ try {
299
+ // This is deliberately adjacent to the real provider boundary. The
300
+ // reservation proves the scheduler observes the same no-resource
301
+ // cleanup path as a genuine Modal rejection; a later persisted
302
+ // attempt calls the real Modal SDK normally.
303
+ if (
304
+ shouldInjectModalSandboxCreateResourceExhausted({
305
+ runtimeTestFaultHeader: config.context.runtimeTestFaultHeader,
306
+ runAttempt: config.context.runAttempt,
307
+ })
308
+ ) {
309
+ throw Object.assign(
310
+ new Error(
311
+ 'SandboxCreate RESOURCE_EXHAUSTED: injected preview runtime test fault',
312
+ ),
313
+ { code: 8, details: 'RESOURCE_EXHAUSTED' },
314
+ );
315
+ }
316
+ const modalCreateRequestedAt = Date.now();
317
+ // A client timeout does not cancel Modal's create RPC. Persist the
318
+ // provider-scoped lookup key before beginning it, so a worker loss or
319
+ // failed late callback leaves an actionable reconciliation obligation
320
+ // rather than an invisible sandbox/capacity leak.
321
+ if (!callbacks?.onAmbiguousModalCreateIntent) {
322
+ throw new Error(
323
+ 'RUNTIME_MODAL_CREATE_RECONCILIATION_UNAVAILABLE: Modal create requires a durable ambiguity recorder.',
324
+ );
325
+ }
326
+ await callbacks.onAmbiguousModalCreateIntent({
327
+ modalAppId: app.appId,
328
+ runtimeEnvironment:
329
+ process.env.DEEPLINE_RUNTIME_ENVIRONMENT === 'preview'
330
+ ? 'preview'
331
+ : 'production',
332
+ tags: createTags,
333
+ ...(capacityReservation
334
+ ? { sandboxCapacityLeaseId: capacityReservation.leaseId }
190
335
  : {}),
191
- })
192
- .catch(async (error) => {
193
- if (capacityReservation) {
194
- await callbacks?.releaseSandboxCapacity?.(
195
- capacityReservation.leaseId,
336
+ });
337
+ sandbox = await awaitModalSandboxCreate({
338
+ create: modalConfig.client.sandboxes.create(app, image, {
339
+ cpu: MODAL_SANDBOX_CPU_CORES,
340
+ cpuLimit: MODAL_SANDBOX_CPU_CORES,
341
+ memoryMiB: MODAL_SANDBOX_MEMORY_MIB,
342
+ memoryLimitMiB: MODAL_SANDBOX_MEMORY_MIB,
343
+ timeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
344
+ idleTimeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
345
+ workdir: modalConfig.workdir,
346
+ tags: createTags,
347
+ ...(modalConfig.outboundCidrAllowlist
348
+ ? { outboundCidrAllowlist: modalConfig.outboundCidrAllowlist }
349
+ : {}),
350
+ }),
351
+ onLateSuccess: async (lateSandbox) => {
352
+ // Do not directly delete a late sandbox. First store it as a
353
+ // durable cleanup obligation, then let the normal cleanup worker
354
+ // perform provider-confirmed deletion, capacity release, and
355
+ // billing settlement. That remains recoverable if this process
356
+ // exits between any of those operations.
357
+ const record = callbacks?.onLateRuntimeResourceAcquired;
358
+ if (!record) {
359
+ throw new Error(
360
+ 'RUNTIME_MODAL_LATE_CREATE_PERSIST_UNAVAILABLE: Modal created a sandbox after the caller deadline, but no durable resource recorder is configured.',
361
+ );
362
+ }
363
+ await persistLateModalSandboxResource({
364
+ record,
365
+ resource: {
366
+ kind: 'modal_sandbox',
367
+ sandboxId: lateSandbox.sandboxId,
368
+ runtimeEnvironment:
369
+ process.env.DEEPLINE_RUNTIME_ENVIRONMENT === 'preview'
370
+ ? 'preview'
371
+ : 'production',
372
+ modalAppId: app.appId,
373
+ billingStartedAt: modalCreateRequestedAt,
374
+ lateAcquired: true,
375
+ maxBillingDurationSeconds:
376
+ modalConfig.limits.timeoutSeconds +
377
+ PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
378
+ cpu: MODAL_SANDBOX_CPU_CORES,
379
+ memoryGiB: MODAL_SANDBOX_MEMORY_MIB / 1024,
380
+ diskGiB: 0,
381
+ ...(capacityReservation
382
+ ? { sandboxCapacityLeaseId: capacityReservation.leaseId }
383
+ : {}),
384
+ },
385
+ });
386
+ },
387
+ onLateFailure: async (lateError) => {
388
+ console.warn('[play-runner.modal] late_create_failed', {
389
+ error:
390
+ lateError instanceof Error
391
+ ? lateError.message
392
+ : String(lateError),
393
+ });
394
+ // The provider has now rejected the timed-out create, proving no
395
+ // sandbox exists. Release the provisional lease instead of
396
+ // holding organization capacity until the crash-backstop TTL.
397
+ if (capacityReservation && callbacks?.releaseSandboxCapacity) {
398
+ await releaseModalSandboxCapacityAfterConfirmedTermination({
399
+ leaseId: capacityReservation.leaseId,
400
+ release: callbacks.releaseSandboxCapacity,
401
+ });
402
+ }
403
+ // The late rejection is provider-confirmed absence. Do not leave
404
+ // the pre-create selector pending merely because it crossed the
405
+ // caller deadline; otherwise the durable reconciler scans a fact
406
+ // we already know is safe.
407
+ await callbacks?.onAmbiguousModalCreateResolved?.();
408
+ },
409
+ });
410
+ } catch (error) {
411
+ if (
412
+ capacityReservation &&
413
+ !(error instanceof ModalSandboxCreateOutcomeUnknownError)
414
+ ) {
415
+ await callbacks?.releaseSandboxCapacity?.(
416
+ capacityReservation.leaseId,
417
+ );
418
+ }
419
+ // A settled rejection is known-safe; a timeout deliberately keeps the
420
+ // pre-create reconciliation obligation pending for recovery.
421
+ if (!(error instanceof ModalSandboxCreateOutcomeUnknownError)) {
422
+ try {
423
+ await callbacks?.onAmbiguousModalCreateResolved?.();
424
+ } catch (reconciliationError) {
425
+ console.warn(
426
+ '[play-runner.modal] create_reconciliation_resolve_failed',
427
+ {
428
+ error:
429
+ reconciliationError instanceof Error
430
+ ? reconciliationError.message
431
+ : String(reconciliationError),
432
+ },
196
433
  );
197
434
  }
198
- throw error;
199
- });
435
+ }
436
+ // A create rejection proves no sandbox and no customer code exist.
437
+ // Preserve that fact for the scheduler rather than converting it
438
+ // into a terminal runner result below.
439
+ throw recoveryForModalSandboxCreateFailure(error) ?? error;
440
+ }
200
441
  const billingStartedAt = Date.now();
201
442
  runtimeTiming.modalCreateMs = billingStartedAt - startedAt;
202
443
  emitModalStage(config.context, 'create:done', {
@@ -237,6 +478,46 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
237
478
  ? { sandboxCapacityLeaseId: capacityReservation.leaseId }
238
479
  : {}),
239
480
  });
481
+ // Do not clear the pre-create obligation until the sandbox itself
482
+ // is durable. A process failure between Modal's success response and
483
+ // resource recording must remain reconciliable rather than looking
484
+ // like a known-safe rejection.
485
+ try {
486
+ await callbacks?.onAmbiguousModalCreateResolved?.();
487
+ } catch (reconciliationError) {
488
+ // The resource record is the authority now. Leaving this intent
489
+ // pending is conservative and observable; do not turn a settled
490
+ // resource into a fresh execution path just because the advisory
491
+ // intent cleanup was temporarily unavailable.
492
+ console.warn(
493
+ '[play-runner.modal] create_reconciliation_resolve_failed',
494
+ {
495
+ sandboxId: sandbox.sandboxId,
496
+ error:
497
+ reconciliationError instanceof Error
498
+ ? reconciliationError.message
499
+ : String(reconciliationError),
500
+ },
501
+ );
502
+ }
503
+ // Circuit state is an operational hint, not part of the durable
504
+ // resource transaction. A successful resource record proves this
505
+ // provider path is healthy even if the best-effort circuit close
506
+ // cannot reach the scheduler control plane right now.
507
+ try {
508
+ await callbacks?.markSandboxProviderHealthy?.(
509
+ 'modal',
510
+ providerCircuitClaim?.probeToken ?? null,
511
+ );
512
+ } catch (circuitError) {
513
+ console.warn('[play-runner.modal] recovery_circuit_close_failed', {
514
+ sandboxId: sandbox.sandboxId,
515
+ error:
516
+ circuitError instanceof Error
517
+ ? circuitError.message
518
+ : String(circuitError),
519
+ });
520
+ }
240
521
  } catch (error) {
241
522
  runtimeResourceRegistrationError = error;
242
523
  throw error;
@@ -278,6 +559,10 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
278
559
  cancellationPromise,
279
560
  ]);
280
561
  runtimeTiming.modalExecuteMs = Date.now() - executeStartedAt;
562
+ // A readiness observation after the command is accepted is ambiguous:
563
+ // the detached runner may already have received its durable park and
564
+ // started customer code while this worker cannot read the scheduler.
565
+ // Never translate that ambiguity into a fresh sandbox retry.
281
566
  await confirmDetachedModalRunnerReady({
282
567
  readiness: readRunnerReadiness,
283
568
  baselineHeartbeatAt,
@@ -372,7 +657,8 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
372
657
  if (
373
658
  error === runtimeResourceRegistrationError ||
374
659
  error instanceof RuntimeResourceFenceLostError ||
375
- isRuntimeSandboxCapacityLimitError(error)
660
+ isRuntimeSandboxCapacityLimitError(error) ||
661
+ isPlayRunRecoveryError(error)
376
662
  ) {
377
663
  throw error;
378
664
  }
@@ -431,6 +717,38 @@ export async function deleteModalSandboxById(input: {
431
717
  }
432
718
  }
433
719
 
720
+ /**
721
+ * Read the provider inventory using the exact app + tag selector persisted
722
+ * before a Modal create call. This is deliberately a tiny provider adapter:
723
+ * the scheduler owns claim/fencing, resource recording, cleanup, and policy.
724
+ */
725
+ export async function findModalSandboxIdsByTags(input: {
726
+ modalAppId: string;
727
+ tags: Record<string, string>;
728
+ environment: 'preview' | 'production';
729
+ }): Promise<string[]> {
730
+ const modalAppId = input.modalAppId.trim();
731
+ if (!modalAppId) throw new Error('Modal reconciliation requires modalAppId.');
732
+ const tags = Object.fromEntries(
733
+ Object.entries(input.tags).filter(
734
+ ([key, value]) => key.trim() && value.trim(),
735
+ ),
736
+ );
737
+ if (Object.keys(tags).length === 0) {
738
+ throw new Error('Modal reconciliation requires non-empty create tags.');
739
+ }
740
+ const { client } = await loadModalClientConfig();
741
+ const sandboxIds: string[] = [];
742
+ for await (const sandbox of client.sandboxes.list({
743
+ appId: modalAppId,
744
+ tags,
745
+ environment: input.environment,
746
+ })) {
747
+ if (sandbox.sandboxId?.trim()) sandboxIds.push(sandbox.sandboxId.trim());
748
+ }
749
+ return [...new Set(sandboxIds)];
750
+ }
751
+
434
752
  export async function readDetachedModalRuntimeCompletion(input: {
435
753
  sandboxId: string;
436
754
  runtimeCompletedPath: string;
@@ -21,6 +21,13 @@ export type PlayRunnerRuntimeResource = {
21
21
  modalAppId?: string;
22
22
  billingStartedAt: number;
23
23
  billingEndedAt?: number | null;
24
+ /**
25
+ * The provider create settled only after the owning scheduler attempt had
26
+ * already timed out. This resource is billed through its own durable
27
+ * adjustment session; it must never be silently folded into an already
28
+ * finalized run session.
29
+ */
30
+ lateAcquired?: boolean;
24
31
  /** Customer liability ceiling for this physical sandbox. */
25
32
  maxBillingDurationSeconds?: number | null;
26
33
  terminalReason?: RuntimeResourceTerminalReason | null;
@@ -32,6 +39,19 @@ export type PlayRunnerRuntimeResource = {
32
39
  sandboxCapacityLeaseId?: string;
33
40
  };
34
41
 
42
+ /**
43
+ * Durable identity for a Modal create that can outlive the caller's timeout.
44
+ * These fields are only provider routing and correlation data; payloads,
45
+ * commands, and credentials never cross this boundary.
46
+ */
47
+ export type PlayRunnerAmbiguousModalCreateIntent = {
48
+ modalAppId: string;
49
+ runtimeEnvironment: 'preview' | 'production';
50
+ tags: Record<string, string>;
51
+ /** The provisional soft-capacity reservation, if admission is enabled. */
52
+ sandboxCapacityLeaseId?: string;
53
+ };
54
+
35
55
  export type RuntimeResourceTerminalReason =
36
56
  | 'completed'
37
57
  | 'suspended'
@@ -91,12 +111,42 @@ export interface PlayRunnerCallbacks {
91
111
  onRuntimeResourceAcquired?: (
92
112
  resource: PlayRunnerRuntimeResource,
93
113
  ) => void | Promise<void>;
114
+ /**
115
+ * A provider create timed out locally, then later produced a sandbox which
116
+ * could not be deleted. Persist it as a cleanup obligation even though the
117
+ * original attempt may already be terminal; it must never be an invisible
118
+ * provider resource.
119
+ */
120
+ onLateRuntimeResourceAcquired?: (
121
+ resource: PlayRunnerRuntimeResource,
122
+ ) => void | Promise<void>;
123
+ /**
124
+ * Record a reconciliation obligation before Modal create begins. It is
125
+ * resolved only after a known rejection or a durably recorded sandbox.
126
+ */
127
+ onAmbiguousModalCreateIntent?: (
128
+ intent: PlayRunnerAmbiguousModalCreateIntent,
129
+ ) => void | Promise<void>;
130
+ onAmbiguousModalCreateResolved?: () => void | Promise<void>;
94
131
  /** Reserve one physical-sandbox slot immediately before a provider create. */
95
132
  reserveSandboxCapacity?: (
96
133
  provider: 'daytona' | 'modal',
97
134
  ) => Promise<{ leaseId: string }>;
98
135
  /** Release an unbound reservation when provider create never produced a resource. */
99
136
  releaseSandboxCapacity?: (leaseId: string) => Promise<void>;
137
+ /**
138
+ * Scheduler-owned dependency circuit admission. A false result means no
139
+ * provider call is safe to make yet; the backend must return/throw a typed
140
+ * pre-code recovery outcome instead.
141
+ */
142
+ shouldAttemptSandboxProvider?: (
143
+ provider: 'daytona' | 'modal',
144
+ ) => Promise<{ allowed: boolean; probeToken: string | null }>;
145
+ /** A durably recorded resource may close only the probe that owns it. */
146
+ markSandboxProviderHealthy?: (
147
+ provider: 'daytona' | 'modal',
148
+ probeToken: string | null,
149
+ ) => Promise<void>;
100
150
  /**
101
151
  * Durable scheduler-owned evidence for a provider create call. Backends
102
152
  * await this callback at the create boundary; it is not best-effort logging.
@@ -105,8 +105,8 @@ import {
105
105
  import {
106
106
  parseRuntimeTestFaultCounts,
107
107
  PLAY_RUNTIME_TEST_FAULT_HEADER,
108
- type RuntimeTestFaultName,
109
108
  } from './test-runtime-seams';
109
+ import type { RuntimeTestFaultName } from './runtime-incident-drills';
110
110
  import { COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID } from './durable-receipt-execution';
111
111
  import { vercelProtectionBypassHeader } from './vercel-protection';
112
112
  import {