deepline 0.1.168 → 0.1.169

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 (30) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +317 -26
  2. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +99 -6
  3. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +229 -75
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +119 -33
  5. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +2 -0
  6. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +3 -0
  7. package/dist/bundling-sources/sdk/src/client.ts +29 -1
  8. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  9. package/dist/bundling-sources/sdk/src/types.ts +3 -0
  10. package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +50 -8
  11. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -1
  12. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +3 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +1 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
  19. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +202 -45
  20. package/dist/cli/index.js +70 -113
  21. package/dist/cli/index.mjs +70 -113
  22. package/dist/{compiler-manifest-VhtM9n24.d.mts → compiler-manifest-OwORQ07f.d.mts} +1 -0
  23. package/dist/{compiler-manifest-VhtM9n24.d.ts → compiler-manifest-OwORQ07f.d.ts} +1 -0
  24. package/dist/index.d.mts +5 -1
  25. package/dist/index.d.ts +5 -1
  26. package/dist/index.js +26 -5
  27. package/dist/index.mjs +26 -5
  28. package/dist/plays/bundle-play-file.d.mts +2 -2
  29. package/dist/plays/bundle-play-file.d.ts +2 -2
  30. package/package.json +1 -1
@@ -263,6 +263,7 @@ type RunRequest = {
263
263
  callbackUrl: string;
264
264
  executorToken: string;
265
265
  baseUrl: string;
266
+ integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
266
267
  orgId: string;
267
268
  playName: string;
268
269
  graphHash?: string | null;
@@ -325,6 +326,14 @@ function getStringField(value: unknown, key: string): string | null {
325
326
  return typeof field === 'string' && field.trim() ? field : null;
326
327
  }
327
328
 
329
+ function normalizeIntegrationMode(
330
+ value: unknown,
331
+ ): 'live' | 'eval_stub' | 'fixture' | null {
332
+ return value === 'live' || value === 'eval_stub' || value === 'fixture'
333
+ ? value
334
+ : null;
335
+ }
336
+
328
337
  function getObjectField(
329
338
  value: unknown,
330
339
  key: string,
@@ -1472,6 +1481,9 @@ async function callToolDirect(
1472
1481
  body: JSON.stringify({
1473
1482
  payload: input,
1474
1483
  metadata: { parent_run_id: req.runId },
1484
+ ...(req.integrationMode
1485
+ ? { integration_mode: req.integrationMode }
1486
+ : {}),
1475
1487
  }),
1476
1488
  },
1477
1489
  {
@@ -1650,13 +1662,44 @@ const WORKER_TOOL_BATCH_GRACE_MS = 250;
1650
1662
  const MAP_EXECUTION_HEARTBEAT_INTERVAL_MS = 5_000;
1651
1663
  const MAP_INCREMENTAL_PERSIST_CHUNK_ROWS = 100;
1652
1664
  const MAP_INCREMENTAL_PERSIST_CHUNK_BYTES = 1 * 1024 * 1024;
1653
- const MAP_INCREMENTAL_PERSIST_INTERVAL_MS = 100;
1665
+ const MAP_INCREMENTAL_PERSIST_INTERVAL_MS = 1_000;
1666
+ const MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS = 1_000;
1654
1667
  /**
1655
1668
  * Bounded number of per-row failure samples carried in chunk summaries and the
1656
1669
  * map's terminal partial-failure log. Every failed row is persisted with its
1657
1670
  * full error in the runtime sheet; the samples just keep run logs readable.
1658
1671
  */
1659
1672
  const MAP_ROW_FAILURE_SAMPLE_LIMIT = 3;
1673
+
1674
+ class RuntimeReceiptPersistenceError extends Error {
1675
+ readonly errors: unknown[];
1676
+
1677
+ constructor(message: string, errors: unknown[] = []) {
1678
+ super(message);
1679
+ this.name = 'RuntimeReceiptPersistenceError';
1680
+ this.errors = errors;
1681
+ }
1682
+ }
1683
+
1684
+ function isRuntimeReceiptPersistenceError(error: unknown): boolean {
1685
+ if (!error || typeof error !== 'object') return false;
1686
+ if (error instanceof RuntimeReceiptPersistenceError) return true;
1687
+ if (error instanceof Error && error.name === 'RuntimeReceiptPersistenceError') {
1688
+ return true;
1689
+ }
1690
+ const nestedErrors = (error as { errors?: unknown }).errors;
1691
+ return (
1692
+ Array.isArray(nestedErrors) &&
1693
+ nestedErrors.some(isRuntimeReceiptPersistenceError)
1694
+ );
1695
+ }
1696
+
1697
+ function isRunFatalWorkerRowError(error: unknown): boolean {
1698
+ return (
1699
+ isRowIsolationExemptError(error) || isRuntimeReceiptPersistenceError(error)
1700
+ );
1701
+ }
1702
+
1660
1703
  // Fallback batch-chunk parallelism when a tool declares no provider rate hints.
1661
1704
  // Matches the prior hardcoded `Math.min(4, ...)` so undeclared providers keep
1662
1705
  // their previous batching behavior; declared providers tighten via the
@@ -1982,6 +2025,7 @@ class WorkerToolBatchScheduler {
1982
2025
  !canReclaimTimedOutWorkerToolReceipt({
1983
2026
  ownerRunId: input.runningReceipt.runId,
1984
2027
  currentRunId: this.req.runId,
2028
+ updatedAt: input.runningReceipt.updatedAt,
1985
2029
  })
1986
2030
  ) {
1987
2031
  this.rejectRawRequests(input.group, waitError);
@@ -2100,9 +2144,9 @@ class WorkerToolBatchScheduler {
2100
2144
  await this.failDurableToolRequest(claimed, error);
2101
2145
  return error;
2102
2146
  } catch (receiptError) {
2103
- return new AggregateError(
2104
- [error, receiptError],
2147
+ return new RuntimeReceiptPersistenceError(
2105
2148
  'Tool call failed and durable receipt could not be marked failed',
2149
+ [error, receiptError],
2106
2150
  );
2107
2151
  }
2108
2152
  }
@@ -2459,25 +2503,38 @@ class WorkerToolBatchScheduler {
2459
2503
  signal: this.abortSignal,
2460
2504
  });
2461
2505
  try {
2462
- const result = await executeToolWithLifecycle(
2463
- this.req,
2464
- { id: request.id, toolId, input: request.input },
2465
- request.workflowStep,
2466
- this.callbacks,
2467
- (retryAfterMs) => this.reportBackpressure(toolId, retryAfterMs),
2468
- () => this.governor.chargeBudget('retry'),
2469
- toolContract?.retrySafeTransientHttp === true,
2470
- this.abortSignal,
2471
- this.runtimeDeadlineMs,
2472
- );
2506
+ let result: unknown;
2507
+ try {
2508
+ result = await executeToolWithLifecycle(
2509
+ this.req,
2510
+ { id: request.id, toolId, input: request.input },
2511
+ request.workflowStep,
2512
+ this.callbacks,
2513
+ (retryAfterMs) =>
2514
+ this.reportBackpressure(toolId, retryAfterMs),
2515
+ () => this.governor.chargeBudget('retry'),
2516
+ toolContract?.retrySafeTransientHttp === true,
2517
+ this.abortSignal,
2518
+ this.runtimeDeadlineMs,
2519
+ );
2520
+ } catch (error) {
2521
+ this.rejectRequests(
2522
+ claimed,
2523
+ await this.failureForRejectedToolRequest(claimed, error),
2524
+ );
2525
+ return;
2526
+ }
2473
2527
  this.settleRequests(
2474
2528
  claimed,
2475
2529
  await this.completeDurableToolRequest(claimed, result),
2476
2530
  );
2477
- } catch (error) {
2531
+ } catch (receiptError) {
2478
2532
  this.rejectRequests(
2479
2533
  claimed,
2480
- await this.failureForRejectedToolRequest(claimed, error),
2534
+ new RuntimeReceiptPersistenceError(
2535
+ 'Tool call succeeded but durable receipt could not be marked completed',
2536
+ [receiptError],
2537
+ ),
2481
2538
  );
2482
2539
  } finally {
2483
2540
  slot.release();
@@ -2651,9 +2708,9 @@ async function executeBatchedWorkerToolGroup(input: {
2651
2708
  try {
2652
2709
  await input.failRequests(entry.request.memberRequests, entry.error);
2653
2710
  } catch (receiptError) {
2654
- rejection = new AggregateError(
2655
- [entry.error, receiptError],
2711
+ rejection = new RuntimeReceiptPersistenceError(
2656
2712
  'Tool call failed and durable receipts could not be marked failed',
2713
+ [entry.error, receiptError],
2657
2714
  );
2658
2715
  }
2659
2716
  for (const claimed of entry.request.memberRequests) {
@@ -2668,16 +2725,28 @@ async function executeBatchedWorkerToolGroup(input: {
2668
2725
  batchResult != null
2669
2726
  ? entry.request.splitResults(batchResult)
2670
2727
  : entry.request.memberRequests.map(() => null);
2671
- const completedResults = await input.completeRequests(
2672
- entry.request.memberRequests.map((claimed, index) => ({
2673
- claimed,
2674
- result: wrapWorkerToolResult(
2675
- claimed.request.toolId,
2676
- splitResults[index] ?? null,
2677
- toolMetadataFallback(claimed.request.toolId),
2678
- ),
2679
- })),
2680
- );
2728
+ let completedResults: unknown[];
2729
+ try {
2730
+ completedResults = await input.completeRequests(
2731
+ entry.request.memberRequests.map((claimed, index) => ({
2732
+ claimed,
2733
+ result: wrapWorkerToolResult(
2734
+ claimed.request.toolId,
2735
+ splitResults[index] ?? null,
2736
+ toolMetadataFallback(claimed.request.toolId),
2737
+ ),
2738
+ })),
2739
+ );
2740
+ } catch (receiptError) {
2741
+ const rejection = new RuntimeReceiptPersistenceError(
2742
+ 'Tool call succeeded but durable receipts could not be marked completed',
2743
+ [receiptError],
2744
+ );
2745
+ for (const claimed of entry.request.memberRequests) {
2746
+ input.rejectRequest(claimed, rejection);
2747
+ }
2748
+ continue;
2749
+ }
2681
2750
  for (let index = 0; index < completedResults.length; index += 1) {
2682
2751
  const claimed = entry.request.memberRequests[index]!;
2683
2752
  const request = claimed.request;
@@ -2690,9 +2759,9 @@ async function executeBatchedWorkerToolGroup(input: {
2690
2759
  try {
2691
2760
  await input.failRequests(input.requests, error);
2692
2761
  } catch (receiptError) {
2693
- rejection = new AggregateError(
2694
- [error, receiptError],
2762
+ rejection = new RuntimeReceiptPersistenceError(
2695
2763
  'Tool call failed and durable receipts could not be marked failed',
2764
+ [error, receiptError],
2696
2765
  );
2697
2766
  }
2698
2767
  for (const claimed of input.requests) {
@@ -2728,6 +2797,7 @@ type WorkerMapChunkSummary<T extends Record<string, unknown>> = {
2728
2797
  stepCellsSkipped: number;
2729
2798
  outputDatasetId: string;
2730
2799
  hash: string;
2800
+ fatalError?: string;
2731
2801
  preview: T[];
2732
2802
  cachedRows?: T[];
2733
2803
  };
@@ -3816,6 +3886,15 @@ async function persistCompletedMapRows(input: {
3816
3886
  rows,
3817
3887
  outputFields,
3818
3888
  });
3889
+ console.warn('[play-runner.persist_completed_map_rows.start]', {
3890
+ runId: req.runId,
3891
+ playName: req.playName,
3892
+ tableNamespace,
3893
+ rows: rows.length,
3894
+ outputFields,
3895
+ extraOutputFields: input.extraOutputFields ?? [],
3896
+ contractColumnCount: sheetContract.columns.length,
3897
+ });
3819
3898
  const persistRequest = {
3820
3899
  ...sessionScope,
3821
3900
  tableNamespace,
@@ -3830,6 +3909,8 @@ async function persistCompletedMapRows(input: {
3830
3909
  .filter((key): key is string => Boolean(key)),
3831
3910
  ),
3832
3911
  ];
3912
+ const expectedVisibleRows =
3913
+ expectedKeys.length > 0 ? expectedKeys.length : rows.length;
3833
3914
  const readVisibleRowCount = async () => {
3834
3915
  if (expectedKeys.length > 0) {
3835
3916
  const result = await harnessReadSheetDatasetRowKeys({
@@ -3852,12 +3933,24 @@ async function persistCompletedMapRows(input: {
3852
3933
  return result.rows.length;
3853
3934
  };
3854
3935
  const result = await harnessPersistCompletedSheetRows(persistRequest);
3936
+ console.warn('[play-runner.persist_completed_map_rows.result]', {
3937
+ runId: req.runId,
3938
+ playName: req.playName,
3939
+ tableNamespace,
3940
+ rows: rows.length,
3941
+ rowsWritten: result.rowsWritten,
3942
+ expectedVisibleRows,
3943
+ expectedKeyCount: expectedKeys.length,
3944
+ });
3855
3945
  let visibleRows = -1;
3856
3946
  let retryWritten: number | null = null;
3857
3947
  let retryVisible: number | null = null;
3858
- if (rows.length <= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS) {
3948
+ if (
3949
+ rows.length <= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS ||
3950
+ result.rowsWritten !== rows.length
3951
+ ) {
3859
3952
  visibleRows = await readVisibleRowCount();
3860
- if (visibleRows < rows.length) {
3953
+ if (visibleRows < expectedVisibleRows) {
3861
3954
  await harnessStartSheetDataset({
3862
3955
  ...sessionScope,
3863
3956
  tableNamespace,
@@ -3869,15 +3962,23 @@ async function persistCompletedMapRows(input: {
3869
3962
  const retry = await harnessPersistCompletedSheetRows(persistRequest);
3870
3963
  retryWritten = retry.rowsWritten;
3871
3964
  retryVisible = await readVisibleRowCount();
3872
- if (retryVisible < rows.length)
3873
- throw new Error(
3874
- `Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}; retry wrote ${retryWritten}/${rows.length}; retry visible ${retryVisible}; run ${req.runId}.`,
3875
- );
3965
+ if (retryVisible >= expectedVisibleRows) {
3966
+ return {
3967
+ rows: rows.length,
3968
+ written: result.rowsWritten,
3969
+ visible: visibleRows,
3970
+ retryWritten,
3971
+ retryVisible,
3972
+ };
3973
+ }
3974
+ throw new Error(
3975
+ `Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}/${expectedVisibleRows}; retry wrote ${retryWritten}/${rows.length}; retry visible ${retryVisible}/${expectedVisibleRows}; run ${req.runId}.`,
3976
+ );
3876
3977
  }
3877
3978
  }
3878
- if (result.rowsWritten !== rows.length && visibleRows < rows.length) {
3979
+ if (result.rowsWritten !== rows.length && visibleRows < expectedVisibleRows) {
3879
3980
  throw new Error(
3880
- `Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; run ${req.runId}.`,
3981
+ `Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}/${expectedVisibleRows}; run ${req.runId}.`,
3881
3982
  );
3882
3983
  }
3883
3984
  return {
@@ -4932,7 +5033,7 @@ function createMinimalWorkerCtx(
4932
5033
  let persistFailure: unknown = null;
4933
5034
  let scheduledLiveUpdateTimer: ReturnType<typeof setTimeout> | null = null;
4934
5035
  let liveUpdateFlushChain: Promise<void> = Promise.resolve();
4935
- let liveUpdateFailure: unknown = null;
5036
+ let liveUpdateFailureCount = 0;
4936
5037
 
4937
5038
  const clearScheduledPersistTimer = () => {
4938
5039
  if (scheduledPersistTimer) {
@@ -5070,25 +5171,35 @@ function createMinimalWorkerCtx(
5070
5171
  const updates = pendingLiveRowUpdates.splice(0);
5071
5172
  const extraOutputFields = Array.from(generatedOutputFields);
5072
5173
  const task = liveUpdateFlushChain.then(async () => {
5073
- if (liveUpdateFailure) throw liveUpdateFailure;
5074
- await applyLiveMapRowUpdates({
5075
- req,
5076
- tableNamespace: name,
5077
- outputFields,
5078
- extraOutputFields,
5079
- updates,
5080
- });
5081
- });
5082
- liveUpdateFlushChain = task.catch((error) => {
5083
- liveUpdateFailure ??= error;
5174
+ try {
5175
+ await applyLiveMapRowUpdates({
5176
+ req,
5177
+ tableNamespace: name,
5178
+ outputFields,
5179
+ extraOutputFields,
5180
+ updates,
5181
+ });
5182
+ } catch (error) {
5183
+ liveUpdateFailureCount += 1;
5184
+ if (liveUpdateFailureCount <= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
5185
+ emitEvent({
5186
+ type: 'log',
5187
+ level: 'warn',
5188
+ message:
5189
+ `Live row update flush failed for ctx.dataset("${name}") ` +
5190
+ `(non-fatal; terminal rows still persist): ${formatWorkerRowFailureMessage(error)}`,
5191
+ ts: nowMs(),
5192
+ });
5193
+ }
5194
+ }
5084
5195
  });
5196
+ liveUpdateFlushChain = task.catch(() => undefined);
5085
5197
  return task;
5086
5198
  };
5087
5199
 
5088
5200
  const scheduleLiveRowUpdates = () => {
5089
- if (liveUpdateFailure) return;
5090
5201
  if (
5091
- pendingLiveRowUpdates.length >= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS
5202
+ pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5092
5203
  ) {
5093
5204
  void flushLiveRowUpdates().catch(() => undefined);
5094
5205
  return;
@@ -5103,14 +5214,12 @@ function createMinimalWorkerCtx(
5103
5214
  const enqueueLiveRowUpdate = (
5104
5215
  update: Omit<PlayRowUpdate, 'rowId'> & { runId?: string },
5105
5216
  ): Promise<void> => {
5106
- if (liveUpdateFailure) {
5107
- return Promise.reject(liveUpdateFailure);
5108
- }
5109
5217
  pendingLiveRowUpdates.push(update);
5110
5218
  if (
5111
- pendingLiveRowUpdates.length >= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS
5219
+ pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5112
5220
  ) {
5113
- return flushLiveRowUpdates();
5221
+ void flushLiveRowUpdates().catch(() => undefined);
5222
+ return Promise.resolve();
5114
5223
  }
5115
5224
  scheduleLiveRowUpdates();
5116
5225
  return Promise.resolve();
@@ -5293,7 +5402,7 @@ function createMinimalWorkerCtx(
5293
5402
  } catch (rowError) {
5294
5403
  // Abort/budget errors stay run-fatal and leave no partial
5295
5404
  // state: rethrow immediately without recording the row.
5296
- if (isRowIsolationExemptError(rowError)) {
5405
+ if (isRunFatalWorkerRowError(rowError)) {
5297
5406
  throw rowError;
5298
5407
  }
5299
5408
  const message = formatWorkerRowFailureMessage(rowError);
@@ -5393,6 +5502,43 @@ function createMinimalWorkerCtx(
5393
5502
  concurrency,
5394
5503
  },
5395
5504
  });
5505
+ const buildRowFailureSamples = () =>
5506
+ failedRowEntries
5507
+ .map((failure, executedIndex) =>
5508
+ failure
5509
+ ? {
5510
+ rowKey: uniqueRowsToExecuteEntries[executedIndex]!.rowKey,
5511
+ error: failure.error,
5512
+ }
5513
+ : null,
5514
+ )
5515
+ .filter(
5516
+ (sample): sample is { rowKey: string; error: string } =>
5517
+ sample !== null,
5518
+ )
5519
+ .slice(0, MAP_ROW_FAILURE_SAMPLE_LIMIT);
5520
+ const fatalMapChunkSummary = async (
5521
+ error: unknown,
5522
+ ): Promise<WorkerMapChunkSummary<T & Record<string, unknown>>> => ({
5523
+ chunkIndex,
5524
+ rangeStart: baseOffset + chunkStart,
5525
+ rangeEnd: baseOffset + chunkStart,
5526
+ rowsRead: chunkRows.length,
5527
+ rowsWritten: 0,
5528
+ rowsExecuted: rowsToExecute.length,
5529
+ rowsCached: 0,
5530
+ rowsDuplicateReused: duplicateInputReuseCount,
5531
+ rowsInserted,
5532
+ rowsSkipped,
5533
+ rowsFailed: failedExecutedRows,
5534
+ rowFailureSamples: buildRowFailureSamples(),
5535
+ stepCellsCompleted,
5536
+ stepCellsSkipped,
5537
+ outputDatasetId: `map:${name}`,
5538
+ hash: await hashJson([]),
5539
+ fatalError: formatWorkerRowFailureMessage(error),
5540
+ preview: [],
5541
+ });
5396
5542
  const persistRowsStartedAt = nowMs();
5397
5543
  recordRunnerPerfTrace({
5398
5544
  req,
@@ -5407,7 +5553,6 @@ function createMinimalWorkerCtx(
5407
5553
  try {
5408
5554
  await flushLiveRowUpdates();
5409
5555
  await liveUpdateFlushChain;
5410
- if (liveUpdateFailure) throw liveUpdateFailure;
5411
5556
  await enqueuePersistExecutedRows();
5412
5557
  await persistFlushChain;
5413
5558
  if (persistFailure) throw persistFailure;
@@ -5433,13 +5578,16 @@ function createMinimalWorkerCtx(
5433
5578
  error: error instanceof Error ? error.message : String(error),
5434
5579
  },
5435
5580
  });
5436
- throw error;
5581
+ return await fatalMapChunkSummary(error);
5437
5582
  }
5438
5583
  const rejectedWorker = workerResults.find(
5439
5584
  (result): result is PromiseRejectedResult =>
5440
5585
  result.status === 'rejected',
5441
5586
  );
5442
5587
  if (rejectedWorker) {
5588
+ if (isRuntimeReceiptPersistenceError(rejectedWorker.reason)) {
5589
+ return await fatalMapChunkSummary(rejectedWorker.reason);
5590
+ }
5443
5591
  throw rejectedWorker.reason;
5444
5592
  }
5445
5593
  const resultByKey = new Map<string, T & Record<string, unknown>>();
@@ -5494,20 +5642,7 @@ function createMinimalWorkerCtx(
5494
5642
  0,
5495
5643
  executedRows.length - failedExecutedRows,
5496
5644
  );
5497
- const rowFailureSamples = failedRowEntries
5498
- .map((failure, executedIndex) =>
5499
- failure
5500
- ? {
5501
- rowKey: uniqueRowsToExecuteEntries[executedIndex]!.rowKey,
5502
- error: failure.error,
5503
- }
5504
- : null,
5505
- )
5506
- .filter(
5507
- (sample): sample is { rowKey: string; error: string } =>
5508
- sample !== null,
5509
- )
5510
- .slice(0, MAP_ROW_FAILURE_SAMPLE_LIMIT);
5645
+ const rowFailureSamples = buildRowFailureSamples();
5511
5646
  const publicOut = out.map((row) => publicCsvOutputRow(row));
5512
5647
  const keyedOut = outEntries.map(({ key, inputIndex, row }) => ({
5513
5648
  ...row,
@@ -5756,6 +5891,15 @@ function createMinimalWorkerCtx(
5756
5891
  continue;
5757
5892
  }
5758
5893
  const chunkResult = await runChunkStep(chunkRows, chunkStart, chunkIndex);
5894
+ if (chunkResult.fatalError) {
5895
+ throw new Error(
5896
+ `ctx.dataset("${name}") stopped after a runtime persistence failure ` +
5897
+ `outside the retryable chunk step. Provider calls already executed for ` +
5898
+ `${chunkResult.rowsExecuted} row(s), so the chunk was not retried. ` +
5899
+ `Fix the runtime persistence cause and re-run to resume. ` +
5900
+ `First error: ${chunkResult.fatalError}`,
5901
+ );
5902
+ }
5759
5903
  totalRowsWritten += chunkResult.rowsWritten;
5760
5904
  totalRowsExecuted += chunkResult.rowsExecuted;
5761
5905
  totalRowsCached += chunkResult.rowsCached;
@@ -6274,6 +6418,7 @@ function createMinimalWorkerCtx(
6274
6418
  orgId: req.orgId,
6275
6419
  callbackBaseUrl: req.callbackUrl,
6276
6420
  baseUrl: req.baseUrl,
6421
+ integrationMode: req.integrationMode ?? null,
6277
6422
  parentExecutorToken: req.executorToken,
6278
6423
  userEmail: req.userEmail ?? '',
6279
6424
  profile: 'workers_edge',
@@ -7652,11 +7797,18 @@ function extractMaxCreditsPerRun(contractSnapshot: unknown): number | null {
7652
7797
  : null;
7653
7798
  }
7654
7799
 
7800
+ function shouldSkipWorkerComputeBilling(req: RunRequest): boolean {
7801
+ return req.integrationMode === 'fixture' || req.integrationMode === 'eval_stub';
7802
+ }
7803
+
7655
7804
  async function finalizeWorkerComputeBilling(input: {
7656
7805
  req: RunRequest;
7657
7806
  success: boolean;
7658
7807
  actionEstimate: number;
7659
7808
  }): Promise<void> {
7809
+ if (shouldSkipWorkerComputeBilling(input.req)) {
7810
+ return;
7811
+ }
7660
7812
  const maxCreditsPerRun = extractMaxCreditsPerRun(input.req.contractSnapshot);
7661
7813
  await postRuntimeApi(input.req.baseUrl, input.req.executorToken, {
7662
7814
  action: 'compute_billing_finalize',
@@ -7712,6 +7864,7 @@ function runRequestFromWorkflowParams(
7712
7864
  callbackUrl: String(params.baseUrl ?? ''),
7713
7865
  executorToken: String(params.executorToken ?? ''),
7714
7866
  baseUrl: String(params.baseUrl ?? ''),
7867
+ integrationMode: normalizeIntegrationMode(params.integrationMode),
7715
7868
  orgId: String(params.orgId ?? ''),
7716
7869
  playName: String(params.playName ?? ''),
7717
7870
  userEmail: typeof params.userEmail === 'string' ? params.userEmail : null,
@@ -8051,7 +8204,7 @@ export class TenantWorkflow extends WorkflowEntrypoint<
8051
8204
  // somewhere inside executeRunRequest. If it doesn't appear, the
8052
8205
  // throw is in the framework wrapper between the loader and run().
8053
8206
  console.log(
8054
- `${runPrefix} TenantWorkflow.run entered baseUrl=${req.baseUrl}`,
8207
+ `${runPrefix} TenantWorkflow.run entered baseUrl=${req.baseUrl} integrationMode=${req.integrationMode ?? 'default'}`,
8055
8208
  );
8056
8209
  captureCoordinatorBinding(this.env);
8057
8210
  captureRuntimeApiBinding(this.env);
@@ -8065,6 +8218,7 @@ export class TenantWorkflow extends WorkflowEntrypoint<
8065
8218
  ms: 0,
8066
8219
  extra: {
8067
8220
  hasWorkflowStep: true,
8221
+ integrationMode: req.integrationMode ?? null,
8068
8222
  },
8069
8223
  });
8070
8224
  // Fire the one-time wiring probe (deduplicated across runs in the