deepline 0.1.238 → 0.1.239

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.
@@ -365,6 +365,9 @@ function datasetAvailableLog(
365
365
  dataset: ReturnType<typeof runtimeDatasetIdentity>,
366
366
  count: number,
367
367
  ): string {
368
+ if (count === 0) {
369
+ return `Dataset ${dataset.path} available: 0 persisted rows; export unavailable: empty dataset`;
370
+ }
368
371
  return `Dataset ${dataset.path} available: ${count} persisted rows; export: deepline runs export ${runId} --dataset ${dataset.path} --out ${dataset.tableNamespace}.csv`;
369
372
  }
370
373
 
@@ -405,6 +405,7 @@ export type PlaySheetRow = {
405
405
  /** Runtime-sheet rows and aggregate progress for one dataset/table namespace. */
406
406
  export type PlaySheetRowsResult = {
407
407
  rows: PlaySheetRow[];
408
+ scope?: { kind: 'database' } | { kind: 'run'; runId: string };
408
409
  summary?: {
409
410
  stats?: {
410
411
  total?: number;
@@ -470,6 +471,15 @@ export type RunsNamespace = {
470
471
  stopAll: (options?: { reason?: string }) => Promise<StopAllPlayRunsResult>;
471
472
  };
472
473
 
474
+ /** Current mutable customer database namespace exposed as `client.db`. */
475
+ export type DbNamespace = {
476
+ /** Run a bounded SQL query against the current customer data plane. */
477
+ query: (input: {
478
+ sql: string;
479
+ maxRows?: number;
480
+ }) => Promise<CustomerDbQueryResult>;
481
+ };
482
+
473
483
  /** One credit grant pool reported by the billing subscription status endpoint. */
474
484
  export type BillingCreditPool = {
475
485
  pool: string;
@@ -1077,6 +1087,8 @@ export class DeeplineClient {
1077
1087
  private readonly config: ResolvedConfig;
1078
1088
  /** Canonical run lifecycle namespace backed by `/api/v2/runs`. */
1079
1089
  readonly runs: RunsNamespace;
1090
+ /** Current mutable customer database namespace backed by `/api/v2/db/query`. */
1091
+ readonly db: DbNamespace;
1080
1092
  /** Billing namespace: subscription status/cancel and invoice history. */
1081
1093
  readonly billing: BillingNamespace;
1082
1094
 
@@ -1101,6 +1113,9 @@ export class DeeplineClient {
1101
1113
  stop: (runId, options) => this.stopRun(runId, options),
1102
1114
  stopAll: (options) => this.stopAllRuns(options),
1103
1115
  };
1116
+ this.db = {
1117
+ query: (input) => this.queryDb(input),
1118
+ };
1104
1119
  this.billing = {
1105
1120
  topUp: (options) => this.topUpBillingBalance(options),
1106
1121
  plans: () => this.getBillingPlans(),
@@ -1460,20 +1475,36 @@ export class DeeplineClient {
1460
1475
  return this.executeTool<TData, TMeta>(toolId, input, options);
1461
1476
  }
1462
1477
 
1478
+ private async queryDb(input: {
1479
+ sql: string;
1480
+ maxRows?: number;
1481
+ }): Promise<CustomerDbQueryResult> {
1482
+ const result = await this.http.post<CustomerDbQueryResult>(
1483
+ '/api/v2/db/query',
1484
+ {
1485
+ sql: input.sql,
1486
+ ...(input.maxRows ? { max_rows: input.maxRows } : {}),
1487
+ },
1488
+ );
1489
+ return {
1490
+ ...result,
1491
+ scope: { kind: 'database', mutability: 'current' },
1492
+ };
1493
+ }
1494
+
1463
1495
  /**
1464
- * Run a bounded SQL query against the customer data plane.
1496
+ * Run a bounded SQL query against the current mutable customer database.
1497
+ *
1498
+ * This query is not scoped to one play run. Use `client.runs` export actions
1499
+ * when the caller needs the rows produced by a specific run.
1465
1500
  *
1466
- * Use this from trusted backend or agent contexts only. The API enforces
1467
- * workspace scoping and row limits.
1501
+ * @deprecated Use {@link DeeplineClient.db} `.query(...)`.
1468
1502
  */
1469
1503
  async queryCustomerDb(input: {
1470
1504
  sql: string;
1471
1505
  maxRows?: number;
1472
1506
  }): Promise<CustomerDbQueryResult> {
1473
- return this.http.post<CustomerDbQueryResult>('/api/v2/db/query', {
1474
- sql: input.sql,
1475
- ...(input.maxRows ? { max_rows: input.maxRows } : {}),
1476
- });
1507
+ return this.db.query(input);
1477
1508
  }
1478
1509
 
1479
1510
  /**
@@ -3000,9 +3031,42 @@ export class DeeplineClient {
3000
3031
  if (input.rowMode === 'all') {
3001
3032
  params.set('rowMode', 'all');
3002
3033
  }
3003
- return await this.http.get<PlaySheetRowsResult>(
3034
+ const result = await this.http.get<PlaySheetRowsResult>(
3004
3035
  `/api/v2/plays/${encodeURIComponent(input.playName)}/sheet?${params.toString()}`,
3005
3036
  );
3037
+ const requestedRunId = input.runId?.trim() || '';
3038
+ if (requestedRunId) {
3039
+ const confirmedRunId =
3040
+ result.scope?.kind === 'run' ? result.scope.runId.trim() : '';
3041
+ const foreignRowRunIds = [
3042
+ ...new Set(
3043
+ result.rows
3044
+ .map((row) =>
3045
+ typeof row.runId === 'string' ? row.runId.trim() : '',
3046
+ )
3047
+ .filter((runId) => runId && runId !== requestedRunId),
3048
+ ),
3049
+ ];
3050
+ if (
3051
+ result.scope?.kind !== 'run' ||
3052
+ confirmedRunId !== requestedRunId ||
3053
+ foreignRowRunIds.length > 0
3054
+ ) {
3055
+ throw new DeeplineError(
3056
+ `Run-scoped dataset export was not confirmed for ${requestedRunId}; no rows were returned to the caller. Update Deepline, then retry the same export command. Do not rerun the play.`,
3057
+ undefined,
3058
+ 'RUN_EXPORT_SCOPE_MISMATCH',
3059
+ {
3060
+ requestedRunId,
3061
+ confirmedScope: result.scope ?? null,
3062
+ foreignRowRunIds,
3063
+ playName: input.playName,
3064
+ tableNamespace: input.tableNamespace,
3065
+ },
3066
+ );
3067
+ }
3068
+ }
3069
+ return result;
3006
3070
  }
3007
3071
 
3008
3072
  /**
@@ -218,6 +218,7 @@ export class HttpClient {
218
218
  'X-Deepline-CLI-Version': SDK_VERSION,
219
219
  'X-Deepline-SDK-Version': SDK_VERSION,
220
220
  'X-Deepline-API-Contract': SDK_API_CONTRACT,
221
+ 'X-Deepline-Run-Result-Shape': 'canonical',
221
222
  ...extra,
222
223
  };
223
224
  const skillsVersion = this.readSkillsVersionHeader();
@@ -62,6 +62,7 @@ export type {
62
62
  BillingInvoiceEntry,
63
63
  BillingInvoicesResult,
64
64
  BillingNamespace,
65
+ DbNamespace,
65
66
  BillingPaymentMethodSummary,
66
67
  BillingSubscriptionCancelResult,
67
68
  BillingSubscriptionStatus,
@@ -141,6 +142,9 @@ export type {
141
142
  LiveEventEnvelope,
142
143
  PlayLiveEvent,
143
144
  PlayRunResult,
145
+ PlayRunActionPackage,
146
+ PlayRunDatasetActions,
147
+ PlayRunPackage,
144
148
  PlayRunStart,
145
149
  ClearPlayHistoryRequest,
146
150
  ClearPlayHistoryResult,
@@ -110,10 +110,10 @@ export const SDK_RELEASE = {
110
110
  // silently materializing them as unmatched results.
111
111
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
112
112
  // automatically without blocking their current command.
113
- version: '0.1.238',
113
+ version: '0.1.239',
114
114
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
115
115
  supportPolicy: {
116
- latest: '0.1.238',
116
+ latest: '0.1.239',
117
117
  minimumSupported: '0.1.53',
118
118
  deprecatedBelow: '0.1.219',
119
119
  commandMinimumSupported: [
@@ -72,12 +72,14 @@ export interface CustomerDbColumn {
72
72
  }
73
73
 
74
74
  /**
75
- * Result returned by {@link DeeplineClient.queryCustomerDb}.
75
+ * Result returned by {@link DeeplineClient.db.query}.
76
76
  *
77
77
  * Rows are intentionally untyped because the schema depends on the caller's SQL
78
78
  * query and selected customer tables.
79
79
  */
80
80
  export interface CustomerDbQueryResult {
81
+ /** This query reads the current mutable customer database, not one run snapshot. */
82
+ scope?: { kind: 'database'; mutability: 'current' };
81
83
  /** Database command executed by the query endpoint. */
82
84
  command: string;
83
85
  /** Total affected row count when reported by the database. */
@@ -443,6 +445,19 @@ export type PlayRunActionPackage =
443
445
  | {
444
446
  kind: 'deepline_run_inspect';
445
447
  runId: string;
448
+ command?: string;
449
+ api: { method: 'GET'; path: string };
450
+ }
451
+ | {
452
+ kind: 'deepline_run_full';
453
+ runId: string;
454
+ command?: string;
455
+ api: { method: 'GET'; path: string };
456
+ }
457
+ | {
458
+ kind: 'deepline_run_billing';
459
+ runId: string;
460
+ command?: string;
446
461
  api: { method: 'GET'; path: string };
447
462
  }
448
463
  | {
@@ -453,6 +468,16 @@ export type PlayRunActionPackage =
453
468
  sqlQualifiedTableName?: string;
454
469
  sql: string;
455
470
  maxRows: number;
471
+ command?: string;
472
+ scope?:
473
+ | { kind: 'database'; mutability: 'current' }
474
+ | { kind: 'run'; runId: string };
475
+ note?: string;
476
+ deprecated?: {
477
+ replacement: 'queryCurrentTable';
478
+ removeAfter: string;
479
+ reason: string;
480
+ };
456
481
  api: {
457
482
  method: 'POST';
458
483
  path: '/api/v2/db/query';
@@ -463,6 +488,14 @@ export type PlayRunActionPackage =
463
488
  runId: string;
464
489
  datasetPath: string;
465
490
  format: 'csv';
491
+ command?: string;
492
+ scope?: { kind: 'run'; runId: string };
493
+ note?: string;
494
+ deprecated?: {
495
+ replacement: 'datasets[].actions.exportCsv';
496
+ removeAfter: string;
497
+ reason: string;
498
+ };
466
499
  }
467
500
  | {
468
501
  kind: 'deepline_run_logs';
@@ -473,6 +506,26 @@ export type PlayRunActionPackage =
473
506
  api: { method: 'GET'; path: string };
474
507
  };
475
508
 
509
+ type PlayRunDbQueryAction = Extract<
510
+ PlayRunActionPackage,
511
+ { kind: 'deepline_db_query' }
512
+ >;
513
+ type PlayRunExportAction = Extract<
514
+ PlayRunActionPackage,
515
+ { kind: 'deepline_run_export' }
516
+ >;
517
+
518
+ export type PlayRunDatasetActions = {
519
+ /** @deprecated Use queryCurrentTable or exportCsv. Removed after 2026-08-18. */
520
+ query?: PlayRunDbQueryAction & { scope?: { kind: 'run'; runId: string } };
521
+ queryCurrentTable?: PlayRunDbQueryAction & {
522
+ scope?: { kind: 'database'; mutability: 'current' };
523
+ };
524
+ exportCsv?: PlayRunExportAction & {
525
+ scope?: { kind: 'run'; runId: string };
526
+ };
527
+ };
528
+
476
529
  /**
477
530
  * Compact canonical package for an inspected play run.
478
531
  *
@@ -508,9 +561,15 @@ export interface PlayRunPackage {
508
561
  path: string;
509
562
  tableNamespace?: string;
510
563
  rowCount?: number;
564
+ sqlTableName?: string;
565
+ sqlQualifiedTableName?: string;
511
566
  recovered?: true;
567
+ exportUnavailable?: {
568
+ reason: 'empty_dataset' | 'shared_table_namespace';
569
+ message: string;
570
+ };
512
571
  preview?: Record<string, unknown>;
513
- actions?: Record<string, PlayRunActionPackage>;
572
+ actions?: PlayRunDatasetActions;
514
573
  }>;
515
574
  /** Small retained tail of customer and runtime logs; fetch the full stream through `runs.logs`. */
516
575
  logs?: {
@@ -522,6 +581,8 @@ export interface PlayRunPackage {
522
581
  /** Follow-up actions a caller can perform against the run. */
523
582
  next?: {
524
583
  inspect?: PlayRunActionPackage;
584
+ full?: PlayRunActionPackage;
585
+ billing?: PlayRunActionPackage;
525
586
  export?: PlayRunActionPackage;
526
587
  query?: PlayRunActionPackage;
527
588
  logs?: PlayRunActionPackage;
@@ -1202,7 +1202,6 @@ export class PlayContextImpl {
1202
1202
  }> = [];
1203
1203
  private processedRowCount = 0;
1204
1204
  private sleepBoundaryIndex = 0;
1205
- private fetchCallIndex = 0;
1206
1205
  private readonly secretRedactor: SecretRedactionContext =
1207
1206
  createSecretRedactionContext();
1208
1207
  private mapInvocationIndex = 0;
@@ -5908,7 +5907,10 @@ export class PlayContextImpl {
5908
5907
  if (!this.toolBatchQueuedAtByKey.has(batchKey)) {
5909
5908
  this.toolBatchQueuedAtByKey.set(batchKey, queuedAt);
5910
5909
  }
5911
- const deadline = queuedAt + TOOL_BATCH_COALESCE_WINDOW_MS;
5910
+ const coalesceWindowMs =
5911
+ this.#options.toolBatchCoalesceWindowMs ??
5912
+ TOOL_BATCH_COALESCE_WINDOW_MS;
5913
+ const deadline = queuedAt + coalesceWindowMs;
5912
5914
  const maxBatchSize = batchMaxSizes.get(batchKey) ?? 1;
5913
5915
  if (count >= maxBatchSize || deadline <= nowMs) {
5914
5916
  readyBatchKeys.add(batchKey);
@@ -6818,11 +6820,15 @@ export class PlayContextImpl {
6818
6820
  options?: FetchOptions,
6819
6821
  ): Promise<PlayFetchResponse> {
6820
6822
  const normalizedKey = this.normalizeContextKey(key, 'fetch');
6821
- if (rowContext.getStore()) {
6822
- throw new Error(
6823
- 'ctx.fetch() must run outside ctx.dataset(); use ctx.tools.execute(...) for row-level external requests so Deepline can batch and checkpoint them.',
6824
- );
6825
- }
6823
+ const rowStore = rowContext.getStore();
6824
+ const rowFetchScope = rowStore
6825
+ ? {
6826
+ tableNamespace: rowStore.tableNamespace ?? null,
6827
+ rowKey: rowStore.rowKey ?? null,
6828
+ rowId: rowStore.rowKey ? null : rowStore.rowId,
6829
+ fieldName: rowStore.fieldName ?? null,
6830
+ }
6831
+ : null;
6826
6832
 
6827
6833
  if (valueContainsSecret(input) || valueContainsSecret(init.body)) {
6828
6834
  throw new Error(
@@ -6858,6 +6864,7 @@ export class PlayContextImpl {
6858
6864
  ...normalizeFetchHeaders(init.headers),
6859
6865
  ...secretHeaderMarkers,
6860
6866
  },
6867
+ row: rowFetchScope,
6861
6868
  }),
6862
6869
  ),
6863
6870
  staleAfterSeconds: options?.staleAfterSeconds,
@@ -6872,7 +6879,7 @@ export class PlayContextImpl {
6872
6879
  const fetchInit = { ...init, headers };
6873
6880
  delete fetchInit.auth;
6874
6881
  const boundaryId = this.durableBoundaryId(
6875
- `fetch-${this.fetchCallIndex}-${stableDigest(
6882
+ `fetch-${stableDigest(
6876
6883
  stableStringify({
6877
6884
  url,
6878
6885
  method,
@@ -6881,10 +6888,10 @@ export class PlayContextImpl {
6881
6888
  ...secretHeaderMarkers,
6882
6889
  },
6883
6890
  body: typeof init.body === 'string' ? init.body : null,
6891
+ row: rowFetchScope,
6884
6892
  }),
6885
6893
  )}`,
6886
6894
  );
6887
- this.fetchCallIndex += 1;
6888
6895
 
6889
6896
  const existing = this.checkpoint.resolvedBoundaries?.[boundaryId];
6890
6897
  if (existing?.kind === 'fetch' && 'output' in existing) {
@@ -680,6 +680,11 @@ export interface ContextOptions {
680
680
  getBatchOperationStrategy?: (
681
681
  operation: string,
682
682
  ) => AnyBatchOperationStrategy | null;
683
+ // How long batchable calls wait for same-key row continuations before
684
+ // dispatching, measured from the first queued item. Defaults to 5ms in
685
+ // production; tests override it to a large value to make coalescing
686
+ // deterministic instead of racing wall-clock milliseconds on slow CI.
687
+ toolBatchCoalesceWindowMs?: number;
683
688
  queryCustomerDb?: CustomerDbQueryHandler;
684
689
  executeStructuredPlayDefinition?: (input: {
685
690
  definition: PlayStructuredDefinition;
@@ -127,14 +127,13 @@ export const PLAY_LATENCY_SUMMARY_COLUMNS: PlayLatencyPhaseDefinition[] = [
127
127
  phase: 'server.start_route_runtime_data_plane_ready',
128
128
  source: 'server',
129
129
  description:
130
- 'Start route Runtime Sheet Data Plane readiness check; active tenants use the cached or record fast path, new tenants may provision here.',
130
+ 'Start route read-only Runtime Sheet Data Plane readiness check.',
131
131
  },
132
132
  {
133
133
  header: 'ready_plane',
134
- phase: 'server.runtime_data_plane_ensure_plane',
134
+ phase: 'server.runtime_data_plane_read_plane',
135
135
  source: 'server',
136
- description:
137
- 'Runtime Sheet readiness control-plane record lookup or first-workspace provisioning.',
136
+ description: 'Runtime Sheet readiness control-plane record lookup.',
138
137
  },
139
138
  {
140
139
  header: 'ready_markers',
@@ -143,20 +142,6 @@ export const PLAY_LATENCY_SUMMARY_COLUMNS: PlayLatencyPhaseDefinition[] = [
143
142
  description:
144
143
  'Single pooled tenant-Postgres read of the storage-contract fingerprint and runtime migration version.',
145
144
  },
146
- {
147
- header: 'ready_repair',
148
- phase: 'server.runtime_data_plane_repair_contract',
149
- source: 'server',
150
- description:
151
- 'Exceptional stale storage-contract repair; absent from the healthy path.',
152
- },
153
- {
154
- header: 'ready_migrate',
155
- phase: 'server.runtime_data_plane_migrate',
156
- source: 'server',
157
- description:
158
- 'Exceptional stale Runtime Sheet migration; absent from the healthy path.',
159
- },
160
145
  {
161
146
  header: 'route_wait',
162
147
  phase: 'server.start_route_wait_completion',
@@ -224,6 +224,20 @@ export type PlayRunnerRuntimeTiming = {
224
224
  daytonaExecuteMs?: number;
225
225
  };
226
226
 
227
+ /**
228
+ * Why replay checkpoint state is present or absent on the detached-runner
229
+ * terminal wire. This describes checkpoint transport only; it never classifies
230
+ * the customer's returned output as oversized.
231
+ */
232
+ export const RUNNER_TERMINAL_CHECKPOINT_DISPOSITION = {
233
+ OMITTED_TERMINAL_REPLAY: 'omitted_terminal_replay',
234
+ INCLUDED_RESUME_REQUIRED: 'included_resume_required',
235
+ ABSENT: 'absent',
236
+ } as const;
237
+
238
+ export type RunnerTerminalCheckpointDisposition =
239
+ (typeof RUNNER_TERMINAL_CHECKPOINT_DISPOSITION)[keyof typeof RUNNER_TERMINAL_CHECKPOINT_DISPOSITION];
240
+
227
241
  export type PlayRunnerResult =
228
242
  | {
229
243
  status: 'completed';
@@ -1,7 +1,10 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { gzipSync } from 'node:zlib';
4
- import type { PlayRunnerExecutionConfig } from '@shared_libs/play-runtime/protocol';
4
+ import {
5
+ RUNNER_TERMINAL_CHECKPOINT_DISPOSITION,
6
+ type PlayRunnerExecutionConfig,
7
+ } from '@shared_libs/play-runtime/protocol';
5
8
  import {
6
9
  PLAY_RUNTIME_CONTRACT,
7
10
  PLAY_RUNTIME_CONTRACT_HEADER,
@@ -126,6 +129,39 @@ function readResultFromOutput() {
126
129
  return null;
127
130
  }
128
131
 
132
+ function terminalResultForTransport(result) {
133
+ if (
134
+ result &&
135
+ ${JSON.stringify(Object.values(RUNNER_TERMINAL_CHECKPOINT_DISPOSITION))}.includes(
136
+ result.checkpointDisposition,
137
+ )
138
+ ) {
139
+ return result;
140
+ }
141
+ if (result && result.status === 'suspended') {
142
+ return {
143
+ ...result,
144
+ checkpointDisposition: '${RUNNER_TERMINAL_CHECKPOINT_DISPOSITION.INCLUDED_RESUME_REQUIRED}',
145
+ checkpointBytes: Buffer.byteLength(JSON.stringify(result.checkpoint)),
146
+ };
147
+ }
148
+ const checkpoint = result && result.checkpoint;
149
+ const terminal = { ...(result || {}) };
150
+ delete terminal.checkpoint;
151
+ if (checkpoint === null || checkpoint === undefined) {
152
+ return {
153
+ ...terminal,
154
+ checkpointDisposition: '${RUNNER_TERMINAL_CHECKPOINT_DISPOSITION.ABSENT}',
155
+ checkpointBytes: 0,
156
+ };
157
+ }
158
+ return {
159
+ ...terminal,
160
+ checkpointDisposition: '${RUNNER_TERMINAL_CHECKPOINT_DISPOSITION.OMITTED_TERMINAL_REPLAY}',
161
+ checkpointBytes: Buffer.byteLength(JSON.stringify(checkpoint)),
162
+ };
163
+ }
164
+
129
165
  async function main() {
130
166
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
131
167
  const context = (config && config.context) || {};
@@ -136,7 +172,8 @@ async function main() {
136
172
  console.log('[crash-terminal] no push config; skipping');
137
173
  return;
138
174
  }
139
- const result = readResultFromOutput() || {
175
+ const parsedResult = readResultFromOutput();
176
+ const result = terminalResultForTransport(parsedResult || {
140
177
  status: 'failed',
141
178
  error:
142
179
  'Daytona play runner exited with code ' +
@@ -146,9 +183,10 @@ async function main() {
146
183
  logs: [],
147
184
  stats: {},
148
185
  steps: [],
149
- checkpoint: null,
186
+ checkpointDisposition: '${RUNNER_TERMINAL_CHECKPOINT_DISPOSITION.ABSENT}',
187
+ checkpointBytes: 0,
150
188
  tableNamespace: null,
151
- };
189
+ });
152
190
  for (let attempt = 1; attempt <= 3; attempt += 1) {
153
191
  try {
154
192
  const controller = new AbortController();
@@ -112,7 +112,10 @@ export type DetachedDaytonaCommandStart = {
112
112
  cmdId: string;
113
113
  };
114
114
 
115
- const DAYTONA_RUNNER_READY_TIMEOUT_MS = 3_000;
115
+ // The marker now proves artifact materialization plus an accepted gateway
116
+ // heartbeat. Keep the handoff bounded, while allowing a cold Fly gateway and
117
+ // one timed-out heartbeat request to recover before the worker rejects it.
118
+ export const DAYTONA_RUNNER_READY_TIMEOUT_MS = 60_000;
116
119
  const DAYTONA_RUNNER_READY_POLL_MS = 250;
117
120
 
118
121
  export class DaytonaRunnerInitializationError extends Error {
@@ -150,9 +153,10 @@ export async function startDetachedDaytonaCommand(input: {
150
153
 
151
154
  /**
152
155
  * A detached command id only proves that Daytona accepted the request. Before
153
- * the worker parks, require a marker written by the initialized runner process.
154
- * This closes the unobservable handoff where a sandbox or command disappears
155
- * after executeSessionCommand but before the first gateway heartbeat.
156
+ * the worker parks, require a marker written only after the initialized runner
157
+ * materializes its artifact and the receipt gateway accepts its first
158
+ * heartbeat. This closes the unobservable handoff where a sandbox or command
159
+ * disappears after executeSessionCommand but before durable runner liveness.
156
160
  */
157
161
  export async function confirmDetachedDaytonaRunnerReady(input: {
158
162
  sandbox: DaytonaSandbox;
@@ -200,6 +204,6 @@ export async function confirmDetachedDaytonaRunnerReady(input: {
200
204
  const oomIndicated =
201
205
  diagnostic.exitCode === 137 || diagnostic.exitCodeFile === 137;
202
206
  throw new DaytonaRunnerInitializationError(
203
- `RUNTIME_SANDBOX_START_FAILED: Daytona accepted detached command ${input.cmdId} in sandbox ${input.sandbox.id}, but the play runner did not initialize within ${timeoutMs}ms. Marker diagnosis: ${markerDiagnosis}. Daytona diagnosis: ${JSON.stringify(diagnostic)}. OOM is ${oomIndicated ? 'indicated by exit code 137' : 'not confirmed by the available Daytona evidence'}. The play was stopped and was not retried.`,
207
+ `RUNTIME_SANDBOX_START_FAILED: Daytona accepted detached command ${input.cmdId} in sandbox ${input.sandbox.id}, but the play runner did not materialize and establish receipt-gateway liveness within ${timeoutMs}ms. Marker diagnosis: ${markerDiagnosis}. Daytona diagnosis: ${JSON.stringify(diagnostic)}. OOM is ${oomIndicated ? 'indicated by exit code 137' : 'not confirmed by the available Daytona evidence'}. The play was stopped and was not retried.`,
204
208
  );
205
209
  }
@@ -785,11 +785,11 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
785
785
  });
786
786
  // Push execution (B2-final, park-and-wake): start the runner command
787
787
  // DETACHED via a Daytona session and return a `detached_runner`
788
- // suspension after the runner writes its one-time ready marker. The
789
- // marker is the handoff proof: a Daytona cmdId alone does not prove
790
- // the sandbox process initialized. After that bounded startup check,
791
- // the worker parks (releasing its claim and slot) and wakes only on
792
- // the runner's pushed terminal or the ceiling timeout.
788
+ // suspension only after the runner writes its one-time ready marker.
789
+ // The marker proves artifact materialization and an accepted gateway
790
+ // heartbeat: a Daytona cmdId alone does not prove durable liveness.
791
+ // After that bounded startup check, the worker parks (releasing its
792
+ // claim and slot) and wakes on terminal push or the ceiling timeout.
793
793
  const sessionId = `deepline-play-${randomUUID()}`;
794
794
  let start: Awaited<ReturnType<typeof startDetachedDaytonaCommand>>;
795
795
  try {