deepline 0.1.209 → 0.1.210

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.
@@ -2445,8 +2445,6 @@ function runRequestFromPlayWorkflowParams(
2445
2445
  preloadedDbSessions: params.preloadedDbSessions ?? null,
2446
2446
  runtimeTestFaultHeader: params.runtimeTestFaultHeader ?? null,
2447
2447
  testPolicyOverrides: params.testPolicyOverrides ?? null,
2448
- inlineChildRunRegistered:
2449
- params.runtimeBackend === 'cf_workflows_dynamic_worker_inline_child',
2450
2448
  coordinatorUrl: params.coordinatorUrl ?? null,
2451
2449
  coordinatorInternalToken: params.coordinatorInternalToken ?? null,
2452
2450
  totalRows: params.totalRows,
@@ -32,6 +32,7 @@ import {
32
32
  type WorkflowEvent,
33
33
  type WorkflowStep,
34
34
  } from 'cloudflare:workers';
35
+ import { AsyncLocalStorage } from 'node:async_hooks';
35
36
  import {
36
37
  deterministicMapChunkStepName,
37
38
  type ExecutionPlan,
@@ -40,7 +41,6 @@ import {
40
41
  STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
41
42
  STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
42
43
  } from '../../../shared_libs/play-runtime/runtime-constants';
43
- import { childSubmitIdempotencyKey } from '../../../shared_libs/play-runtime/child-run-id';
44
44
  import {
45
45
  createPlayExecutionGovernor,
46
46
  type GovernanceSnapshot,
@@ -104,7 +104,7 @@ import {
104
104
  import { createDeferredPlayDataset } from '../../../shared_libs/plays/dataset';
105
105
  import {
106
106
  buildDurableCtxCallCacheKey,
107
- buildDurableRunPlaySemanticKey,
107
+ buildDurableRunPlayInvocationScope,
108
108
  } from '../../../shared_libs/play-runtime/durable-call-cache';
109
109
  import {
110
110
  resolveRuntimeToolReceiptWaitMaxAttempts,
@@ -368,8 +368,6 @@ type RunRequest = {
368
368
  /** Internal ctx.runPlay lineage. Public SDK/users never see this. */
369
369
  playCallGovernance?: PlayCallGovernanceSnapshot | null;
370
370
  preloadedDbSessions?: PreloadedRuntimeDbSession[] | null;
371
- /** Coordinator already created the child run row before invoking /run-inline. */
372
- inlineChildRunRegistered?: boolean | null;
373
371
  /** Cloudflare coordinator URL for direct Workflow control-plane signals. */
374
372
  coordinatorUrl?: string | null;
375
373
  /** Request-scoped coordinator auth token for preview/dev direct control calls. */
@@ -1148,11 +1146,29 @@ async function drainRunnerPerfTraces(req: RunRequest): Promise<void> {
1148
1146
 
1149
1147
  let workerRequestSequence = 0;
1150
1148
 
1149
+ const workerRunPlayRowContext = new AsyncLocalStorage<{
1150
+ tableNamespace: string;
1151
+ rowKey: string;
1152
+ fieldName: string;
1153
+ }>();
1154
+
1151
1155
  function makeRequestId(): string {
1152
1156
  workerRequestSequence = (workerRequestSequence + 1) % Number.MAX_SAFE_INTEGER;
1153
1157
  return `worker-request-${workerRequestSequence}`;
1154
1158
  }
1155
1159
 
1160
+ async function executeFreshChildInvocation<T>(
1161
+ input: {
1162
+ parentRunId: string;
1163
+ key: string;
1164
+ invocationScope: string;
1165
+ },
1166
+ execute: (invocationId: string) => Promise<T>,
1167
+ ): Promise<T> {
1168
+ const invocationId = `${input.parentRunId}:runPlay:${input.key}:${input.invocationScope}`;
1169
+ return await execute(invocationId);
1170
+ }
1171
+
1156
1172
  function makeRuntimeSheetAttempt(input: {
1157
1173
  runId: string;
1158
1174
  runAttempt?: number | null;
@@ -1665,27 +1681,6 @@ async function hashChildPlayEventKey(input: unknown): Promise<string> {
1665
1681
  return (await hashJson(input)).slice(0, 32);
1666
1682
  }
1667
1683
 
1668
- function workerChildRevisionFingerprint(input: {
1669
- playName: string;
1670
- manifest: PlayRuntimeManifest | null | undefined;
1671
- }): string | null {
1672
- const manifest = input.manifest;
1673
- if (!manifest) return null;
1674
- return sha256Hex(
1675
- stableStringify({
1676
- playId: manifest.playName?.trim() || input.playName,
1677
- codeFormat: 'cjs_module',
1678
- artifactHash: manifest.artifactHash ?? null,
1679
- graphHash: manifest.graphHash ?? null,
1680
- sourceHash: null,
1681
- sourceCodeHash:
1682
- typeof manifest.sourceCode === 'string'
1683
- ? sha256Hex(manifest.sourceCode)
1684
- : null,
1685
- }),
1686
- );
1687
- }
1688
-
1689
1684
  type RuntimeResolvedPlayResponse = {
1690
1685
  manifest?: PlayRuntimeManifest | null;
1691
1686
  };
@@ -5580,37 +5575,49 @@ function createMinimalWorkerCtx(
5580
5575
  })
5581
5576
  : null,
5582
5577
  });
5583
- const resolved = await executeWorkerStepResolver(
5584
- value,
5585
- enriched,
5586
- rowCtx,
5587
- absoluteIndex,
5588
- previousCell,
5589
- isWorkerStepProgram(value)
5590
- ? {
5591
- parentField: key,
5592
- path: [],
5593
- outputs: stepProgramOutputs,
5594
- onOutput: async (stepOutput) => {
5595
- generatedOutputFields.add(stepOutput.columnName);
5596
- const status = stepOutput.status ?? 'completed';
5597
- if (status === 'skipped') stepCellsSkipped += 1;
5598
- else stepCellsCompleted += 1;
5599
- await enqueueLiveRowUpdate({
5600
- key: entry.rowKey,
5601
- dataPatch: {
5602
- [stepOutput.columnName]: stepOutput.value,
5603
- },
5604
- cellMetaPatch: {
5605
- [stepOutput.columnName]: {
5606
- status,
5607
- stage: stepOutput.stepId,
5608
- },
5578
+ const resolved = await workerRunPlayRowContext.run(
5579
+ {
5580
+ tableNamespace: name,
5581
+ rowKey: entry.rowKey,
5582
+ fieldName: key,
5583
+ },
5584
+ () =>
5585
+ executeWorkerStepResolver(
5586
+ value,
5587
+ enriched,
5588
+ rowCtx,
5589
+ absoluteIndex,
5590
+ previousCell,
5591
+ isWorkerStepProgram(value)
5592
+ ? {
5593
+ parentField: key,
5594
+ path: [],
5595
+ outputs: stepProgramOutputs,
5596
+ onOutput: async (stepOutput) => {
5597
+ generatedOutputFields.add(
5598
+ stepOutput.columnName,
5599
+ );
5600
+ const status =
5601
+ stepOutput.status ?? 'completed';
5602
+ if (status === 'skipped')
5603
+ stepCellsSkipped += 1;
5604
+ else stepCellsCompleted += 1;
5605
+ await enqueueLiveRowUpdate({
5606
+ key: entry.rowKey,
5607
+ dataPatch: {
5608
+ [stepOutput.columnName]: stepOutput.value,
5609
+ },
5610
+ cellMetaPatch: {
5611
+ [stepOutput.columnName]: {
5612
+ status,
5613
+ stage: stepOutput.stepId,
5614
+ },
5615
+ },
5616
+ });
5609
5617
  },
5610
- });
5611
- },
5612
- }
5613
- : undefined,
5618
+ }
5619
+ : undefined,
5620
+ ),
5614
5621
  );
5615
5622
  enriched[key] = resolved.value;
5616
5623
  fieldOutputs[key] = resolved.value;
@@ -6933,7 +6940,6 @@ function createMinimalWorkerCtx(
6933
6940
  options?: {
6934
6941
  description?: string;
6935
6942
  timeoutMs?: number;
6936
- staleAfterSeconds?: number;
6937
6943
  },
6938
6944
  ): Promise<unknown> {
6939
6945
  const normalizedKey = normalizeContextKey(key, 'runPlay');
@@ -6947,34 +6953,49 @@ function createMinimalWorkerCtx(
6947
6953
  playRef: resolvedName,
6948
6954
  cache: childManifestCache,
6949
6955
  });
6950
- const receiptKey = buildDurableCtxCallCacheKey({
6951
- orgId: req.orgId,
6952
- playId: req.playName,
6953
- kind: 'runPlay',
6954
- id: normalizedKey,
6955
- semanticKey: buildDurableRunPlaySemanticKey({
6956
- childPlayName: resolvedName,
6957
- childRevisionFingerprint: workerChildRevisionFingerprint({
6958
- playName: resolvedName,
6959
- manifest: childManifest,
6960
- }),
6961
- input,
6962
- }),
6963
- staleAfterSeconds: options?.staleAfterSeconds,
6956
+ const rowScope = workerRunPlayRowContext.getStore();
6957
+ const invocationScope = buildDurableRunPlayInvocationScope({
6958
+ childPlayName: resolvedName,
6959
+ input,
6960
+ rowScope: rowScope
6961
+ ? {
6962
+ fieldName: rowScope.fieldName,
6963
+ rowKey: rowScope.rowKey,
6964
+ tableNamespace: rowScope.tableNamespace,
6965
+ }
6966
+ : null,
6964
6967
  });
6965
- return await executeWithRuntimeReceipt(
6966
- receiptKey,
6967
- async (receiptContext, wasFailed) => {
6968
+ return await executeFreshChildInvocation(
6969
+ {
6970
+ parentRunId: req.runId,
6971
+ key: normalizedKey,
6972
+ invocationScope,
6973
+ },
6974
+ async (childInvocationId) => {
6968
6975
  // The Governor owns the play-call lineage: forkChild does the cycle
6969
6976
  // guard, depth/per-parent/playCall/descendant budget charges, and
6970
6977
  // returns the snapshot to thread into the child so budgets accumulate
6971
- // across isolates. Charged inside the receipt boundary so a replay
6972
- // (cache hit) never double-charges.
6973
- const childRunId = `${req.runId}:child:${normalizedKey}`;
6974
- const childGovernance = await governor.forkChild({
6975
- childPlayName: resolvedName,
6976
- childRunId,
6977
- });
6978
+ // across isolates. Every ctx.runPlay call is a fresh child invocation.
6979
+ const childRunId = `${req.runId}:child:${childInvocationId}`;
6980
+ // Checkpoint governance admission separately from the child result.
6981
+ // Workflow replay reuses this admission and the coordinator's stable
6982
+ // child launch identity, but ctx.runPlay never caches child output.
6983
+ const forkChild = () =>
6984
+ governor.forkChild({
6985
+ childPlayName: resolvedName,
6986
+ childRunId,
6987
+ });
6988
+ const childGovernance = workflowStep
6989
+ ? await (
6990
+ workflowStep.do as unknown as <T>(
6991
+ name: string,
6992
+ callback: () => Promise<T>,
6993
+ ) => Promise<T>
6994
+ )(
6995
+ `runPlay-governance:${sha256Hex(childInvocationId).slice(0, 24)}`,
6996
+ forkChild,
6997
+ )
6998
+ : await forkChild();
6978
6999
  const nextDepth = childGovernance.callDepth;
6979
7000
  const nextParentCalls =
6980
7001
  governor.snapshot().parentChildCalls[req.playName] ?? 0;
@@ -7047,11 +7068,7 @@ function createMinimalWorkerCtx(
7047
7068
  options?.timeoutMs == null && !childNeedsWorkflowScheduler,
7048
7069
  body: {
7049
7070
  name: resolvedName,
7050
- childIdempotencyKey: childSubmitIdempotencyKey({
7051
- receiptKey,
7052
- wasFailed,
7053
- leaseId: receiptContext.leaseId,
7054
- }),
7071
+ childIdempotencyKey: childInvocationId,
7055
7072
  input: isRecord(input) ? input : {},
7056
7073
  orgId: req.orgId,
7057
7074
  callbackUrl: req.callbackUrl,
@@ -7329,12 +7346,6 @@ function createMinimalWorkerCtx(
7329
7346
  childPlaySlot?.release();
7330
7347
  }
7331
7348
  },
7332
- {
7333
- forceFailedRefresh: true,
7334
- repairRunningReceiptForSameRunAfterWaitTimeout: true,
7335
- runningReceiptWaitMaxAttempts:
7336
- resolveRuntimeToolReceiptWaitMaxAttempts(input),
7337
- },
7338
7349
  );
7339
7350
  },
7340
7351
  async fetch(
@@ -7615,13 +7626,6 @@ async function handleRunInline(
7615
7626
  const probeStartedAt = nowMs();
7616
7627
  await probeHarnessOnce(env, runPrefix);
7617
7628
  traceInline('inline.probe_harness', probeStartedAt);
7618
- if (!req.inlineChildRunRegistered) {
7619
- const registerStartedAt = nowMs();
7620
- await registerInlineChildRun(req);
7621
- traceInline('inline.register_child_run', registerStartedAt);
7622
- } else {
7623
- traceInline('inline.register_child_run', nowMs(), { skipped: true });
7624
- }
7625
7629
  const executeStartedAt = nowMs();
7626
7630
  const output = await executeRunRequest(
7627
7631
  req,
@@ -7667,46 +7671,6 @@ async function handleRunInline(
7667
7671
  }
7668
7672
  }
7669
7673
 
7670
- async function registerInlineChildRun(req: RunRequest): Promise<void> {
7671
- const snapshot = isRecord(req.contractSnapshot) ? req.contractSnapshot : {};
7672
- const artifactMetadata = isRecord(snapshot.artifactMetadata)
7673
- ? snapshot.artifactMetadata
7674
- : {};
7675
- const governance = req.playCallGovernance;
7676
- await postRuntimeApi(req.baseUrl, req.executorToken, {
7677
- action: 'start_inline_child_run',
7678
- playName: req.playName,
7679
- runId: req.runId,
7680
- parentRunId: governance?.parentRunId,
7681
- rootRunId: governance?.rootRunId,
7682
- workflowFamilyKey:
7683
- governance?.rootRunId ?? governance?.parentRunId ?? req.runId,
7684
- artifactStorageKey:
7685
- typeof artifactMetadata.storageKey === 'string'
7686
- ? artifactMetadata.storageKey
7687
- : undefined,
7688
- artifactHash:
7689
- typeof artifactMetadata.artifactHash === 'string'
7690
- ? artifactMetadata.artifactHash
7691
- : undefined,
7692
- graphHash:
7693
- typeof artifactMetadata.graphHash === 'string'
7694
- ? artifactMetadata.graphHash
7695
- : undefined,
7696
- runtimeBackend: 'workers_edge',
7697
- schedulerBackend: 'inline_child',
7698
- executionProfile: 'workers_edge',
7699
- maxCreditsPerRun: extractMaxCreditsPerRun(req.contractSnapshot),
7700
- staticPipeline: snapshot.staticPipeline ?? null,
7701
- source:
7702
- snapshot.source === 'published' ||
7703
- snapshot.source === 'ad_hoc' ||
7704
- snapshot.source === 'draft'
7705
- ? snapshot.source
7706
- : 'published',
7707
- });
7708
- }
7709
-
7710
7674
  /** Cap on run log lines retained in the terminal output compatibility shape. */
7711
7675
  const RUN_LOG_BUFFER_LIMIT = 500;
7712
7676
  /** Min wall-clock interval between live run-ledger flushes during a run. */
@@ -54,6 +54,7 @@ import {
54
54
  markWorkerToolReceiptResultCached,
55
55
  markWorkerToolReceiptResultExecution,
56
56
  planWorkerToolReceiptGroups,
57
+ resolveRunningReceiptWaitMaxAttempts,
57
58
  resolveWorkerToolReceiptGroupWaitMaxAttempts,
58
59
  resolveWorkerToolRuntimeTimeoutMs,
59
60
  } from './tool-receipts';
@@ -1232,10 +1233,17 @@ export class WorkerToolBatchScheduler {
1232
1233
  try {
1233
1234
  const result = await this.waitForDurableToolReceipt({
1234
1235
  receiptKey,
1235
- maxAttempts: resolveWorkerToolReceiptGroupWaitMaxAttempts(
1236
- group,
1237
- (request) => request.receiptWaitMaxAttempts,
1238
- ),
1236
+ maxAttempts: resolveRunningReceiptWaitMaxAttempts({
1237
+ ownerRunId: claim.receipt.runId,
1238
+ currentRunId: this.options.req.runId,
1239
+ updatedAt: claim.receipt.updatedAt,
1240
+ requestedMaxAttempts:
1241
+ resolveWorkerToolReceiptGroupWaitMaxAttempts(
1242
+ group,
1243
+ (request) => request.receiptWaitMaxAttempts,
1244
+ ),
1245
+ nowMs: this.options.nowMs(),
1246
+ }),
1239
1247
  });
1240
1248
  if (groupState.forceDurableRefresh) {
1241
1249
  return await this.claimForcedDurableToolReceiptGroupAfterWait(
@@ -39,106 +39,39 @@ export function canReclaimTimedOutWorkerToolReceipt(input: {
39
39
  * conservative constant, not a heartbeat multiple.
40
40
  */
41
41
  export const RUNNING_RECEIPT_DEAD_OWNER_STALENESS_MS = 60_000;
42
+ export const RUNNING_RECEIPT_POLL_INTERVAL_MS = 250;
42
43
 
43
- /**
44
- * Decide whether a `running` receipt should be reclaimed IMMEDIATELY, skipping
45
- * the completion poll.
46
- *
47
- * The poll (`waitForCompletedRuntimeReceipt`) issues one service-binding
48
- * subrequest per 250ms tick, up to the receipt's wait budget (~1320 ticks for a
49
- * 5.5-minute default). On resume, a map can carry many receipts left `running`
50
- * by a DEAD prior run; polling each one burns its full tick budget waiting on a
51
- * receipt no live worker will complete, which blows Cloudflare's ~1000
52
- * subrequests-per-invocation budget and kills the resume with
53
- * "Too many subrequests by single Worker invocation" (prod resume deaths
54
- * 20260703t230209 / 20260703t232104).
55
- *
56
- * A receipt is a dead-owner orphan when it is owned by a DIFFERENT run (a
57
- * same-run `running` is this run replaying — its owner may still be live) and
58
- * its last write is stale (or missing entirely — a live owner would carry a
59
- * recent claim timestamp). Those reclaim immediately, matching the dispatch
60
- * code's own contract ("on resume it reclaims and re-executes"). A live or
61
- * freshly-claimed owner keeps the poll.
62
- */
63
- export function shouldReclaimRunningReceiptWithoutPolling(input: {
44
+ /** Bound a follower poll by when a different-run owner becomes reclaimable. */
45
+ export function resolveRunningReceiptWaitMaxAttempts(input: {
64
46
  ownerRunId?: string | null;
65
47
  currentRunId: string;
66
48
  updatedAt?: string | null;
49
+ requestedMaxAttempts: number;
67
50
  nowMs?: number;
68
51
  stalenessFloorMs?: number;
69
- }): boolean {
70
- const currentRunId = input.currentRunId.trim();
52
+ pollIntervalMs?: number;
53
+ }): number {
54
+ const requested = Math.max(1, Math.floor(input.requestedMaxAttempts));
71
55
  const ownerRunId = input.ownerRunId?.trim() ?? '';
72
- // Only a receipt owned by a different run can be a dead prior-run orphan.
73
- if (!currentRunId || !ownerRunId || ownerRunId === currentRunId) return false;
74
- const floorMs =
75
- input.stalenessFloorMs ?? RUNNING_RECEIPT_DEAD_OWNER_STALENESS_MS;
76
- // No freshness signal at all: treat as stale. A live owner would carry a
77
- // recent claim timestamp; an orphan from a dead run may carry none.
78
- if (!input.updatedAt) return true;
79
- const updatedAtMs = Date.parse(input.updatedAt);
80
- if (!Number.isFinite(updatedAtMs)) return true;
81
- const now = input.nowMs ?? Date.now();
82
- return now - updatedAtMs >= floorMs;
83
- }
84
-
85
- /** Metadata a `running` receipt carries about its owner and last write. */
86
- export interface RunningReceiptOwnershipInput {
87
- ownerRunId?: string | null;
88
- currentRunId: string;
89
- updatedAt?: string | null;
90
- nowMs?: number;
91
- stalenessFloorMs?: number;
92
- }
93
-
94
- /** Outcome of polling a `running` receipt for completion. */
95
- export type RunningReceiptPollOutcome =
96
- /** The poll saw the receipt complete and already resolved the group. */
97
- | { kind: 'completed' }
98
- /** The poll timed out; the receipt is still `running`. */
99
- | { kind: 'timed_out'; error: unknown }
100
- /** The poll failed for a non-timeout reason (e.g. the receipt failed). */
101
- | { kind: 'errored'; error: unknown };
102
-
103
- /**
104
- * Orchestrate the resume-time handling of a `running` receipt: reclaim it
105
- * immediately when it is a dead-owner orphan, otherwise poll for completion and
106
- * only reclaim after the poll times out. Kept as a pure, dependency-injected
107
- * function (the poll and reclaim side effects are passed in) so the
108
- * "skip the poll for a dead owner" decision is unit-testable with a poll counter
109
- * — the Worker scheduler that owns the real effects imports `cloudflare:workers`
110
- * and cannot be loaded under vitest.
111
- *
112
- * Behavior (must match the scheduler's prior inline logic exactly):
113
- * - dead-owner + reclaim-eligible → `reclaim` now, never polling;
114
- * - otherwise `poll`; on `completed` the group is already resolved (return []);
115
- * on a non-timeout error `reject` and stop; on timeout, `reclaim` if still
116
- * eligible else `reject` with the timeout error.
117
- */
118
- export async function reclaimRunningReceiptGroupAfterWait<TClaimed>(input: {
119
- ownership: RunningReceiptOwnershipInput;
120
- timeoutError: unknown;
121
- poll: () => Promise<RunningReceiptPollOutcome>;
122
- reclaim: (rejectionOnFallthrough: unknown) => Promise<TClaimed[]>;
123
- reject: (error: unknown) => void;
124
- }): Promise<TClaimed[]> {
125
- if (
126
- shouldReclaimRunningReceiptWithoutPolling(input.ownership) &&
127
- canReclaimTimedOutWorkerToolReceipt(input.ownership)
128
- ) {
129
- return await input.reclaim(input.timeoutError);
130
- }
131
- const outcome = await input.poll();
132
- if (outcome.kind === 'completed') return [];
133
- if (outcome.kind === 'errored') {
134
- input.reject(outcome.error);
135
- return [];
136
- }
137
- if (!canReclaimTimedOutWorkerToolReceipt(input.ownership)) {
138
- input.reject(outcome.error);
139
- return [];
56
+ const currentRunId = input.currentRunId.trim();
57
+ if (!ownerRunId || !currentRunId || ownerRunId === currentRunId) {
58
+ return requested;
140
59
  }
141
- return await input.reclaim(outcome.error);
60
+ const updatedAtMs = input.updatedAt ? Date.parse(input.updatedAt) : NaN;
61
+ if (!Number.isFinite(updatedAtMs)) return 1;
62
+ const remainingMs = Math.max(
63
+ 0,
64
+ updatedAtMs +
65
+ (input.stalenessFloorMs ?? RUNNING_RECEIPT_DEAD_OWNER_STALENESS_MS) -
66
+ (input.nowMs ?? Date.now()),
67
+ );
68
+ const attemptsUntilReclaim = Math.max(
69
+ 1,
70
+ Math.floor(
71
+ remainingMs / (input.pollIntervalMs ?? RUNNING_RECEIPT_POLL_INTERVAL_MS),
72
+ ) + 1,
73
+ );
74
+ return Math.min(requested, attemptsUntilReclaim);
142
75
  }
143
76
 
144
77
  export function canReclaimFailedWorkerToolReceipt(input: {
@@ -400,7 +400,7 @@ export type StepOptions<Row, Value = unknown> = {
400
400
  * Legacy dataset-column recompute flag accepted for older authored plays.
401
401
  *
402
402
  * Prefer putting freshness on the actual reusable call
403
- * (`ctx.tools.execute`, `ctx.step`, `ctx.fetch`, or `ctx.runPlay`).
403
+ * (`ctx.tools.execute`, `ctx.step`, or `ctx.fetch`).
404
404
  */
405
405
  readonly recompute?: boolean;
406
406
  /** Legacy error-recompute flag accepted for older authored plays. */
@@ -889,7 +889,7 @@ export interface DeeplinePlayRuntimeContext {
889
889
  key: string,
890
890
  playRef: string | PlayReferenceLike,
891
891
  input: Record<string, unknown>,
892
- options: { description: string; staleAfterSeconds?: number },
892
+ options: { description: string },
893
893
  ): Promise<TOutput>;
894
894
 
895
895
  /**
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.209',
109
+ version: '0.1.210',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.209',
112
+ latest: '0.1.210',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -44,8 +44,7 @@ export interface ChildRunIdInputs {
44
44
  key?: string | null;
45
45
  /**
46
46
  * Canonical durable runPlay semantic key when the launching substrate has one.
47
- * In-process runtimes pass the same key used by durable receipts so row-scoped
48
- * receipt identity and child run identity cannot drift apart.
47
+ * This scopes each fresh child invocation to its parent call site and row.
49
48
  */
50
49
  runPlaySemanticKey?: string | null;
51
50
  /** Child input payload (semantic-fallback input). */
@@ -57,9 +56,9 @@ export interface ChildRunIdInputs {
57
56
  }
58
57
 
59
58
  /**
60
- * A failed durable runPlay receipt must launch a fresh child attempt while
61
- * ordinary submit retries keep attaching to the original child. The receipt
62
- * lease is the attempt fence, so it is the only extra identity required.
59
+ * A failed child launch must use a fresh attempt identity while ordinary
60
+ * transport retries keep attaching to the original child. The caller-provided
61
+ * lease is the attempt fence.
63
62
  */
64
63
  export function childSubmitIdempotencyKey(input: {
65
64
  receiptKey: string;
@@ -107,7 +107,7 @@ import {
107
107
  } from './tool-execute-retry-policy';
108
108
  import {
109
109
  buildDurableCtxCallCacheKey,
110
- buildDurableRunPlaySemanticKey,
110
+ buildDurableRunPlayInvocationScope,
111
111
  buildDurableToolAggregateProviderIdempotencyKey,
112
112
  buildDurableToolAggregateReceiptKey,
113
113
  buildDurableToolCallAuthScopeDigest,
@@ -819,7 +819,7 @@ function stableDigest(value: string): string {
819
819
  return sha256Hex(value);
820
820
  }
821
821
 
822
- type DurableCtxOperation = 'step' | 'tool' | 'fetch' | 'runPlay';
822
+ type DurableCtxOperation = 'step' | 'tool' | 'fetch';
823
823
 
824
824
  function durableCtxKey(input: {
825
825
  orgId?: string | null;
@@ -6370,8 +6370,6 @@ export class PlayContextImpl {
6370
6370
  `Unable to resolve play "${resolvedName}" for ctx.runPlay(...).`,
6371
6371
  );
6372
6372
  }
6373
- const childRevisionFingerprint =
6374
- resolvedPlayRevisionFingerprint(resolvedPlay);
6375
6373
  const childExecutionDecision = resolveChildExecutionStrategy({
6376
6374
  pipeline: resolvedPlay.staticPipeline,
6377
6375
  });
@@ -6413,14 +6411,12 @@ export class PlayContextImpl {
6413
6411
  ),
6414
6412
  );
6415
6413
  }
6416
- const runPlaySemanticKey = buildDurableRunPlaySemanticKey({
6414
+ const runPlayInvocationScope = buildDurableRunPlayInvocationScope({
6417
6415
  childPlayName: resolvedName,
6418
- childRevisionFingerprint,
6419
6416
  input,
6420
6417
  rowScope: runPlayRowScope
6421
6418
  ? {
6422
6419
  fieldName: runPlayRowScope.fieldName,
6423
- rowId: runPlayRowScope.rowId,
6424
6420
  rowKey: runPlayRowScope.rowKey ?? null,
6425
6421
  tableNamespace: runPlayRowScope.tableNamespace ?? null,
6426
6422
  }
@@ -6458,7 +6454,7 @@ export class PlayContextImpl {
6458
6454
  parentRunId: this.currentRunId,
6459
6455
  parentPlayName: this.#options.playName,
6460
6456
  key: normalizedKey,
6461
- runPlaySemanticKey,
6457
+ runPlaySemanticKey: runPlayInvocationScope,
6462
6458
  input,
6463
6459
  graphHash: null,
6464
6460
  }),
@@ -6700,24 +6696,7 @@ export class PlayContextImpl {
6700
6696
  }
6701
6697
  };
6702
6698
 
6703
- return await this.executeWithRuntimeReceipt<TOutput>(
6704
- 'runPlay',
6705
- normalizedKey,
6706
- this.currentRunId,
6707
- {
6708
- semanticKey: runPlaySemanticKey,
6709
- staleAfterSeconds: options?.staleAfterSeconds,
6710
- markSkipped: () => {
6711
- this.log(
6712
- `ctx.runPlay(${normalizedKey}): no-op due completed receipt`,
6713
- );
6714
- },
6715
- repairRunningReceiptForSameRunAfterWaitTimeout: true,
6716
- runningReceiptWaitMaxAttempts:
6717
- resolveRuntimeToolReceiptWaitMaxAttempts(input),
6718
- execute: executePlayCall,
6719
- },
6720
- );
6699
+ return await executePlayCall();
6721
6700
  } finally {
6722
6701
  scheduledChildPlayCall?.release();
6723
6702
  }
@@ -281,7 +281,6 @@ export type CustomerDbQueryHandler = (
281
281
 
282
282
  export interface PlayCallOptions {
283
283
  description?: string;
284
- staleAfterSeconds?: number;
285
284
  }
286
285
 
287
286
  export interface StepOptions {
@@ -591,7 +590,7 @@ export interface ContextOptions {
591
590
  runId?: string;
592
591
  /** Physical executor run that owns leases for an inline child context. */
593
592
  runtimeReceiptOwnerRunId?: string;
594
- /** Logical invocation scope for nested step/fetch/runPlay receipt identity. */
593
+ /** Logical invocation scope for receipts created inside an inline child. */
595
594
  runtimeReceiptScope?: string;
596
595
  runAttempt?: number | null;
597
596
  /**
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  export const DURABLE_CALL_CACHE_POLICY_VERSION = 'call-cache-v1';
11
11
 
12
- export type DurableCallKind = 'tool' | 'step' | 'fetch' | 'runPlay';
12
+ export type DurableCallKind = 'tool' | 'step' | 'fetch';
13
13
 
14
14
  function validateStaleAfterSeconds(staleAfterSeconds?: number | null): void {
15
15
  if (staleAfterSeconds === undefined || staleAfterSeconds === null) {
@@ -121,16 +121,14 @@ export function buildDurableCtxCallCacheKey(input: {
121
121
  return `ctx:${orgId}:call:${input.kind}:${normalizedId}:${digest}`;
122
122
  }
123
123
 
124
- export function buildDurableRunPlaySemanticKey(input: {
124
+ export function buildDurableRunPlayInvocationScope(input: {
125
125
  childPlayName: string;
126
126
  input: Record<string, unknown>;
127
- childRevisionFingerprint?: string | null;
128
127
  rowScope?: Record<string, unknown> | null;
129
128
  }): string {
130
129
  return sha256Hex(
131
130
  stableStringify({
132
131
  childPlayName: input.childPlayName.trim(),
133
- childRevisionFingerprint: input.childRevisionFingerprint ?? null,
134
132
  input: input.input,
135
133
  rowScope: input.rowScope ?? null,
136
134
  }),
package/dist/cli/index.js CHANGED
@@ -623,10 +623,10 @@ var SDK_RELEASE = {
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
- version: "0.1.209",
626
+ version: "0.1.210",
627
627
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
628
628
  supportPolicy: {
629
- latest: "0.1.209",
629
+ latest: "0.1.210",
630
630
  minimumSupported: "0.1.53",
631
631
  deprecatedBelow: "0.1.53",
632
632
  commandMinimumSupported: [
@@ -17215,12 +17215,9 @@ function renderColumnRunIfFunction(command) {
17215
17215
  if (!command.run_if_js) {
17216
17216
  return null;
17217
17217
  }
17218
- if (command.play) {
17219
- return `(__dlRawRow) => { const row = __dlPrepareEnrichRow(__dlRawRow, [${stringLiteral(command.alias)}]); const input = row; const context = row;
17218
+ return `(__dlRawRow) => { const row = __dlPrepareEnrichRow(__dlRawRow, [${stringLiteral(command.alias)}]); const input = row; const context = row;
17220
17219
  ${indent(renderJavascriptBody(command.run_if_js), 6)}
17221
17220
  }`;
17222
- }
17223
- return renderRunIfFunction(command);
17224
17221
  }
17225
17222
  function renderCombinedRunIfFunction(precheck, runIfSource) {
17226
17223
  if (!runIfSource) {
@@ -608,10 +608,10 @@ var SDK_RELEASE = {
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
- version: "0.1.209",
611
+ version: "0.1.210",
612
612
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
613
613
  supportPolicy: {
614
- latest: "0.1.209",
614
+ latest: "0.1.210",
615
615
  minimumSupported: "0.1.53",
616
616
  deprecatedBelow: "0.1.53",
617
617
  commandMinimumSupported: [
@@ -17244,12 +17244,9 @@ function renderColumnRunIfFunction(command) {
17244
17244
  if (!command.run_if_js) {
17245
17245
  return null;
17246
17246
  }
17247
- if (command.play) {
17248
- return `(__dlRawRow) => { const row = __dlPrepareEnrichRow(__dlRawRow, [${stringLiteral(command.alias)}]); const input = row; const context = row;
17247
+ return `(__dlRawRow) => { const row = __dlPrepareEnrichRow(__dlRawRow, [${stringLiteral(command.alias)}]); const input = row; const context = row;
17249
17248
  ${indent(renderJavascriptBody(command.run_if_js), 6)}
17250
17249
  }`;
17251
- }
17252
- return renderRunIfFunction(command);
17253
17250
  }
17254
17251
  function renderCombinedRunIfFunction(precheck, runIfSource) {
17255
17252
  if (!runIfSource) {
package/dist/index.d.mts CHANGED
@@ -3219,7 +3219,7 @@ type StepOptions<Row, Value = unknown> = {
3219
3219
  * Legacy dataset-column recompute flag accepted for older authored plays.
3220
3220
  *
3221
3221
  * Prefer putting freshness on the actual reusable call
3222
- * (`ctx.tools.execute`, `ctx.step`, `ctx.fetch`, or `ctx.runPlay`).
3222
+ * (`ctx.tools.execute`, `ctx.step`, or `ctx.fetch`).
3223
3223
  */
3224
3224
  readonly recompute?: boolean;
3225
3225
  /** Legacy error-recompute flag accepted for older authored plays. */
@@ -3607,7 +3607,6 @@ interface DeeplinePlayRuntimeContext {
3607
3607
  */
3608
3608
  runPlay<TOutput = unknown>(key: string, playRef: string | PlayReferenceLike, input: Record<string, unknown>, options: {
3609
3609
  description: string;
3610
- staleAfterSeconds?: number;
3611
3610
  }): Promise<TOutput>;
3612
3611
  /**
3613
3612
  * Emit a log line visible in `play tail` and the play's progress logs.
package/dist/index.d.ts CHANGED
@@ -3219,7 +3219,7 @@ type StepOptions<Row, Value = unknown> = {
3219
3219
  * Legacy dataset-column recompute flag accepted for older authored plays.
3220
3220
  *
3221
3221
  * Prefer putting freshness on the actual reusable call
3222
- * (`ctx.tools.execute`, `ctx.step`, `ctx.fetch`, or `ctx.runPlay`).
3222
+ * (`ctx.tools.execute`, `ctx.step`, or `ctx.fetch`).
3223
3223
  */
3224
3224
  readonly recompute?: boolean;
3225
3225
  /** Legacy error-recompute flag accepted for older authored plays. */
@@ -3607,7 +3607,6 @@ interface DeeplinePlayRuntimeContext {
3607
3607
  */
3608
3608
  runPlay<TOutput = unknown>(key: string, playRef: string | PlayReferenceLike, input: Record<string, unknown>, options: {
3609
3609
  description: string;
3610
- staleAfterSeconds?: number;
3611
3610
  }): Promise<TOutput>;
3612
3611
  /**
3613
3612
  * Emit a log line visible in `play tail` and the play's progress logs.
package/dist/index.js CHANGED
@@ -422,10 +422,10 @@ var SDK_RELEASE = {
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
- version: "0.1.209",
425
+ version: "0.1.210",
426
426
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
427
427
  supportPolicy: {
428
- latest: "0.1.209",
428
+ latest: "0.1.210",
429
429
  minimumSupported: "0.1.53",
430
430
  deprecatedBelow: "0.1.53",
431
431
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -352,10 +352,10 @@ var SDK_RELEASE = {
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
- version: "0.1.209",
355
+ version: "0.1.210",
356
356
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
357
357
  supportPolicy: {
358
- latest: "0.1.209",
358
+ latest: "0.1.210",
359
359
  minimumSupported: "0.1.53",
360
360
  deprecatedBelow: "0.1.53",
361
361
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.209",
3
+ "version": "0.1.210",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {