import { PLAY_RUNNER_TERMINAL_GRACE_SECONDS, STANDARD_PLAY_RUNTIME_LIMIT_SECONDS, } from './runtime-constants'; export type PlayExecutionSuspension = | { kind: 'sleep'; boundaryId: string; delayMs: number; } | { kind: 'integration_event'; boundaryId: string; eventKey: string; timeoutMs: number; } | { kind: 'integration_event_batch'; boundaries: Array<{ boundaryId: string; eventKey: string; timeoutMs: number; }>; } | { /** * Push-execution park (B2-final): the Daytona runner command was started * DETACHED and the worker parks instead of holding anything. The parked * attempt wakes on the runner's pushed terminal (the gateway emits the * absurd wake event), the scheduler-owned heartbeat deadline, or the * hard execution ceiling, then finalizes from persisted state. */ kind: 'detached_runner'; boundaryId: string; /** Attempt generation whose executor token the detached runner holds — * the key under which its pushed terminal is recorded and its wake event * is named. */ runnerAttempt: number; /** Missing on historical records, which are Daytona. */ sandboxProvider?: 'daytona' | 'modal'; /** Versioned physical identity for provider-safe wake and cleanup. * Legacy fields remain until every launch-pinned release has drained. */ runtimeSandboxRef?: { schemaVersion: 1; provider: 'daytona' | 'modal'; resourceId: string; routingDomain: string | null; }; sandboxId: string; sessionId: string; cmdId: string; /** Captured output/exit-code file paths inside the sandbox, used by the * timeout-wake salvage verification. */ outputPath: string; exitCodePath: string; /** Exact customer-code completion fence inside the Daytona sandbox. */ runtimeCompletedPath?: string; /** Bounded watchdog-owned cause marker for crash-only diagnostics. */ terminationDiagnosticPath?: string; startedAtMs: number; /** Scheduler-owned liveness deadline. The sandbox can renew it only by * persisting `heartbeat_at` through the receipt gateway. */ heartbeatTimeoutMs: number; /** Overall run ceiling; the park timeout. */ ceilingMs: number; /** Launch-pinned user-code deadline. Missing on historical suspensions. */ runtimeLimitSeconds?: number; }; export type DetachedRunnerSuspension = Extract< PlayExecutionSuspension, { kind: 'detached_runner' } >; export function resolveDetachedRunnerRuntimeLimitSeconds( suspension: Pick< DetachedRunnerSuspension, 'runtimeLimitSeconds' | 'ceilingMs' >, ): number { if ( Number.isSafeInteger(suspension.runtimeLimitSeconds) && Number(suspension.runtimeLimitSeconds) > 0 ) { return Number(suspension.runtimeLimitSeconds); } const legacyDerived = (suspension.ceilingMs - PLAY_RUNNER_TERMINAL_GRACE_SECONDS * 1_000) / 1_000; return Number.isSafeInteger(legacyDerived) && legacyDerived > 0 ? legacyDerived : STANDARD_PLAY_RUNTIME_LIMIT_SECONDS; } export class PlayExecutionSuspendedError extends Error { readonly suspension: PlayExecutionSuspension; constructor(suspension: PlayExecutionSuspension) { super( suspension.kind === 'sleep' ? `Play execution suspended for durable sleep (${suspension.delayMs}ms).` : suspension.kind === 'integration_event_batch' ? `Play execution suspended waiting for ${suspension.boundaries.length} integration events.` : suspension.kind === 'detached_runner' ? `Play execution parked on detached runner ${suspension.cmdId} (sandbox ${suspension.sandboxId}).` : `Play execution suspended waiting for integration event ${JSON.stringify(suspension.eventKey)}.`, ); this.name = 'PlayExecutionSuspendedError'; this.suspension = suspension; } } export class PlayRowExecutionSuspendedError extends Error { readonly boundary: { boundaryId: string; eventKey: string; timeoutMs: number; }; constructor(boundary: { boundaryId: string; eventKey: string; timeoutMs: number; }) { super( `Play row execution suspended waiting for integration event ${JSON.stringify(boundary.eventKey)}.`, ); this.name = 'PlayRowExecutionSuspendedError'; this.boundary = boundary; } } export function isPlayRowExecutionSuspendedError( value: unknown, ): value is PlayRowExecutionSuspendedError { return value instanceof PlayRowExecutionSuspendedError; } export function isPlayExecutionSuspendedError( value: unknown, ): value is PlayExecutionSuspendedError { return value instanceof PlayExecutionSuspendedError; }