deepline 0.1.168 → 0.1.170

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 (32) 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 +100 -8
  3. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +294 -81
  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/receipts.ts +4 -1
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +56 -0
  7. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +3 -0
  8. package/dist/bundling-sources/sdk/src/client.ts +29 -1
  9. package/dist/bundling-sources/sdk/src/play.ts +4 -0
  10. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  11. package/dist/bundling-sources/sdk/src/types.ts +3 -0
  12. package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +50 -8
  13. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +135 -4
  16. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +9 -3
  17. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +1 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
  21. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +202 -45
  22. package/dist/cli/index.js +70 -113
  23. package/dist/cli/index.mjs +70 -113
  24. package/dist/{compiler-manifest-VhtM9n24.d.mts → compiler-manifest-OwORQ07f.d.mts} +1 -0
  25. package/dist/{compiler-manifest-VhtM9n24.d.ts → compiler-manifest-OwORQ07f.d.ts} +1 -0
  26. package/dist/index.d.mts +9 -1
  27. package/dist/index.d.ts +9 -1
  28. package/dist/index.js +26 -5
  29. package/dist/index.mjs +26 -5
  30. package/dist/plays/bundle-play-file.d.mts +2 -2
  31. package/dist/plays/bundle-play-file.d.ts +2 -2
  32. package/package.json +1 -1
@@ -159,6 +159,8 @@ import {
159
159
  markWorkerToolReceiptResultCached,
160
160
  markWorkerToolReceiptResultExecution,
161
161
  planWorkerToolReceiptGroups,
162
+ resolveWorkerToolReceiptGroupWaitMaxAttempts,
163
+ resolveWorkerToolRuntimeTimeoutMs,
162
164
  } from './runtime/tool-receipts';
163
165
  // The harness stub forwards leaf calls (validation, runtime-api HTTP) into
164
166
  // the long-lived Play Harness Worker via env.HARNESS. We import the
@@ -263,6 +265,7 @@ type RunRequest = {
263
265
  callbackUrl: string;
264
266
  executorToken: string;
265
267
  baseUrl: string;
268
+ integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
266
269
  orgId: string;
267
270
  playName: string;
268
271
  graphHash?: string | null;
@@ -325,6 +328,14 @@ function getStringField(value: unknown, key: string): string | null {
325
328
  return typeof field === 'string' && field.trim() ? field : null;
326
329
  }
327
330
 
331
+ function normalizeIntegrationMode(
332
+ value: unknown,
333
+ ): 'live' | 'eval_stub' | 'fixture' | null {
334
+ return value === 'live' || value === 'eval_stub' || value === 'fixture'
335
+ ? value
336
+ : null;
337
+ }
338
+
328
339
  function getObjectField(
329
340
  value: unknown,
330
341
  key: string,
@@ -554,11 +565,13 @@ function resolveRuntimeDeadlineRemainingMs(runtimeDeadlineMs?: number): number {
554
565
 
555
566
  function resolveToolRuntimeApiTimeout(input: {
556
567
  requestInput: Record<string, unknown>;
568
+ timeoutMs?: number;
557
569
  runtimeDeadlineMs?: number;
558
570
  }): { timeoutMs: number; timeoutErrorMessage?: string } {
559
- const toolTimeoutMs = resolveRuntimeToolReceiptWaitTimeoutMs(
560
- input.requestInput,
561
- );
571
+ const toolTimeoutMs =
572
+ typeof input.timeoutMs === 'number' && Number.isFinite(input.timeoutMs)
573
+ ? Math.max(1, Math.ceil(input.timeoutMs))
574
+ : resolveRuntimeToolReceiptWaitTimeoutMs(input.requestInput);
562
575
  const remainingMs = resolveRuntimeDeadlineRemainingMs(
563
576
  input.runtimeDeadlineMs,
564
577
  );
@@ -1256,6 +1269,7 @@ async function executeTool(
1256
1269
  transientHttpRetrySafe = false,
1257
1270
  abortSignal?: AbortSignal,
1258
1271
  runtimeDeadlineMs?: number,
1272
+ runtimeTimeoutMs?: number,
1259
1273
  ): Promise<ToolExecuteResult> {
1260
1274
  if (args.toolId === 'test_wait_for_event' && workflowStep) {
1261
1275
  const result = await waitForSyntheticIntegrationEvent(
@@ -1278,6 +1292,7 @@ async function executeTool(
1278
1292
  transientHttpRetrySafe,
1279
1293
  abortSignal,
1280
1294
  runtimeDeadlineMs,
1295
+ runtimeTimeoutMs,
1281
1296
  );
1282
1297
  }
1283
1298
 
@@ -1291,6 +1306,7 @@ async function executeToolWithLifecycle(
1291
1306
  transientHttpRetrySafe = false,
1292
1307
  abortSignal?: AbortSignal,
1293
1308
  runtimeDeadlineMs?: number,
1309
+ runtimeTimeoutMs?: number,
1294
1310
  ): Promise<ToolExecuteResult> {
1295
1311
  callbacks?.onToolCalled?.(args.toolId, nowMs());
1296
1312
  try {
@@ -1303,6 +1319,7 @@ async function executeToolWithLifecycle(
1303
1319
  transientHttpRetrySafe,
1304
1320
  abortSignal,
1305
1321
  runtimeDeadlineMs,
1322
+ runtimeTimeoutMs,
1306
1323
  );
1307
1324
  } catch (error) {
1308
1325
  callbacks?.onToolFailed?.(args.toolId, nowMs());
@@ -1320,6 +1337,8 @@ function normalizeToolExecuteArgs(request: unknown): {
1320
1337
  input: Record<string, unknown>;
1321
1338
  force?: boolean;
1322
1339
  staleAfterSeconds?: number;
1340
+ timeoutMs?: number;
1341
+ receiptWaitMs?: number;
1323
1342
  } {
1324
1343
  if (!isToolExecuteRecord(request)) {
1325
1344
  throw new Error(
@@ -1345,6 +1364,14 @@ function normalizeToolExecuteArgs(request: unknown): {
1345
1364
  ...(typeof request.staleAfterSeconds === 'number'
1346
1365
  ? { staleAfterSeconds: request.staleAfterSeconds }
1347
1366
  : {}),
1367
+ ...(typeof request.timeoutMs === 'number' &&
1368
+ Number.isFinite(request.timeoutMs)
1369
+ ? { timeoutMs: request.timeoutMs }
1370
+ : {}),
1371
+ ...(typeof request.receiptWaitMs === 'number' &&
1372
+ Number.isFinite(request.receiptWaitMs)
1373
+ ? { receiptWaitMs: request.receiptWaitMs }
1374
+ : {}),
1348
1375
  };
1349
1376
  }
1350
1377
 
@@ -1441,6 +1468,7 @@ async function callToolDirect(
1441
1468
  transientHttpRetrySafe = false,
1442
1469
  abortSignal?: AbortSignal,
1443
1470
  runtimeDeadlineMs?: number,
1471
+ runtimeTimeoutMs?: number,
1444
1472
  ): Promise<ToolExecuteResult> {
1445
1473
  const { id, toolId, input } = args;
1446
1474
  const path = `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
@@ -1455,6 +1483,7 @@ async function callToolDirect(
1455
1483
  try {
1456
1484
  const runtimeApiTimeout = resolveToolRuntimeApiTimeout({
1457
1485
  requestInput: input,
1486
+ timeoutMs: runtimeTimeoutMs,
1458
1487
  runtimeDeadlineMs,
1459
1488
  });
1460
1489
  res = await fetchRuntimeApi(
@@ -1472,6 +1501,9 @@ async function callToolDirect(
1472
1501
  body: JSON.stringify({
1473
1502
  payload: input,
1474
1503
  metadata: { parent_run_id: req.runId },
1504
+ ...(req.integrationMode
1505
+ ? { integration_mode: req.integrationMode }
1506
+ : {}),
1475
1507
  }),
1476
1508
  },
1477
1509
  {
@@ -1628,6 +1660,7 @@ type WorkerToolBatchRequest = {
1628
1660
  receiptKey: string | null;
1629
1661
  force: boolean;
1630
1662
  receiptWaitMaxAttempts: number;
1663
+ runtimeTimeoutMs?: number;
1631
1664
  toolId: string;
1632
1665
  input: Record<string, unknown>;
1633
1666
  workflowStep?: WorkflowStep;
@@ -1646,17 +1679,62 @@ type PreparedWorkerToolBatchRequests = {
1646
1679
  deferredClaimedRequests: Promise<ClaimedWorkerToolBatchRequest[]>[];
1647
1680
  };
1648
1681
 
1682
+ function resolveClaimedWorkerToolRuntimeTimeoutMs(
1683
+ claimedRequests: ClaimedWorkerToolBatchRequest[],
1684
+ input: { runtimeDeadlineMs?: number },
1685
+ ): number | undefined {
1686
+ return resolveWorkerToolRuntimeTimeoutMs(claimedRequests, {
1687
+ resolveOwnerTimeoutMs: (request) =>
1688
+ resolveToolRuntimeApiTimeout({
1689
+ requestInput: request.input,
1690
+ timeoutMs: request.runtimeTimeoutMs,
1691
+ runtimeDeadlineMs: input.runtimeDeadlineMs,
1692
+ }).timeoutMs,
1693
+ });
1694
+ }
1695
+
1649
1696
  const WORKER_TOOL_BATCH_GRACE_MS = 250;
1650
1697
  const MAP_EXECUTION_HEARTBEAT_INTERVAL_MS = 5_000;
1651
1698
  const MAP_INCREMENTAL_PERSIST_CHUNK_ROWS = 100;
1652
1699
  const MAP_INCREMENTAL_PERSIST_CHUNK_BYTES = 1 * 1024 * 1024;
1653
- const MAP_INCREMENTAL_PERSIST_INTERVAL_MS = 100;
1700
+ const MAP_INCREMENTAL_PERSIST_INTERVAL_MS = 1_000;
1701
+ const MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS = 1_000;
1654
1702
  /**
1655
1703
  * Bounded number of per-row failure samples carried in chunk summaries and the
1656
1704
  * map's terminal partial-failure log. Every failed row is persisted with its
1657
1705
  * full error in the runtime sheet; the samples just keep run logs readable.
1658
1706
  */
1659
1707
  const MAP_ROW_FAILURE_SAMPLE_LIMIT = 3;
1708
+
1709
+ class RuntimeReceiptPersistenceError extends Error {
1710
+ readonly errors: unknown[];
1711
+
1712
+ constructor(message: string, errors: unknown[] = []) {
1713
+ super(message);
1714
+ this.name = 'RuntimeReceiptPersistenceError';
1715
+ this.errors = errors;
1716
+ }
1717
+ }
1718
+
1719
+ function isRuntimeReceiptPersistenceError(error: unknown): boolean {
1720
+ if (!error || typeof error !== 'object') return false;
1721
+ if (error instanceof RuntimeReceiptPersistenceError) return true;
1722
+ if (error instanceof Error && error.name === 'RuntimeReceiptPersistenceError') {
1723
+ return true;
1724
+ }
1725
+ const nestedErrors = (error as { errors?: unknown }).errors;
1726
+ return (
1727
+ Array.isArray(nestedErrors) &&
1728
+ nestedErrors.some(isRuntimeReceiptPersistenceError)
1729
+ );
1730
+ }
1731
+
1732
+ function isRunFatalWorkerRowError(error: unknown): boolean {
1733
+ return (
1734
+ isRowIsolationExemptError(error) || isRuntimeReceiptPersistenceError(error)
1735
+ );
1736
+ }
1737
+
1660
1738
  // Fallback batch-chunk parallelism when a tool declares no provider rate hints.
1661
1739
  // Matches the prior hardcoded `Math.min(4, ...)` so undeclared providers keep
1662
1740
  // their previous batching behavior; declared providers tighten via the
@@ -1759,6 +1837,7 @@ class WorkerToolBatchScheduler {
1759
1837
  input: Record<string, unknown>,
1760
1838
  workflowStep?: WorkflowStep,
1761
1839
  options?: { force?: boolean; staleAfterSeconds?: number | null },
1840
+ runtimeOptions?: { timeoutMs?: number; receiptWaitMs?: number },
1762
1841
  ): Promise<unknown> {
1763
1842
  const providerActionVersion =
1764
1843
  await this.resolveToolActionCacheVersion(toolId);
@@ -1776,7 +1855,14 @@ class WorkerToolBatchScheduler {
1776
1855
  cacheKey: receiptKey,
1777
1856
  receiptKey,
1778
1857
  force: options?.force === true,
1779
- receiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(input),
1858
+ receiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(
1859
+ typeof runtimeOptions?.receiptWaitMs === 'number'
1860
+ ? { max_wait_ms: runtimeOptions.receiptWaitMs }
1861
+ : input,
1862
+ ),
1863
+ ...(typeof runtimeOptions?.timeoutMs === 'number'
1864
+ ? { runtimeTimeoutMs: runtimeOptions.timeoutMs }
1865
+ : {}),
1780
1866
  toolId,
1781
1867
  input,
1782
1868
  workflowStep,
@@ -1962,7 +2048,10 @@ class WorkerToolBatchScheduler {
1962
2048
  try {
1963
2049
  const receipt = await this.waitForDurableToolReceipt({
1964
2050
  receiptKey: input.receiptKey,
1965
- maxAttempts: request.receiptWaitMaxAttempts,
2051
+ maxAttempts: resolveWorkerToolReceiptGroupWaitMaxAttempts(
2052
+ input.group,
2053
+ (groupRequest) => groupRequest.receiptWaitMaxAttempts,
2054
+ ),
1966
2055
  });
1967
2056
  await this.resolveCompletedDurableToolReceiptGroup({
1968
2057
  group: input.group,
@@ -1982,6 +2071,7 @@ class WorkerToolBatchScheduler {
1982
2071
  !canReclaimTimedOutWorkerToolReceipt({
1983
2072
  ownerRunId: input.runningReceipt.runId,
1984
2073
  currentRunId: this.req.runId,
2074
+ updatedAt: input.runningReceipt.updatedAt,
1985
2075
  })
1986
2076
  ) {
1987
2077
  this.rejectRawRequests(input.group, waitError);
@@ -2070,7 +2160,10 @@ class WorkerToolBatchScheduler {
2070
2160
  try {
2071
2161
  const receipt = await this.waitForDurableToolReceipt({
2072
2162
  receiptKey: input.receiptKey,
2073
- maxAttempts: request.receiptWaitMaxAttempts,
2163
+ maxAttempts: resolveWorkerToolReceiptGroupWaitMaxAttempts(
2164
+ input.group,
2165
+ (groupRequest) => groupRequest.receiptWaitMaxAttempts,
2166
+ ),
2074
2167
  });
2075
2168
  await this.resolveCompletedDurableToolReceiptGroup({
2076
2169
  group: input.group,
@@ -2100,9 +2193,9 @@ class WorkerToolBatchScheduler {
2100
2193
  await this.failDurableToolRequest(claimed, error);
2101
2194
  return error;
2102
2195
  } catch (receiptError) {
2103
- return new AggregateError(
2104
- [error, receiptError],
2196
+ return new RuntimeReceiptPersistenceError(
2105
2197
  'Tool call failed and durable receipt could not be marked failed',
2198
+ [error, receiptError],
2106
2199
  );
2107
2200
  }
2108
2201
  }
@@ -2459,25 +2552,41 @@ class WorkerToolBatchScheduler {
2459
2552
  signal: this.abortSignal,
2460
2553
  });
2461
2554
  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
- );
2555
+ let result: unknown;
2556
+ try {
2557
+ result = await executeToolWithLifecycle(
2558
+ this.req,
2559
+ { id: request.id, toolId, input: request.input },
2560
+ request.workflowStep,
2561
+ this.callbacks,
2562
+ (retryAfterMs) =>
2563
+ this.reportBackpressure(toolId, retryAfterMs),
2564
+ () => this.governor.chargeBudget('retry'),
2565
+ toolContract?.retrySafeTransientHttp === true,
2566
+ this.abortSignal,
2567
+ this.runtimeDeadlineMs,
2568
+ resolveClaimedWorkerToolRuntimeTimeoutMs([claimed], {
2569
+ runtimeDeadlineMs: this.runtimeDeadlineMs,
2570
+ }),
2571
+ );
2572
+ } catch (error) {
2573
+ this.rejectRequests(
2574
+ claimed,
2575
+ await this.failureForRejectedToolRequest(claimed, error),
2576
+ );
2577
+ return;
2578
+ }
2473
2579
  this.settleRequests(
2474
2580
  claimed,
2475
2581
  await this.completeDurableToolRequest(claimed, result),
2476
2582
  );
2477
- } catch (error) {
2583
+ } catch (receiptError) {
2478
2584
  this.rejectRequests(
2479
2585
  claimed,
2480
- await this.failureForRejectedToolRequest(claimed, error),
2586
+ new RuntimeReceiptPersistenceError(
2587
+ 'Tool call succeeded but durable receipt could not be marked completed',
2588
+ [receiptError],
2589
+ ),
2481
2590
  );
2482
2591
  } finally {
2483
2592
  slot.release();
@@ -2629,6 +2738,9 @@ async function executeBatchedWorkerToolGroup(input: {
2629
2738
  toolContract?.retrySafeTransientHttp === true,
2630
2739
  input.abortSignal,
2631
2740
  input.runtimeDeadlineMs,
2741
+ resolveClaimedWorkerToolRuntimeTimeoutMs(batch.memberRequests, {
2742
+ runtimeDeadlineMs: input.runtimeDeadlineMs,
2743
+ }),
2632
2744
  );
2633
2745
  } catch (error) {
2634
2746
  input.callbacks?.onToolFailed?.(batch.batchOperation, nowMs());
@@ -2651,9 +2763,9 @@ async function executeBatchedWorkerToolGroup(input: {
2651
2763
  try {
2652
2764
  await input.failRequests(entry.request.memberRequests, entry.error);
2653
2765
  } catch (receiptError) {
2654
- rejection = new AggregateError(
2655
- [entry.error, receiptError],
2766
+ rejection = new RuntimeReceiptPersistenceError(
2656
2767
  'Tool call failed and durable receipts could not be marked failed',
2768
+ [entry.error, receiptError],
2657
2769
  );
2658
2770
  }
2659
2771
  for (const claimed of entry.request.memberRequests) {
@@ -2668,16 +2780,28 @@ async function executeBatchedWorkerToolGroup(input: {
2668
2780
  batchResult != null
2669
2781
  ? entry.request.splitResults(batchResult)
2670
2782
  : 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
- );
2783
+ let completedResults: unknown[];
2784
+ try {
2785
+ completedResults = await input.completeRequests(
2786
+ entry.request.memberRequests.map((claimed, index) => ({
2787
+ claimed,
2788
+ result: wrapWorkerToolResult(
2789
+ claimed.request.toolId,
2790
+ splitResults[index] ?? null,
2791
+ toolMetadataFallback(claimed.request.toolId),
2792
+ ),
2793
+ })),
2794
+ );
2795
+ } catch (receiptError) {
2796
+ const rejection = new RuntimeReceiptPersistenceError(
2797
+ 'Tool call succeeded but durable receipts could not be marked completed',
2798
+ [receiptError],
2799
+ );
2800
+ for (const claimed of entry.request.memberRequests) {
2801
+ input.rejectRequest(claimed, rejection);
2802
+ }
2803
+ continue;
2804
+ }
2681
2805
  for (let index = 0; index < completedResults.length; index += 1) {
2682
2806
  const claimed = entry.request.memberRequests[index]!;
2683
2807
  const request = claimed.request;
@@ -2690,9 +2814,9 @@ async function executeBatchedWorkerToolGroup(input: {
2690
2814
  try {
2691
2815
  await input.failRequests(input.requests, error);
2692
2816
  } catch (receiptError) {
2693
- rejection = new AggregateError(
2694
- [error, receiptError],
2817
+ rejection = new RuntimeReceiptPersistenceError(
2695
2818
  'Tool call failed and durable receipts could not be marked failed',
2819
+ [error, receiptError],
2696
2820
  );
2697
2821
  }
2698
2822
  for (const claimed of input.requests) {
@@ -2728,6 +2852,7 @@ type WorkerMapChunkSummary<T extends Record<string, unknown>> = {
2728
2852
  stepCellsSkipped: number;
2729
2853
  outputDatasetId: string;
2730
2854
  hash: string;
2855
+ fatalError?: string;
2731
2856
  preview: T[];
2732
2857
  cachedRows?: T[];
2733
2858
  };
@@ -3816,6 +3941,15 @@ async function persistCompletedMapRows(input: {
3816
3941
  rows,
3817
3942
  outputFields,
3818
3943
  });
3944
+ console.warn('[play-runner.persist_completed_map_rows.start]', {
3945
+ runId: req.runId,
3946
+ playName: req.playName,
3947
+ tableNamespace,
3948
+ rows: rows.length,
3949
+ outputFields,
3950
+ extraOutputFields: input.extraOutputFields ?? [],
3951
+ contractColumnCount: sheetContract.columns.length,
3952
+ });
3819
3953
  const persistRequest = {
3820
3954
  ...sessionScope,
3821
3955
  tableNamespace,
@@ -3830,6 +3964,8 @@ async function persistCompletedMapRows(input: {
3830
3964
  .filter((key): key is string => Boolean(key)),
3831
3965
  ),
3832
3966
  ];
3967
+ const expectedVisibleRows =
3968
+ expectedKeys.length > 0 ? expectedKeys.length : rows.length;
3833
3969
  const readVisibleRowCount = async () => {
3834
3970
  if (expectedKeys.length > 0) {
3835
3971
  const result = await harnessReadSheetDatasetRowKeys({
@@ -3852,12 +3988,24 @@ async function persistCompletedMapRows(input: {
3852
3988
  return result.rows.length;
3853
3989
  };
3854
3990
  const result = await harnessPersistCompletedSheetRows(persistRequest);
3991
+ console.warn('[play-runner.persist_completed_map_rows.result]', {
3992
+ runId: req.runId,
3993
+ playName: req.playName,
3994
+ tableNamespace,
3995
+ rows: rows.length,
3996
+ rowsWritten: result.rowsWritten,
3997
+ expectedVisibleRows,
3998
+ expectedKeyCount: expectedKeys.length,
3999
+ });
3855
4000
  let visibleRows = -1;
3856
4001
  let retryWritten: number | null = null;
3857
4002
  let retryVisible: number | null = null;
3858
- if (rows.length <= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS) {
4003
+ if (
4004
+ rows.length <= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS ||
4005
+ result.rowsWritten !== rows.length
4006
+ ) {
3859
4007
  visibleRows = await readVisibleRowCount();
3860
- if (visibleRows < rows.length) {
4008
+ if (visibleRows < expectedVisibleRows) {
3861
4009
  await harnessStartSheetDataset({
3862
4010
  ...sessionScope,
3863
4011
  tableNamespace,
@@ -3869,15 +4017,23 @@ async function persistCompletedMapRows(input: {
3869
4017
  const retry = await harnessPersistCompletedSheetRows(persistRequest);
3870
4018
  retryWritten = retry.rowsWritten;
3871
4019
  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
- );
4020
+ if (retryVisible >= expectedVisibleRows) {
4021
+ return {
4022
+ rows: rows.length,
4023
+ written: result.rowsWritten,
4024
+ visible: visibleRows,
4025
+ retryWritten,
4026
+ retryVisible,
4027
+ };
4028
+ }
4029
+ throw new Error(
4030
+ `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}.`,
4031
+ );
3876
4032
  }
3877
4033
  }
3878
- if (result.rowsWritten !== rows.length && visibleRows < rows.length) {
4034
+ if (result.rowsWritten !== rows.length && visibleRows < expectedVisibleRows) {
3879
4035
  throw new Error(
3880
- `Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; run ${req.runId}.`,
4036
+ `Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}/${expectedVisibleRows}; run ${req.runId}.`,
3881
4037
  );
3882
4038
  }
3883
4039
  return {
@@ -4932,7 +5088,7 @@ function createMinimalWorkerCtx(
4932
5088
  let persistFailure: unknown = null;
4933
5089
  let scheduledLiveUpdateTimer: ReturnType<typeof setTimeout> | null = null;
4934
5090
  let liveUpdateFlushChain: Promise<void> = Promise.resolve();
4935
- let liveUpdateFailure: unknown = null;
5091
+ let liveUpdateFailureCount = 0;
4936
5092
 
4937
5093
  const clearScheduledPersistTimer = () => {
4938
5094
  if (scheduledPersistTimer) {
@@ -5070,25 +5226,35 @@ function createMinimalWorkerCtx(
5070
5226
  const updates = pendingLiveRowUpdates.splice(0);
5071
5227
  const extraOutputFields = Array.from(generatedOutputFields);
5072
5228
  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;
5229
+ try {
5230
+ await applyLiveMapRowUpdates({
5231
+ req,
5232
+ tableNamespace: name,
5233
+ outputFields,
5234
+ extraOutputFields,
5235
+ updates,
5236
+ });
5237
+ } catch (error) {
5238
+ liveUpdateFailureCount += 1;
5239
+ if (liveUpdateFailureCount <= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
5240
+ emitEvent({
5241
+ type: 'log',
5242
+ level: 'warn',
5243
+ message:
5244
+ `Live row update flush failed for ctx.dataset("${name}") ` +
5245
+ `(non-fatal; terminal rows still persist): ${formatWorkerRowFailureMessage(error)}`,
5246
+ ts: nowMs(),
5247
+ });
5248
+ }
5249
+ }
5084
5250
  });
5251
+ liveUpdateFlushChain = task.catch(() => undefined);
5085
5252
  return task;
5086
5253
  };
5087
5254
 
5088
5255
  const scheduleLiveRowUpdates = () => {
5089
- if (liveUpdateFailure) return;
5090
5256
  if (
5091
- pendingLiveRowUpdates.length >= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS
5257
+ pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5092
5258
  ) {
5093
5259
  void flushLiveRowUpdates().catch(() => undefined);
5094
5260
  return;
@@ -5103,14 +5269,12 @@ function createMinimalWorkerCtx(
5103
5269
  const enqueueLiveRowUpdate = (
5104
5270
  update: Omit<PlayRowUpdate, 'rowId'> & { runId?: string },
5105
5271
  ): Promise<void> => {
5106
- if (liveUpdateFailure) {
5107
- return Promise.reject(liveUpdateFailure);
5108
- }
5109
5272
  pendingLiveRowUpdates.push(update);
5110
5273
  if (
5111
- pendingLiveRowUpdates.length >= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS
5274
+ pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5112
5275
  ) {
5113
- return flushLiveRowUpdates();
5276
+ void flushLiveRowUpdates().catch(() => undefined);
5277
+ return Promise.resolve();
5114
5278
  }
5115
5279
  scheduleLiveRowUpdates();
5116
5280
  return Promise.resolve();
@@ -5293,7 +5457,7 @@ function createMinimalWorkerCtx(
5293
5457
  } catch (rowError) {
5294
5458
  // Abort/budget errors stay run-fatal and leave no partial
5295
5459
  // state: rethrow immediately without recording the row.
5296
- if (isRowIsolationExemptError(rowError)) {
5460
+ if (isRunFatalWorkerRowError(rowError)) {
5297
5461
  throw rowError;
5298
5462
  }
5299
5463
  const message = formatWorkerRowFailureMessage(rowError);
@@ -5393,6 +5557,43 @@ function createMinimalWorkerCtx(
5393
5557
  concurrency,
5394
5558
  },
5395
5559
  });
5560
+ const buildRowFailureSamples = () =>
5561
+ failedRowEntries
5562
+ .map((failure, executedIndex) =>
5563
+ failure
5564
+ ? {
5565
+ rowKey: uniqueRowsToExecuteEntries[executedIndex]!.rowKey,
5566
+ error: failure.error,
5567
+ }
5568
+ : null,
5569
+ )
5570
+ .filter(
5571
+ (sample): sample is { rowKey: string; error: string } =>
5572
+ sample !== null,
5573
+ )
5574
+ .slice(0, MAP_ROW_FAILURE_SAMPLE_LIMIT);
5575
+ const fatalMapChunkSummary = async (
5576
+ error: unknown,
5577
+ ): Promise<WorkerMapChunkSummary<T & Record<string, unknown>>> => ({
5578
+ chunkIndex,
5579
+ rangeStart: baseOffset + chunkStart,
5580
+ rangeEnd: baseOffset + chunkStart,
5581
+ rowsRead: chunkRows.length,
5582
+ rowsWritten: 0,
5583
+ rowsExecuted: rowsToExecute.length,
5584
+ rowsCached: 0,
5585
+ rowsDuplicateReused: duplicateInputReuseCount,
5586
+ rowsInserted,
5587
+ rowsSkipped,
5588
+ rowsFailed: failedExecutedRows,
5589
+ rowFailureSamples: buildRowFailureSamples(),
5590
+ stepCellsCompleted,
5591
+ stepCellsSkipped,
5592
+ outputDatasetId: `map:${name}`,
5593
+ hash: await hashJson([]),
5594
+ fatalError: formatWorkerRowFailureMessage(error),
5595
+ preview: [],
5596
+ });
5396
5597
  const persistRowsStartedAt = nowMs();
5397
5598
  recordRunnerPerfTrace({
5398
5599
  req,
@@ -5407,7 +5608,6 @@ function createMinimalWorkerCtx(
5407
5608
  try {
5408
5609
  await flushLiveRowUpdates();
5409
5610
  await liveUpdateFlushChain;
5410
- if (liveUpdateFailure) throw liveUpdateFailure;
5411
5611
  await enqueuePersistExecutedRows();
5412
5612
  await persistFlushChain;
5413
5613
  if (persistFailure) throw persistFailure;
@@ -5433,13 +5633,16 @@ function createMinimalWorkerCtx(
5433
5633
  error: error instanceof Error ? error.message : String(error),
5434
5634
  },
5435
5635
  });
5436
- throw error;
5636
+ return await fatalMapChunkSummary(error);
5437
5637
  }
5438
5638
  const rejectedWorker = workerResults.find(
5439
5639
  (result): result is PromiseRejectedResult =>
5440
5640
  result.status === 'rejected',
5441
5641
  );
5442
5642
  if (rejectedWorker) {
5643
+ if (isRuntimeReceiptPersistenceError(rejectedWorker.reason)) {
5644
+ return await fatalMapChunkSummary(rejectedWorker.reason);
5645
+ }
5443
5646
  throw rejectedWorker.reason;
5444
5647
  }
5445
5648
  const resultByKey = new Map<string, T & Record<string, unknown>>();
@@ -5494,20 +5697,7 @@ function createMinimalWorkerCtx(
5494
5697
  0,
5495
5698
  executedRows.length - failedExecutedRows,
5496
5699
  );
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);
5700
+ const rowFailureSamples = buildRowFailureSamples();
5511
5701
  const publicOut = out.map((row) => publicCsvOutputRow(row));
5512
5702
  const keyedOut = outEntries.map(({ key, inputIndex, row }) => ({
5513
5703
  ...row,
@@ -5756,6 +5946,15 @@ function createMinimalWorkerCtx(
5756
5946
  continue;
5757
5947
  }
5758
5948
  const chunkResult = await runChunkStep(chunkRows, chunkStart, chunkIndex);
5949
+ if (chunkResult.fatalError) {
5950
+ throw new Error(
5951
+ `ctx.dataset("${name}") stopped after a runtime persistence failure ` +
5952
+ `outside the retryable chunk step. Provider calls already executed for ` +
5953
+ `${chunkResult.rowsExecuted} row(s), so the chunk was not retried. ` +
5954
+ `Fix the runtime persistence cause and re-run to resume. ` +
5955
+ `First error: ${chunkResult.fatalError}`,
5956
+ );
5957
+ }
5759
5958
  totalRowsWritten += chunkResult.rowsWritten;
5760
5959
  totalRowsExecuted += chunkResult.rowsExecuted;
5761
5960
  totalRowsCached += chunkResult.rowsCached;
@@ -6136,6 +6335,10 @@ function createMinimalWorkerCtx(
6136
6335
  force: request.force === true,
6137
6336
  staleAfterSeconds: request.staleAfterSeconds,
6138
6337
  },
6338
+ {
6339
+ timeoutMs: request.timeoutMs,
6340
+ receiptWaitMs: request.receiptWaitMs,
6341
+ },
6139
6342
  );
6140
6343
  },
6141
6344
  },
@@ -6274,6 +6477,7 @@ function createMinimalWorkerCtx(
6274
6477
  orgId: req.orgId,
6275
6478
  callbackBaseUrl: req.callbackUrl,
6276
6479
  baseUrl: req.baseUrl,
6480
+ integrationMode: req.integrationMode ?? null,
6277
6481
  parentExecutorToken: req.executorToken,
6278
6482
  userEmail: req.userEmail ?? '',
6279
6483
  profile: 'workers_edge',
@@ -7652,11 +7856,18 @@ function extractMaxCreditsPerRun(contractSnapshot: unknown): number | null {
7652
7856
  : null;
7653
7857
  }
7654
7858
 
7859
+ function shouldSkipWorkerComputeBilling(req: RunRequest): boolean {
7860
+ return req.integrationMode === 'fixture' || req.integrationMode === 'eval_stub';
7861
+ }
7862
+
7655
7863
  async function finalizeWorkerComputeBilling(input: {
7656
7864
  req: RunRequest;
7657
7865
  success: boolean;
7658
7866
  actionEstimate: number;
7659
7867
  }): Promise<void> {
7868
+ if (shouldSkipWorkerComputeBilling(input.req)) {
7869
+ return;
7870
+ }
7660
7871
  const maxCreditsPerRun = extractMaxCreditsPerRun(input.req.contractSnapshot);
7661
7872
  await postRuntimeApi(input.req.baseUrl, input.req.executorToken, {
7662
7873
  action: 'compute_billing_finalize',
@@ -7712,6 +7923,7 @@ function runRequestFromWorkflowParams(
7712
7923
  callbackUrl: String(params.baseUrl ?? ''),
7713
7924
  executorToken: String(params.executorToken ?? ''),
7714
7925
  baseUrl: String(params.baseUrl ?? ''),
7926
+ integrationMode: normalizeIntegrationMode(params.integrationMode),
7715
7927
  orgId: String(params.orgId ?? ''),
7716
7928
  playName: String(params.playName ?? ''),
7717
7929
  userEmail: typeof params.userEmail === 'string' ? params.userEmail : null,
@@ -8051,7 +8263,7 @@ export class TenantWorkflow extends WorkflowEntrypoint<
8051
8263
  // somewhere inside executeRunRequest. If it doesn't appear, the
8052
8264
  // throw is in the framework wrapper between the loader and run().
8053
8265
  console.log(
8054
- `${runPrefix} TenantWorkflow.run entered baseUrl=${req.baseUrl}`,
8266
+ `${runPrefix} TenantWorkflow.run entered baseUrl=${req.baseUrl} integrationMode=${req.integrationMode ?? 'default'}`,
8055
8267
  );
8056
8268
  captureCoordinatorBinding(this.env);
8057
8269
  captureRuntimeApiBinding(this.env);
@@ -8065,6 +8277,7 @@ export class TenantWorkflow extends WorkflowEntrypoint<
8065
8277
  ms: 0,
8066
8278
  extra: {
8067
8279
  hasWorkflowStep: true,
8280
+ integrationMode: req.integrationMode ?? null,
8068
8281
  },
8069
8282
  });
8070
8283
  // Fire the one-time wiring probe (deduplicated across runs in the