deepline 0.1.212 → 0.1.213

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 (36) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +167 -329
  2. package/dist/bundling-sources/sdk/src/index.ts +2 -0
  3. package/dist/bundling-sources/sdk/src/play.ts +8 -1
  4. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +11 -1
  5. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +49 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-placement.ts +14 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +57 -23
  9. package/dist/bundling-sources/shared_libs/play-runtime/completed-receipt-cache.ts +166 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +142 -101
  11. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +2 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +30 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +160 -2
  14. package/dist/bundling-sources/shared_libs/play-runtime/gateway-auth-session.ts +93 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/play-call-execution.ts +1 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +16 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +10 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +80 -3
  19. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +94 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +54 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +477 -85
  22. package/dist/bundling-sources/shared_libs/play-runtime/runtime-postgres-admission.ts +162 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +16 -17
  24. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
  25. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +2 -4
  26. package/dist/cli/index.js +2 -2
  27. package/dist/cli/index.mjs +2 -2
  28. package/dist/{compiler-manifest-j6z8qzpd.d.mts → compiler-manifest-BhgZ23A4.d.mts} +1 -0
  29. package/dist/{compiler-manifest-j6z8qzpd.d.ts → compiler-manifest-BhgZ23A4.d.ts} +1 -0
  30. package/dist/index.d.mts +8 -5
  31. package/dist/index.d.ts +8 -5
  32. package/dist/index.js +2 -2
  33. package/dist/index.mjs +2 -2
  34. package/dist/plays/bundle-play-file.d.mts +2 -2
  35. package/dist/plays/bundle-play-file.d.ts +2 -2
  36. package/package.json +1 -1
@@ -40,7 +40,6 @@ import {
40
40
  mapRowOutcomeRuntimeFields,
41
41
  resolveMapRowOutcomeKey,
42
42
  } from './map-row-outcome';
43
- import { RuntimeSheetRowsBlockedError } from './runtime-sheet-errors';
44
43
  import { buildChildRunId } from './child-run-id';
45
44
  import type { PlayCallGovernanceSnapshot } from './scheduler-backend';
46
45
  import {
@@ -1521,12 +1520,14 @@ export class PlayContextImpl {
1521
1520
 
1522
1521
  private recordPlayCallStep(input: {
1523
1522
  playId: string;
1523
+ execution?: 'inline' | 'child-workflow';
1524
1524
  description?: string | null;
1525
1525
  nestedSteps?: PlayStep[];
1526
1526
  }): void {
1527
1527
  const step = {
1528
1528
  type: 'play_call' as const,
1529
1529
  playId: input.playId,
1530
+ ...(input.execution ? { execution: input.execution } : {}),
1530
1531
  nestedSteps: input.nestedSteps ?? [],
1531
1532
  description: normalizeStepDescription(input.description ?? undefined),
1532
1533
  };
@@ -1903,6 +1904,7 @@ export class PlayContextImpl {
1903
1904
  }
1904
1905
  const claimed = await this.#options.claimRuntimeStepReceipt({
1905
1906
  key,
1907
+ leaseId: `receipt-lease:${crypto.randomUUID()}`,
1906
1908
  runId: this.currentReceiptOwnerRunId,
1907
1909
  runAttempt: this.currentRunAttempt,
1908
1910
  leaseAware: true,
@@ -1979,30 +1981,6 @@ export class PlayContextImpl {
1979
1981
  };
1980
1982
  }
1981
1983
 
1982
- private async skipRuntimeStepReceipt(
1983
- key: string,
1984
- runId: string,
1985
- leaseId?: string | null,
1986
- ): Promise<RuntimeStepReceipt | null> {
1987
- if (!this.#options.skipRuntimeStepReceipt) {
1988
- return null;
1989
- }
1990
- const skipped = await this.#options.skipRuntimeStepReceipt({
1991
- key,
1992
- runId,
1993
- runAttempt: this.currentRunAttempt,
1994
- ...(leaseId ? { leaseId } : {}),
1995
- });
1996
- if (!skipped || typeof skipped.key !== 'string' || !skipped.key.trim()) {
1997
- return null;
1998
- }
1999
- return {
2000
- ...skipped,
2001
- key: skipped.key.trim(),
2002
- runId: skipped.runId ?? null,
2003
- };
2004
- }
2005
-
2006
1984
  private async heartbeatRuntimeStepReceipt(
2007
1985
  key: string,
2008
1986
  _runId: string,
@@ -2310,6 +2288,11 @@ export class PlayContextImpl {
2310
2288
  ? await this.dispatchChunkedRuntimeReceiptRequest(uniqueKeys, (chunk) =>
2311
2289
  claimReceipts({
2312
2290
  keys: chunk,
2291
+ // A transport retry can replay this mutation after the store
2292
+ // committed but before the response body was consumed. Stable
2293
+ // caller-owned tokens distinguish that replay from a concurrent
2294
+ // claimant without weakening the provider-call execution fence.
2295
+ leaseIds: chunk.map(() => `receipt-lease:${crypto.randomUUID()}`),
2313
2296
  runId: this.currentReceiptOwnerRunId,
2314
2297
  runAttempt: this.currentRunAttempt,
2315
2298
  leaseAware: true,
@@ -2777,6 +2760,24 @@ export class PlayContextImpl {
2777
2760
  this.#options.completeRuntimeStepReceipt ||
2778
2761
  this.#options.completeRuntimeStepReceipts,
2779
2762
  ),
2763
+ ...(this.#options.acquireRuntimeReceiptExecutionLock &&
2764
+ this.#options.releaseRuntimeReceiptExecutionLock
2765
+ ? {
2766
+ acquireExecutionLock: ({ receiptKey, ownerExecutionId, ttlMs }) =>
2767
+ this.#options.acquireRuntimeReceiptExecutionLock!({
2768
+ key: receiptKey,
2769
+ runId: this.currentReceiptOwnerRunId,
2770
+ ownerExecutionId,
2771
+ ttlMs,
2772
+ }),
2773
+ releaseExecutionLock: ({ receiptKey, ownerExecutionId }) =>
2774
+ this.#options.releaseRuntimeReceiptExecutionLock!({
2775
+ key: receiptKey,
2776
+ runId: this.currentReceiptOwnerRunId,
2777
+ ownerExecutionId,
2778
+ }),
2779
+ }
2780
+ : {}),
2780
2781
  };
2781
2782
  }
2782
2783
 
@@ -2834,6 +2835,8 @@ export class PlayContextImpl {
2834
2835
  onClaimedResult?: (output: T, receiptKey: string) => T;
2835
2836
  shouldPersistFailure?: (error: unknown) => boolean;
2836
2837
  markRunningBeforeExecute?: boolean;
2838
+ requiresExecutionLock?: boolean;
2839
+ executionLockTtlMs?: number;
2837
2840
  execute: (context: { leaseId: string | null }) => Promise<T>;
2838
2841
  },
2839
2842
  ): Promise<T> {
@@ -2865,6 +2868,9 @@ export class PlayContextImpl {
2865
2868
  onClaimedResult: opts.onClaimedResult,
2866
2869
  shouldPersistFailure: opts.shouldPersistFailure,
2867
2870
  markRunningBeforeExecute: opts.markRunningBeforeExecute,
2871
+ completedCacheOnly: operation === 'tool',
2872
+ requiresExecutionLock: opts.requiresExecutionLock,
2873
+ executionLockTtlMs: opts.executionLockTtlMs,
2868
2874
  formatError: (error) => this.formatRuntimeError(error),
2869
2875
  log: (message) => this.log(message),
2870
2876
  execute: opts.execute,
@@ -4016,12 +4022,6 @@ export class PlayContextImpl {
4016
4022
  resolvedTableNamespace = normalizeTableNamespace(
4017
4023
  mapStartResult.tableNamespace,
4018
4024
  );
4019
- if ((mapStartResult.blockedRows?.length ?? 0) > 0) {
4020
- throw new RuntimeSheetRowsBlockedError({
4021
- tableNamespace: resolvedTableNamespace,
4022
- blockedRows: mapStartResult.blockedRows ?? [],
4023
- });
4024
- }
4025
4025
 
4026
4026
  const persistedRowIdentity = (
4027
4027
  row: Record<string, unknown>,
@@ -4456,12 +4456,6 @@ export class PlayContextImpl {
4456
4456
  resolvedTableNamespace = normalizeTableNamespace(
4457
4457
  mapStartResult.tableNamespace,
4458
4458
  );
4459
- if ((mapStartResult.blockedRows?.length ?? 0) > 0) {
4460
- throw new RuntimeSheetRowsBlockedError({
4461
- tableNamespace: resolvedTableNamespace,
4462
- blockedRows: mapStartResult.blockedRows ?? [],
4463
- });
4464
- }
4465
4459
  const persistedRowIdentity = (row: Record<string, unknown>, index = 0) =>
4466
4460
  resolveMapRowOutcomeKey(row) ?? rowIdentity(row, index);
4467
4461
  const pendingRowsByKey = new Map<string, Record<string, unknown>>();
@@ -4574,6 +4568,7 @@ export class PlayContextImpl {
4574
4568
  );
4575
4569
 
4576
4570
  this.activeMapCellMeta = new Map();
4571
+ const staleCompletionKeys = new Set<string>();
4577
4572
  const persistMapRows = async (rows: PersistableMapRow[]) => {
4578
4573
  if (!this.#options.onMapRowsCompleted || rows.length === 0) {
4579
4574
  return;
@@ -4597,7 +4592,7 @@ export class PlayContextImpl {
4597
4592
  );
4598
4593
  const flushStartedAt = Date.now();
4599
4594
  try {
4600
- await this.#options.onMapRowsCompleted!({
4595
+ const writeResult = await this.#options.onMapRowsCompleted!({
4601
4596
  playName: this.#options.playName,
4602
4597
  playId: this.#options.playId,
4603
4598
  runId: this.#options.runId,
@@ -4609,6 +4604,11 @@ export class PlayContextImpl {
4609
4604
  ),
4610
4605
  staticPipeline: this.#options.staticPipeline ?? null,
4611
4606
  });
4607
+ if (writeResult) {
4608
+ for (const key of writeResult.staleDroppedKeys ?? []) {
4609
+ staleCompletionKeys.add(key);
4610
+ }
4611
+ }
4612
4612
  this.resourceGovernor.observe({
4613
4613
  sheetFlushBytes: chunkBytes,
4614
4614
  sheetFlushLatencyMs: Date.now() - flushStartedAt,
@@ -4654,6 +4654,7 @@ export class PlayContextImpl {
4654
4654
  completedRows: duplicateReuseCount,
4655
4655
  },
4656
4656
  {
4657
+ emitTerminalEvent: false,
4657
4658
  onRowError: options?.onRowError,
4658
4659
  executionRowKeys: rowsToExecuteEntries.map((entry) => entry.rowKey),
4659
4660
  executionRowIndexes: rowsToExecuteEntries.map(
@@ -4751,7 +4752,49 @@ export class PlayContextImpl {
4751
4752
  }
4752
4753
  this.activeMapCellMeta = null;
4753
4754
 
4754
- if (this.#options.onMapRowsCompleted) {
4755
+ if (staleCompletionKeys.size > 0) {
4756
+ results = results.filter((row, index) => {
4757
+ const key = rowIdentity(row, itemOriginalIndexes[index] ?? index);
4758
+ return !staleCompletionKeys.has(key);
4759
+ });
4760
+ }
4761
+
4762
+ const durableCompletedRows = Math.max(
4763
+ 0,
4764
+ reusedCount + mapResult.completedRows.length - staleCompletionKeys.size,
4765
+ );
4766
+ const durableFailedRows = mapResult.failedRows.length;
4767
+ const terminalFrame = this.checkpoint.mapFrames?.[mapScope.mapInvocationId];
4768
+ if (terminalFrame) {
4769
+ this.setMapFrame({
4770
+ ...terminalFrame,
4771
+ status: 'completed',
4772
+ // Incremental rows add keys after their commit. Pure maps use the
4773
+ // durable aggregate count below instead of materializing up to millions
4774
+ // of keys only to mark the terminal frame.
4775
+ completedRowKeys: terminalFrame.completedRowKeys,
4776
+ pendingRowKeys: [],
4777
+ completedRowsCount: durableCompletedRows,
4778
+ pendingRowsCount: 0,
4779
+ failedRowsCount: durableFailedRows,
4780
+ activeBoundaryId: null,
4781
+ updatedAt: Date.now(),
4782
+ });
4783
+ }
4784
+ this.emitExecutionEvent({
4785
+ type: 'map.completed',
4786
+ mapInvocationId: mapScope.mapInvocationId,
4787
+ mapNodeId: mapScope.mapNodeId ?? null,
4788
+ logicalNamespace: mapScope.logicalNamespace,
4789
+ artifactTableNamespace: resolvedTableNamespace,
4790
+ completedRows: durableCompletedRows,
4791
+ failedRows: durableFailedRows,
4792
+ totalRows: totalInputCount,
4793
+ ...this.inlineChildAggregateEventFields(),
4794
+ at: Date.now(),
4795
+ });
4796
+
4797
+ if (this.#options.onMapRowsCompleted && staleCompletionKeys.size === 0) {
4755
4798
  results = await reconcileNodeRuntimeMapResultsWithPersistedSheet({
4756
4799
  mapName: normalizedMapNamespace,
4757
4800
  tableNamespace: resolvedTableNamespace,
@@ -4988,11 +5031,21 @@ export class PlayContextImpl {
4988
5031
  mapName: normalizedTableNamespace,
4989
5032
  budgetBytes: runtimeOptions?.retainedRowsMemoryBudgetBytes,
4990
5033
  });
4991
- const enqueueIncrementalPersist = (row: PersistableMapRow): void => {
4992
- void incrementalPersistence?.persistRows([row]).catch(() => {
4993
- // The final map-level flush awaits the same chain and surfaces the
4994
- // persistence failure loudly without holding a completed row slot open.
4995
- });
5034
+ const enqueueIncrementalPersist = (
5035
+ row: PersistableMapRow,
5036
+ onCommitted: () => void,
5037
+ ): void => {
5038
+ if (!incrementalPersistence) {
5039
+ onCommitted();
5040
+ return;
5041
+ }
5042
+ void incrementalPersistence
5043
+ .persistRows([row])
5044
+ .then(onCommitted)
5045
+ .catch(() => {
5046
+ // The final map-level flush awaits the same chain and surfaces the
5047
+ // persistence failure loudly without holding a completed row slot open.
5048
+ });
4996
5049
  };
4997
5050
 
4998
5051
  if (completedRows > 0 || pendingRows !== totalRows) {
@@ -5158,14 +5211,16 @@ export class PlayContextImpl {
5158
5211
  // this replaces ran AFTER every row had already computed, emitting 150k
5159
5212
  // post-hoc map.progress events and re-copying the completed-keys array
5160
5213
  // per row — all theater, no live progress value.
5161
- updateMapFrameProgress({
5162
- completedRowKeys: results.map((_row, index) =>
5163
- executionRowKey(
5164
- this.toOutputRow(items[index] as Record<string, unknown>),
5165
- index,
5214
+ if (!incrementalPersistence) {
5215
+ updateMapFrameProgress({
5216
+ completedRowKeys: results.map((_row, index) =>
5217
+ executionRowKey(
5218
+ this.toOutputRow(items[index] as Record<string, unknown>),
5219
+ index,
5220
+ ),
5166
5221
  ),
5167
- ),
5168
- });
5222
+ });
5223
+ }
5169
5224
  if (emitTerminalEvent) {
5170
5225
  updateMapFrameProgress({
5171
5226
  status: 'completed',
@@ -5346,9 +5401,6 @@ export class PlayContextImpl {
5346
5401
  });
5347
5402
  retainedRowsMemoryTracker.track(failedRow);
5348
5403
  failedRowsToPersist.push(failedRow);
5349
- updateMapFrameProgress({
5350
- failedRowKey: rowKey,
5351
- });
5352
5404
  this.emitScopedRowUpdate(rowKey, normalizedTableNamespace, {
5353
5405
  rowId: idx,
5354
5406
  status: 'failed',
@@ -5357,7 +5409,9 @@ export class PlayContextImpl {
5357
5409
  error: formattedError,
5358
5410
  dataPatch: {},
5359
5411
  });
5360
- enqueueIncrementalPersist(failedRow);
5412
+ enqueueIncrementalPersist(failedRow, () => {
5413
+ updateMapFrameProgress({ failedRowKey: rowKey });
5414
+ });
5361
5415
  return FAILED_ROW;
5362
5416
  }
5363
5417
  const cellValue = this.serializeCellValue(value);
@@ -5404,9 +5458,6 @@ export class PlayContextImpl {
5404
5458
 
5405
5459
  const merged = cloneCsvAliasedRow(baseRow, computedFields);
5406
5460
  activeFieldName = null;
5407
- updateMapFrameProgress({
5408
- completedRowKey: rowKey,
5409
- });
5410
5461
  this.emitScopedRowUpdate(rowKey, normalizedTableNamespace, {
5411
5462
  rowId: idx,
5412
5463
  status: 'completed',
@@ -5426,7 +5477,9 @@ export class PlayContextImpl {
5426
5477
  });
5427
5478
  retainedRowsMemoryTracker.track(completedRow);
5428
5479
  completedRowsToPersist.push(completedRow);
5429
- enqueueIncrementalPersist(completedRow);
5480
+ enqueueIncrementalPersist(completedRow, () => {
5481
+ updateMapFrameProgress({ completedRowKey: rowKey });
5482
+ });
5430
5483
  return publicRow;
5431
5484
  } catch (error) {
5432
5485
  if (isPlayRowExecutionSuspendedError(error)) {
@@ -5597,6 +5650,11 @@ export class PlayContextImpl {
5597
5650
  (result): result is Record<string, unknown> =>
5598
5651
  result !== WAITING_ROW && result !== FAILED_ROW,
5599
5652
  );
5653
+ // A row is acknowledged only after its terminal sheet batch commits. The
5654
+ // incremental promises publish their frame updates from the commit
5655
+ // continuation, so this barrier also makes the terminal map event derive
5656
+ // from durable row truth instead of resolver completion.
5657
+ await incrementalPersistence?.flush();
5600
5658
  if (emitTerminalEvent) {
5601
5659
  updateMapFrameProgress({
5602
5660
  status: 'completed',
@@ -6081,7 +6139,10 @@ export class PlayContextImpl {
6081
6139
  });
6082
6140
  let executionAuthScopeDigest =
6083
6141
  (await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
6084
- const cacheableToolResult = !isQueryResultDatasetReadRequest(toolId, input);
6142
+ const eventWaitHandler =
6143
+ (await this.#options.getIntegrationEventWaitHandler?.(toolId)) ?? null;
6144
+ const cacheableToolResult =
6145
+ !eventWaitHandler && !isQueryResultDatasetReadRequest(toolId, input);
6085
6146
  let durableCacheKey = await this.durableToolCallCacheKey({
6086
6147
  toolId,
6087
6148
  requestInput: input,
@@ -6089,8 +6150,6 @@ export class PlayContextImpl {
6089
6150
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
6090
6151
  });
6091
6152
  const checkpointCacheKeys = [durableCacheKey];
6092
- const eventWaitHandler =
6093
- (await this.#options.getIntegrationEventWaitHandler?.(toolId)) ?? null;
6094
6153
  const store = rowContext.getStore();
6095
6154
 
6096
6155
  const executeTool = async (context?: {
@@ -6306,12 +6365,10 @@ export class PlayContextImpl {
6306
6365
  authScopeAttempt < 2;
6307
6366
  authScopeAttempt += 1
6308
6367
  ) {
6309
- // Capture the lease the receipt store hands to `execute` for THIS
6310
- // attempt's key. On an auth-scope re-claim we mark that old-scope receipt
6311
- // `skipped` (below), and the skip is fenced on this run+attempt+lease so
6312
- // it can only supersede the receipt this attempt owns.
6313
- let claimedReceiptLeaseId: string | null = null;
6314
6368
  try {
6369
+ const toolRetryPolicy = await this.#options
6370
+ .getToolRetryPolicy?.(toolId)
6371
+ .catch(() => null);
6315
6372
  return await this.executeWithRuntimeReceipt(
6316
6373
  'tool',
6317
6374
  normalizedKey,
@@ -6320,6 +6377,15 @@ export class PlayContextImpl {
6320
6377
  receiptKey: durableCacheKey,
6321
6378
  semanticKey: toolRequestIdentity,
6322
6379
  force: toolCachePolicy.force,
6380
+ // Missing metadata is intentionally lock-free. Only explicitly
6381
+ // dangerous non-idempotent side effects enter the fence path.
6382
+ requiresExecutionLock:
6383
+ toolRetryPolicy?.requiresExecutionFence === true,
6384
+ executionLockTtlMs: Math.min(
6385
+ 600_000,
6386
+ resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs) ??
6387
+ 300_000,
6388
+ ),
6323
6389
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
6324
6390
  onClaimedResult: (output, receiptKey) =>
6325
6391
  markToolExecuteResultExecutionOutcome(output, {
@@ -6337,10 +6403,7 @@ export class PlayContextImpl {
6337
6403
  shouldPersistFailure: (error) =>
6338
6404
  !(error instanceof ToolExecuteAuthScopeChangedError),
6339
6405
  markRunningBeforeExecute: false,
6340
- execute: ({ leaseId }) => {
6341
- claimedReceiptLeaseId = leaseId;
6342
- return executeTool({ leaseId });
6343
- },
6406
+ execute: ({ leaseId }) => executeTool({ leaseId }),
6344
6407
  runningReceiptWaitMaxAttempts:
6345
6408
  resolveRuntimeToolReceiptWaitMaxAttempts(
6346
6409
  typeof options?.receiptWaitMs === 'number'
@@ -6356,36 +6419,9 @@ export class PlayContextImpl {
6356
6419
  ) {
6357
6420
  throw error;
6358
6421
  }
6359
- // Supersede the old-scope receipt so the run's node projection does not
6360
- // surface it as `failed`. shouldPersistFailure kept it out of the failed
6361
- // state, but the claim left it `running` with a now-orphaned lease under
6362
- // the OLD cache key; without an explicit terminal it projects as a failed
6363
- // node even though the work is about to be re-done under the new scope.
6364
- // `skipped` is the terminal for superseded work; the skip is fenced on
6365
- // this run+attempt+lease so it can only touch the receipt this attempt
6366
- // owns. Best-effort: a skip that cannot fence (store returns null) must
6367
- // not mask the reclaim itself.
6368
- const supersededReceiptKey = durableCacheKey;
6369
- try {
6370
- const skipped = await this.skipRuntimeStepReceipt(
6371
- supersededReceiptKey,
6372
- this.currentRunId,
6373
- claimedReceiptLeaseId,
6374
- );
6375
- if (skipped) {
6376
- this.log(
6377
- `ctx.tools.execute(${toolId}): auth_scope_changed_superseded ` +
6378
- `old-scope receipt ${toolRequestIdentity} (label: ${normalizedKey}) ` +
6379
- `marked skipped so the reclaim does not project as a failed node.`,
6380
- );
6381
- }
6382
- } catch (skipError) {
6383
- this.log(
6384
- `ctx.tools.execute(${toolId}): auth_scope_changed_supersede_skip_failed ` +
6385
- `(label: ${normalizedKey}); could not mark the old-scope receipt ` +
6386
- `skipped: ${this.formatRuntimeError(skipError)}`,
6387
- );
6388
- }
6422
+ // Completed-receipt cache misses never publish a running receipt. The
6423
+ // old auth-scope attempt therefore has no ownership metadata to clean
6424
+ // up; re-key under the refreshed credential scope and retry once.
6389
6425
  // Make the bounded auth-scope re-claim observable in run logs instead
6390
6426
  // of a silent continuation: the credential identity changed after the
6391
6427
  // receipt key was prepared, so we evict the cached digest, re-resolve,
@@ -6552,6 +6588,8 @@ export class PlayContextImpl {
6552
6588
  }
6553
6589
  const childExecutionDecision = resolveChildExecutionStrategy({
6554
6590
  pipeline: resolvedPlay.staticPipeline,
6591
+ execution: options?.execution,
6592
+ childPlayName: resolvedName,
6555
6593
  });
6556
6594
  const launchThroughScheduler =
6557
6595
  this.shouldLaunchChildPlaysThroughScheduler() &&
@@ -6696,6 +6734,7 @@ export class PlayContextImpl {
6696
6734
  }
6697
6735
  this.recordPlayCallStep({
6698
6736
  playId: resolvedName,
6737
+ execution: options?.execution,
6699
6738
  description: options?.description,
6700
6739
  });
6701
6740
  return checkpointResult.output;
@@ -6743,6 +6782,7 @@ export class PlayContextImpl {
6743
6782
  }
6744
6783
  this.recordPlayCallStep({
6745
6784
  playId: resolvedName,
6785
+ execution: options?.execution,
6746
6786
  description: options?.description,
6747
6787
  });
6748
6788
  return terminal.output;
@@ -6843,6 +6883,7 @@ export class PlayContextImpl {
6843
6883
  }
6844
6884
  this.recordPlayCallStep({
6845
6885
  playId: resolvedName,
6886
+ execution: options?.execution,
6846
6887
  description: options?.description,
6847
6888
  nestedSteps: childContext.getSteps(),
6848
6889
  });
@@ -214,6 +214,7 @@ export type PlayStep =
214
214
  | {
215
215
  type: 'play_call';
216
216
  playId: string;
217
+ execution?: 'inline' | 'child-workflow';
217
218
  results?: PlayStepRowResult[];
218
219
  nestedSteps: PlayStep[];
219
220
  description?: string;
@@ -252,6 +253,7 @@ export type PlayDatasetSubstep =
252
253
  | {
253
254
  type: 'play_call';
254
255
  playId: string;
256
+ execution?: 'inline' | 'child-workflow';
255
257
  results?: PlayStepRowResult[];
256
258
  nestedSteps: PlayStep[];
257
259
  description?: string;
@@ -24,6 +24,7 @@ import type { PreviousCell } from './cell-staleness';
24
24
  import type { MapRowOutcome } from './durability-store';
25
25
  import type { WorkReceiptFailureKind } from './work-receipts';
26
26
  import type { RunExecutionScope } from './run-execution-scope';
27
+ import type { PlayCallExecution } from './play-call-execution';
27
28
 
28
29
  export interface RowState {
29
30
  results: Map<string, unknown>;
@@ -69,12 +70,26 @@ export interface RuntimeStepReceipt {
69
70
  claimState?: 'claimed' | 'existing';
70
71
  }
71
72
 
73
+ export type AcquireRuntimeReceiptExecutionLockInput = {
74
+ key: string;
75
+ runId: string;
76
+ ownerExecutionId: string;
77
+ ttlMs: number;
78
+ };
79
+
80
+ export type ReleaseRuntimeReceiptExecutionLockInput = {
81
+ key: string;
82
+ runId: string;
83
+ ownerExecutionId: string;
84
+ };
85
+
72
86
  export interface GetRuntimeStepReceiptInput {
73
87
  key: string;
74
88
  }
75
89
 
76
90
  export interface ClaimRuntimeStepReceiptInput {
77
91
  key: string;
92
+ leaseId?: string;
78
93
  runId: string;
79
94
  runAttempt?: number | null;
80
95
  leaseAware?: boolean;
@@ -128,6 +143,7 @@ export interface GetRuntimeStepReceiptsInput {
128
143
 
129
144
  export interface ClaimRuntimeStepReceiptsInput {
130
145
  keys: string[];
146
+ leaseIds?: string[];
131
147
  runId: string;
132
148
  runAttempt?: number | null;
133
149
  leaseAware?: boolean;
@@ -281,6 +297,7 @@ export type CustomerDbQueryHandler = (
281
297
 
282
298
  export interface PlayCallOptions {
283
299
  description?: string;
300
+ execution?: PlayCallExecution;
284
301
  }
285
302
 
286
303
  export interface StepOptions {
@@ -597,7 +614,10 @@ export interface ContextOptions {
597
614
  rows: MapRowOutcome[];
598
615
  outputFields: string[];
599
616
  staticPipeline?: PlayStaticPipeline | null;
600
- }) => Promise<void>;
617
+ }) => Promise<void | {
618
+ updated: number;
619
+ staleDroppedKeys?: string[];
620
+ }>;
601
621
  playId?: string;
602
622
  runId?: string;
603
623
  /** Physical executor run that owns leases for an inline child context. */
@@ -634,6 +654,7 @@ export interface ContextOptions {
634
654
  getToolQueueHints?: (toolId: string) => Promise<readonly PlayQueueHint[]>;
635
655
  getToolRetryPolicy?: (toolId: string) => Promise<{
636
656
  retrySafeTransientHttp?: boolean;
657
+ requiresExecutionFence?: boolean;
637
658
  } | null>;
638
659
  getToolActionCacheVersion?: (toolId: string) => Promise<string> | string;
639
660
  getToolAuthScopeDigest?: (toolId: string) => Promise<string> | string;
@@ -672,6 +693,12 @@ export interface ContextOptions {
672
693
  getRuntimeStepReceipt?: (
673
694
  input: GetRuntimeStepReceiptInput,
674
695
  ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
696
+ acquireRuntimeReceiptExecutionLock?: (
697
+ input: AcquireRuntimeReceiptExecutionLockInput,
698
+ ) => Promise<{ ownerExecutionId: string; expiresAt: string } | null>;
699
+ releaseRuntimeReceiptExecutionLock?: (
700
+ input: ReleaseRuntimeReceiptExecutionLockInput,
701
+ ) => Promise<boolean>;
675
702
  getRuntimeStepReceipts?: (
676
703
  input: GetRuntimeStepReceiptsInput,
677
704
  ) =>
@@ -932,6 +959,7 @@ export type PlayStep =
932
959
  | {
933
960
  type: 'play_call';
934
961
  playId: string;
962
+ execution?: PlayCallExecution;
935
963
  results?: PlayStepRowResult[];
936
964
  nestedSteps: PlayStep[];
937
965
  description?: string;
@@ -971,6 +999,7 @@ export type PlayDatasetSubstep =
971
999
  | {
972
1000
  type: 'play_call';
973
1001
  playId: string;
1002
+ execution?: PlayCallExecution;
974
1003
  results?: PlayStepRowResult[];
975
1004
  nestedSteps: PlayStep[];
976
1005
  description?: string;