deepline 0.1.60 → 0.1.62

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.
package/dist/cli/index.js CHANGED
@@ -220,10 +220,10 @@ function resolveConfig(options) {
220
220
 
221
221
  // src/release.ts
222
222
  var SDK_RELEASE = {
223
- version: "0.1.60",
223
+ version: "0.1.62",
224
224
  apiContract: "2026-05-play-bootstrap-dataset-summary",
225
225
  supportPolicy: {
226
- latest: "0.1.60",
226
+ latest: "0.1.62",
227
227
  minimumSupported: "0.1.53",
228
228
  deprecatedBelow: "0.1.53"
229
229
  }
@@ -197,10 +197,10 @@ function resolveConfig(options) {
197
197
 
198
198
  // src/release.ts
199
199
  var SDK_RELEASE = {
200
- version: "0.1.60",
200
+ version: "0.1.62",
201
201
  apiContract: "2026-05-play-bootstrap-dataset-summary",
202
202
  supportPolicy: {
203
- latest: "0.1.60",
203
+ latest: "0.1.62",
204
204
  minimumSupported: "0.1.53",
205
205
  deprecatedBelow: "0.1.53"
206
206
  }
package/dist/index.js CHANGED
@@ -232,10 +232,10 @@ function resolveConfig(options) {
232
232
 
233
233
  // src/release.ts
234
234
  var SDK_RELEASE = {
235
- version: "0.1.60",
235
+ version: "0.1.62",
236
236
  apiContract: "2026-05-play-bootstrap-dataset-summary",
237
237
  supportPolicy: {
238
- latest: "0.1.60",
238
+ latest: "0.1.62",
239
239
  minimumSupported: "0.1.53",
240
240
  deprecatedBelow: "0.1.53"
241
241
  }
package/dist/index.mjs CHANGED
@@ -170,10 +170,10 @@ function resolveConfig(options) {
170
170
 
171
171
  // src/release.ts
172
172
  var SDK_RELEASE = {
173
- version: "0.1.60",
173
+ version: "0.1.62",
174
174
  apiContract: "2026-05-play-bootstrap-dataset-summary",
175
175
  supportPolicy: {
176
- latest: "0.1.60",
176
+ latest: "0.1.62",
177
177
  minimumSupported: "0.1.53",
178
178
  deprecatedBelow: "0.1.53"
179
179
  }
@@ -545,7 +545,7 @@ const WORKFLOW_POOL_PROTOCOL_VERSION =
545
545
  const WORKFLOW_POOL_DO_NAME = 'workflow-pool:v2';
546
546
  const WORKFLOW_POOL_START_EVENT_TYPE = 'play_start';
547
547
  const WORKFLOW_POOL_TTL_MS = 8 * 60 * 1000;
548
- const WORKFLOW_POOL_TARGET_SIZE = 16;
548
+ const WORKFLOW_POOL_TARGET_SIZE = 0;
549
549
  const WORKFLOW_POOL_READY_TIMEOUT_MS = 1_500;
550
550
  const WORKFLOW_POOL_READY_POLL_MS = 250;
551
551
  const WORKFLOW_POOL_REFILL_ON_MISS_TIMEOUT_MS = 2_500;
@@ -3637,10 +3637,10 @@ async function handleWorkflowRoute(input: {
3637
3637
  }
3638
3638
  return Response.json({ runId, status: 'cancelled' });
3639
3639
  }
3640
- if (!instance) {
3641
- return new Response('not found', { status: 404 });
3642
- }
3643
3640
  if (action === 'signal') {
3641
+ if (!instance) {
3642
+ return new Response('not found', { status: 404 });
3643
+ }
3644
3644
  const body = (await request.json().catch(() => ({}))) as Record<
3645
3645
  string,
3646
3646
  unknown
@@ -3697,11 +3697,14 @@ async function handleWorkflowRoute(input: {
3697
3697
  waitMs: 0,
3698
3698
  workflowStatus: 'terminal-cache',
3699
3699
  statusPolls: 0,
3700
- instanceId: instance.id,
3700
+ instanceId: instance?.id ?? null,
3701
3701
  },
3702
3702
  ...(includeTrace ? { coordinatorTrace } : {}),
3703
3703
  });
3704
3704
  }
3705
+ if (!instance) {
3706
+ return new Response('not found', { status: 404 });
3707
+ }
3705
3708
  const status = await instance.status();
3706
3709
  const workflowError = readWorkflowError(status);
3707
3710
  if (workflowError) {
@@ -117,6 +117,7 @@ import {
117
117
  harnessStartSheetDataset,
118
118
  setHarnessBinding,
119
119
  } from '../../../sdk/src/plays/harness-stub';
120
+ import { createHarnessWorkerReceiptStore } from './runtime/harness-receipt-store';
120
121
  import {
121
122
  applyCsvRenameProjection,
122
123
  stripCsvProjectedFields,
@@ -397,7 +398,7 @@ async function fetchRuntimeApi(
397
398
  ? RUNTIME_API_PLAY_RUN_TIMEOUT_MS
398
399
  : /^\/api\/v2\/integrations\/[^/]+\/execute$/.test(path)
399
400
  ? RUNTIME_API_INTEGRATION_EXECUTE_TIMEOUT_MS
400
- : RUNTIME_API_TIMEOUT_MS;
401
+ : RUNTIME_API_TIMEOUT_MS;
401
402
  const controller = new AbortController();
402
403
  let timeout: ReturnType<typeof setTimeout> | null = null;
403
404
  const timeoutPromise = new Promise<never>((_, reject) => {
@@ -619,10 +620,7 @@ function publicCsvOutputRow<T extends Record<string, unknown>>(row: T): T {
619
620
  const stripped = stripCsvProjectionMetadata(row) as Record<string, unknown>;
620
621
  const publicRow: Record<string, unknown> = {};
621
622
  for (const fieldName of Reflect.ownKeys(stripped)) {
622
- if (
623
- typeof fieldName === 'string' &&
624
- fieldName.startsWith('__deepline')
625
- ) {
623
+ if (typeof fieldName === 'string' && fieldName.startsWith('__deepline')) {
626
624
  continue;
627
625
  }
628
626
  const descriptor = Object.getOwnPropertyDescriptor(stripped, fieldName);
@@ -3141,6 +3139,7 @@ function createMinimalWorkerCtx(
3141
3139
  ): unknown {
3142
3140
  let playCallCount = 0;
3143
3141
  const parentChildCalls: Record<string, number> = {};
3142
+ const stepCallCounts: Record<string, number> = {};
3144
3143
  const inFlightChildCallsByPlayName: Record<string, number> = {};
3145
3144
  let inFlightChildPlayCalls = 0;
3146
3145
  const childPlaySlotWaiters: Array<() => void> = [];
@@ -3179,9 +3178,13 @@ function createMinimalWorkerCtx(
3179
3178
  };
3180
3179
  const rootGovernance = req.playCallGovernance;
3181
3180
  const rootRunId = rootGovernance?.rootRunId ?? req.runId;
3181
+ const receiptStore = env.HARNESS
3182
+ ? createHarnessWorkerReceiptStore({ executorToken: req.executorToken })
3183
+ : undefined;
3182
3184
  const executeWithRuntimeReceipt = async <T>(
3183
3185
  key: string,
3184
3186
  execute: () => Promise<T> | T,
3187
+ repairRunningReceiptForSameRun = false,
3185
3188
  ): Promise<T> => {
3186
3189
  const serialized = await runWorkerRuntimeReceiptBoundary<unknown>({
3187
3190
  baseUrl: req.baseUrl,
@@ -3191,10 +3194,38 @@ function createMinimalWorkerCtx(
3191
3194
  runId: req.runId,
3192
3195
  key,
3193
3196
  postRuntimeApi,
3197
+ receiptStore,
3194
3198
  execute: async () => serializeDurableStepValue(await execute()),
3199
+ repairRunningReceiptForSameRun,
3195
3200
  });
3196
3201
  return deserializeDurableStepValue(serialized) as T;
3197
3202
  };
3203
+ const executeWithWorkflowStep = async <T>(
3204
+ name: string,
3205
+ execute: () => Promise<T> | T,
3206
+ ): Promise<T> => {
3207
+ if (!workflowStep) {
3208
+ return await executeWithRuntimeReceipt(name, execute);
3209
+ }
3210
+ return await executeWithRuntimeReceipt(
3211
+ name,
3212
+ async () => {
3213
+ const serialized = await (
3214
+ workflowStep.do as unknown as (
3215
+ name: string,
3216
+ callback: () => Promise<unknown>,
3217
+ ) => Promise<unknown>
3218
+ )(name, async () => serializeDurableStepValue(await execute()));
3219
+ return deserializeDurableStepValue(serialized) as T;
3220
+ },
3221
+ true,
3222
+ );
3223
+ };
3224
+ const nextCtxStepReceiptKey = (name: string): string => {
3225
+ const count = stepCallCounts[name] ?? 0;
3226
+ stepCallCounts[name] = count + 1;
3227
+ return count === 0 ? `step:${name}` : `step:${name}:${count}`;
3228
+ };
3198
3229
  const staleRuntimeSuffix = (staleAfterSeconds?: number): string => {
3199
3230
  if (staleAfterSeconds === undefined) return '';
3200
3231
  if (
@@ -3952,11 +3983,8 @@ function createMinimalWorkerCtx(
3952
3983
  if (!normalizedName) {
3953
3984
  throw new Error('ctx.step(name, callback) requires a name.');
3954
3985
  }
3955
- // Static pipeline JS blocks are already Workflow steps in the Workers
3956
- // backend. Nesting another `step.do` here can leave preview runs parked
3957
- // inside the JS stage before they reach subsequent event waits.
3958
- return await executeWithRuntimeReceipt(
3959
- `step:${normalizedName}${staleRuntimeSuffix(options?.staleAfterSeconds)}`,
3986
+ return await executeWithWorkflowStep(
3987
+ `${nextCtxStepReceiptKey(normalizedName)}${staleRuntimeSuffix(options?.staleAfterSeconds)}`,
3960
3988
  callback,
3961
3989
  );
3962
3990
  },
@@ -4275,15 +4303,15 @@ function createMinimalWorkerCtx(
4275
4303
  req,
4276
4304
  allowInline:
4277
4305
  options?.timeoutMs == null && !childNeedsWorkflowScheduler,
4278
- body: {
4279
- name: resolvedName,
4280
- input: isRecord(input) ? input : {},
4281
- orgId: req.orgId,
4282
- callbackBaseUrl: req.callbackUrl,
4283
- baseUrl: req.baseUrl,
4284
- parentExecutorToken: req.executorToken,
4285
- userEmail: req.userEmail ?? '',
4286
- profile: 'workers_edge',
4306
+ body: {
4307
+ name: resolvedName,
4308
+ input: isRecord(input) ? input : {},
4309
+ orgId: req.orgId,
4310
+ callbackBaseUrl: req.callbackUrl,
4311
+ baseUrl: req.baseUrl,
4312
+ parentExecutorToken: req.executorToken,
4313
+ userEmail: req.userEmail ?? '',
4314
+ profile: 'workers_edge',
4287
4315
  manifest: childManifest,
4288
4316
  childPlayManifests: req.childPlayManifests ?? null,
4289
4317
  internalRunPlay: {
@@ -4607,7 +4635,7 @@ async function executeRunRequest(
4607
4635
  runtimeBackend: 'cf_workflows_dynamic_worker',
4608
4636
  },
4609
4637
  ];
4610
- let lastLedgerFlushAt = 0;
4638
+ let lastLedgerFlushAt = startedAt;
4611
4639
  let ledgerFlushInFlight: Promise<void> = Promise.resolve();
4612
4640
 
4613
4641
  const appendRunLogLine = (line: string) => {
@@ -4760,7 +4788,6 @@ async function executeRunRequest(
4760
4788
  terminalEvent: PlayRunLedgerEvent,
4761
4789
  ): Promise<void> => {
4762
4790
  if (!options?.persistResultDatasets) return;
4763
- await ledgerFlushInFlight.catch(() => undefined);
4764
4791
  const now = nowMs();
4765
4792
  pendingRunLogLines = runLogBuffer;
4766
4793
  dirtyProgressNodeIds = new Set([
@@ -4768,6 +4795,7 @@ async function executeRunRequest(
4768
4795
  ...Object.keys(stepProgressByNodeId),
4769
4796
  ]);
4770
4797
  pendingLedgerEvents = [...pendingLedgerEvents, terminalEvent];
4798
+ await ledgerFlushInFlight;
4771
4799
  const events = drainPendingLedgerEvents(now);
4772
4800
  if (events.length === 0) return;
4773
4801
  try {
@@ -4851,7 +4879,7 @@ async function executeRunRequest(
4851
4879
  });
4852
4880
  if (options?.persistResultDatasets) {
4853
4881
  const ledgerFlushWaitStartedAt = nowMs();
4854
- await ledgerFlushInFlight.catch(() => undefined);
4882
+ await ledgerFlushInFlight;
4855
4883
  recordRunnerPerfTrace({
4856
4884
  req,
4857
4885
  phase: 'runner.run_ledger_flush_wait',
@@ -4866,29 +4894,19 @@ async function executeRunRequest(
4866
4894
  });
4867
4895
  const terminalResult = trimResultForStatus(serializedResult);
4868
4896
  const terminalOccurredAt = nowMs();
4869
- const terminalLedgerPromise = (async () => {
4870
- const terminalUpdateStartedAt = nowMs();
4871
- await flushTerminalLedgerEvents({
4872
- type: 'run.completed',
4873
- runId: req.runId,
4874
- source: 'worker',
4875
- occurredAt: terminalOccurredAt,
4876
- result: terminalResult,
4877
- });
4878
- recordRunnerPerfTrace({
4879
- req,
4880
- phase: 'runner.terminal_ledger_append',
4881
- ms: nowMs() - terminalUpdateStartedAt,
4882
- });
4883
- })().catch((error) => {
4884
- console.error(
4885
- `[play-harness] non-fatal terminal ledger append failed runId=${req.runId}: ${
4886
- error instanceof Error ? error.message : String(error)
4887
- }`,
4888
- );
4897
+ const terminalUpdateStartedAt = nowMs();
4898
+ await flushTerminalLedgerEvents({
4899
+ type: 'run.completed',
4900
+ runId: req.runId,
4901
+ source: 'worker',
4902
+ occurredAt: terminalOccurredAt,
4903
+ result: terminalResult,
4904
+ });
4905
+ recordRunnerPerfTrace({
4906
+ req,
4907
+ phase: 'runner.terminal_ledger_append',
4908
+ ms: nowMs() - terminalUpdateStartedAt,
4889
4909
  });
4890
-
4891
- await terminalLedgerPromise;
4892
4910
 
4893
4911
  const billingStartedAt = nowMs();
4894
4912
  const billingPromise = finalizeWorkerComputeBilling({
@@ -0,0 +1,31 @@
1
+ import {
2
+ harnessClaimRuntimeReceipt,
3
+ harnessCompleteRuntimeReceipt,
4
+ harnessFailRuntimeReceipt,
5
+ } from '../../../../sdk/src/plays/harness-stub';
6
+ import type { WorkerRuntimeReceiptStore } from './receipts';
7
+
8
+ export function createHarnessWorkerReceiptStore(input: {
9
+ executorToken: string;
10
+ }): WorkerRuntimeReceiptStore {
11
+ return {
12
+ claimReceipt(command) {
13
+ return harnessClaimRuntimeReceipt({
14
+ executorToken: input.executorToken,
15
+ ...command,
16
+ });
17
+ },
18
+ completeReceipt(command) {
19
+ return harnessCompleteRuntimeReceipt({
20
+ executorToken: input.executorToken,
21
+ ...command,
22
+ });
23
+ },
24
+ failReceipt(command) {
25
+ return harnessFailRuntimeReceipt({
26
+ executorToken: input.executorToken,
27
+ ...command,
28
+ });
29
+ },
30
+ };
31
+ }
@@ -1,17 +1,14 @@
1
- export type RuntimeReceiptStatus =
2
- | 'pending'
3
- | 'running'
4
- | 'completed'
5
- | 'failed'
6
- | 'skipped';
7
-
8
- export type WorkerRuntimeReceipt = {
9
- key: string;
10
- status: RuntimeReceiptStatus;
11
- output?: unknown;
12
- error?: string;
13
- runId?: string | null;
14
- };
1
+ import type {
2
+ WorkReceipt,
3
+ WorkReceiptClaim,
4
+ WorkReceiptCommand,
5
+ WorkReceiptStatus,
6
+ WorkReceiptStore,
7
+ } from '../../../../shared_libs/play-runtime/work-receipts';
8
+
9
+ export type RuntimeReceiptStatus = WorkReceiptStatus;
10
+
11
+ export type WorkerRuntimeReceipt = WorkReceipt;
15
12
 
16
13
  export type WorkerRuntimeReceiptResponse = {
17
14
  receipt?: WorkerRuntimeReceipt | null;
@@ -45,18 +42,27 @@ export type WorkerRuntimeReceiptAction =
45
42
  error: string;
46
43
  };
47
44
 
45
+ export type WorkerRuntimeReceiptCommand = WorkReceiptCommand;
46
+
47
+ export type WorkerRuntimeReceiptClaim = WorkReceiptClaim;
48
+
49
+ export type WorkerRuntimeReceiptStore = WorkReceiptStore;
50
+
51
+ type PostRuntimeApi = (
52
+ baseUrl: string,
53
+ executorToken: string,
54
+ body: WorkerRuntimeReceiptAction,
55
+ ) => Promise<WorkerRuntimeReceiptResponse>;
56
+
48
57
  type RuntimeReceiptContext = {
49
- baseUrl: string;
50
- executorToken: string;
58
+ baseUrl?: string;
59
+ executorToken?: string;
51
60
  orgId?: string | null;
52
61
  playName: string;
53
62
  runId: string;
54
63
  key: string;
55
- postRuntimeApi: (
56
- baseUrl: string,
57
- executorToken: string,
58
- body: WorkerRuntimeReceiptAction,
59
- ) => Promise<WorkerRuntimeReceiptResponse>;
64
+ postRuntimeApi?: PostRuntimeApi;
65
+ receiptStore?: WorkerRuntimeReceiptStore;
60
66
  };
61
67
 
62
68
  function scopedReceiptKey(input: {
@@ -75,94 +81,185 @@ function errorMessage(error: unknown): string {
75
81
  return error instanceof Error ? error.message : String(error);
76
82
  }
77
83
 
78
- function runningReceiptError(key: string, receipt: WorkerRuntimeReceipt): Error {
84
+ function runningReceiptError(
85
+ key: string,
86
+ receipt: WorkerRuntimeReceipt,
87
+ ): Error {
79
88
  return new Error(
80
89
  `Runtime receipt ${key} is already running for run ${receipt.runId ?? 'unknown'}.`,
81
90
  );
82
91
  }
83
92
 
84
- export async function runWorkerRuntimeReceiptBoundary<T>(
85
- input: RuntimeReceiptContext & {
86
- execute: () => Promise<T> | T;
87
- },
88
- ): Promise<T> {
89
- const key = scopedReceiptKey(input);
93
+ function isReusableReceipt(receipt: WorkerRuntimeReceipt): boolean {
94
+ return receipt.status === 'completed' || receipt.status === 'skipped';
95
+ }
96
+
97
+ export function createRuntimeApiWorkerReceiptStore(input: {
98
+ baseUrl: string;
99
+ executorToken: string;
100
+ postRuntimeApi: PostRuntimeApi;
101
+ }): WorkerRuntimeReceiptStore {
90
102
  const postRuntimeReceiptAction = (body: WorkerRuntimeReceiptAction) =>
91
103
  input.postRuntimeApi(input.baseUrl, input.executorToken, body);
92
- const existing = await postRuntimeReceiptAction({
93
- action: 'get_runtime_step_receipt',
94
- playName: input.playName,
95
- runId: input.runId,
96
- key,
97
- });
98
- if (
99
- existing.receipt?.status === 'completed' ||
100
- existing.receipt?.status === 'skipped'
101
- ) {
102
- return receiptOutput<T>(existing.receipt);
103
- }
104
104
 
105
- const claimed = await postRuntimeReceiptAction({
106
- action: 'claim_runtime_step_receipt',
107
- playName: input.playName,
108
- runId: input.runId,
109
- key,
110
- });
111
- if (!claimed.receipt) {
112
- const latest = await postRuntimeReceiptAction({
113
- action: 'get_runtime_step_receipt',
114
- playName: input.playName,
115
- runId: input.runId,
116
- key,
117
- });
118
- if (
119
- latest.receipt?.status === 'completed' ||
120
- latest.receipt?.status === 'skipped'
121
- ) {
122
- return receiptOutput<T>(latest.receipt);
123
- }
124
- if (latest.receipt?.status === 'running') {
125
- throw runningReceiptError(key, latest.receipt);
126
- }
127
- if (latest.receipt?.status === 'failed') {
105
+ return {
106
+ async claimReceipt(command) {
107
+ const claimed = await postRuntimeReceiptAction({
108
+ action: 'claim_runtime_step_receipt',
109
+ playName: command.playName,
110
+ runId: command.runId,
111
+ key: command.key,
112
+ });
113
+ if (claimed.receipt) {
114
+ return { disposition: 'claimed', receipt: claimed.receipt };
115
+ }
116
+
117
+ const latest = await postRuntimeReceiptAction({
118
+ action: 'get_runtime_step_receipt',
119
+ playName: command.playName,
120
+ runId: command.runId,
121
+ key: command.key,
122
+ });
123
+ if (latest.receipt && isReusableReceipt(latest.receipt)) {
124
+ return { disposition: 'reused', receipt: latest.receipt };
125
+ }
126
+ if (latest.receipt?.status === 'running') {
127
+ return { disposition: 'running', receipt: latest.receipt };
128
+ }
129
+ if (latest.receipt?.status === 'failed') {
130
+ return { disposition: 'failed', receipt: latest.receipt };
131
+ }
128
132
  throw new Error(
129
- `Runtime receipt ${key} is failed and could not be claimed: ${latest.receipt.error ?? 'unknown error'}`,
133
+ `Runtime receipt ${command.key} claim did not return execution ownership.`,
130
134
  );
131
- }
135
+ },
136
+
137
+ async completeReceipt(command) {
138
+ const completed = await postRuntimeReceiptAction({
139
+ action: 'complete_runtime_step_receipt',
140
+ playName: command.playName,
141
+ runId: command.runId,
142
+ key: command.key,
143
+ output: command.output,
144
+ });
145
+ return completed.receipt ?? null;
146
+ },
147
+
148
+ async failReceipt(command) {
149
+ const failed = await postRuntimeReceiptAction({
150
+ action: 'fail_runtime_step_receipt',
151
+ playName: command.playName,
152
+ runId: command.runId,
153
+ key: command.key,
154
+ error: command.error,
155
+ });
156
+ return failed.receipt ?? null;
157
+ },
158
+ };
159
+ }
160
+
161
+ function resolveReceiptStore(
162
+ input: RuntimeReceiptContext,
163
+ ): WorkerRuntimeReceiptStore {
164
+ if (input.receiptStore) {
165
+ return input.receiptStore;
166
+ }
167
+ if (!input.baseUrl || !input.executorToken || !input.postRuntimeApi) {
132
168
  throw new Error(
133
- `Runtime receipt ${key} claim did not return execution ownership.`,
169
+ 'Runtime receipts require either a receiptStore or Runtime API transport.',
134
170
  );
135
171
  }
172
+ return createRuntimeApiWorkerReceiptStore({
173
+ baseUrl: input.baseUrl,
174
+ executorToken: input.executorToken,
175
+ postRuntimeApi: input.postRuntimeApi,
176
+ });
177
+ }
136
178
 
179
+ async function executeAndPersistReceipt<T>(input: {
180
+ key: string;
181
+ playName: string;
182
+ runId: string;
183
+ execute: () => Promise<T> | T;
184
+ receiptStore: WorkerRuntimeReceiptStore;
185
+ ownership: 'claimed' | 'workflow_replay';
186
+ }): Promise<T> {
137
187
  let output: T;
138
188
  try {
139
189
  output = await input.execute();
140
190
  } catch (error) {
141
- const failed = await postRuntimeReceiptAction({
142
- action: 'fail_runtime_step_receipt',
191
+ const failed = await input.receiptStore.failReceipt({
143
192
  playName: input.playName,
144
193
  runId: input.runId,
145
- key,
194
+ key: input.key,
146
195
  error: errorMessage(error),
147
196
  });
148
- if (!failed.receipt) {
197
+ if (!failed) {
149
198
  throw new Error(
150
- `Runtime receipt ${key} execution failed and failed receipt could not be persisted: ${errorMessage(error)}`,
199
+ `Runtime receipt ${input.key} ${input.ownership} execution failed and failed receipt could not be persisted: ${errorMessage(error)}`,
151
200
  );
152
201
  }
153
202
  throw error;
154
203
  }
155
- const completed = await postRuntimeReceiptAction({
156
- action: 'complete_runtime_step_receipt',
204
+
205
+ const completed = await input.receiptStore.completeReceipt({
157
206
  playName: input.playName,
158
207
  runId: input.runId,
159
- key,
208
+ key: input.key,
160
209
  output,
161
210
  });
162
- if (!completed.receipt) {
211
+ if (!completed) {
163
212
  throw new Error(
164
- `Runtime receipt ${key} execution completed but completed receipt could not be persisted.`,
213
+ `Runtime receipt ${input.key} ${input.ownership} execution completed but completed receipt could not be persisted.`,
165
214
  );
166
215
  }
167
216
  return output;
168
217
  }
218
+
219
+ export async function runWorkerRuntimeReceiptBoundary<T>(
220
+ input: RuntimeReceiptContext & {
221
+ execute: () => Promise<T> | T;
222
+ repairRunningReceiptForSameRun?: boolean;
223
+ },
224
+ ): Promise<T> {
225
+ const key = scopedReceiptKey(input);
226
+ const receiptStore = resolveReceiptStore(input);
227
+ const claimed = await receiptStore.claimReceipt({
228
+ playName: input.playName,
229
+ runId: input.runId,
230
+ key,
231
+ });
232
+ if (claimed.disposition === 'reused') {
233
+ return receiptOutput<T>(claimed.receipt);
234
+ }
235
+ if (claimed.disposition === 'running') {
236
+ if (
237
+ input.repairRunningReceiptForSameRun &&
238
+ claimed.receipt.runId === input.runId
239
+ ) {
240
+ return executeAndPersistReceipt({
241
+ key,
242
+ playName: input.playName,
243
+ runId: input.runId,
244
+ execute: input.execute,
245
+ receiptStore,
246
+ ownership: 'workflow_replay',
247
+ });
248
+ }
249
+ throw runningReceiptError(key, claimed.receipt);
250
+ }
251
+ if (claimed.disposition === 'failed') {
252
+ throw new Error(
253
+ `Runtime receipt ${key} is failed and could not be claimed: ${claimed.receipt.error ?? 'unknown error'}`,
254
+ );
255
+ }
256
+
257
+ return executeAndPersistReceipt({
258
+ key,
259
+ playName: input.playName,
260
+ runId: input.runId,
261
+ execute: input.execute,
262
+ receiptStore,
263
+ ownership: 'claimed',
264
+ });
265
+ }
@@ -32,13 +32,20 @@
32
32
  import type {
33
33
  PlayHarnessRpc,
34
34
  PreloadedRuntimeDbSessionInput,
35
+ CompleteRuntimeReceiptInput,
36
+ FailRuntimeReceiptInput,
35
37
  RuntimeApiCallInput,
36
38
  RuntimeApiCallResult,
39
+ RuntimeReceiptInput,
37
40
  SheetDatasetRowsInput,
38
41
  SheetDatasetRowsResult,
39
42
  StagedFileChunkInput,
40
43
  StagedFileChunkResult,
41
44
  } from '../../../apps/play-harness-worker/src/rpc-types';
45
+ import type {
46
+ WorkReceipt,
47
+ WorkReceiptClaim,
48
+ } from '../../../shared_libs/play-runtime/work-receipts';
42
49
 
43
50
  /**
44
51
  * Service-binding RPC stub shape — what `env.HARNESS` looks like inside
@@ -119,6 +126,24 @@ export async function harnessRuntimeApiCall(
119
126
  return requireBinding().runtimeApiCall(input);
120
127
  }
121
128
 
129
+ export async function harnessClaimRuntimeReceipt(
130
+ input: RuntimeReceiptInput,
131
+ ): Promise<WorkReceiptClaim> {
132
+ return requireBinding().claimRuntimeReceipt(input);
133
+ }
134
+
135
+ export async function harnessCompleteRuntimeReceipt(
136
+ input: CompleteRuntimeReceiptInput,
137
+ ): Promise<WorkReceipt | null> {
138
+ return requireBinding().completeRuntimeReceipt(input);
139
+ }
140
+
141
+ export async function harnessFailRuntimeReceipt(
142
+ input: FailRuntimeReceiptInput,
143
+ ): Promise<WorkReceipt | null> {
144
+ return requireBinding().failRuntimeReceipt(input);
145
+ }
146
+
122
147
  /**
123
148
  * Read a bounded staged-file byte range through typed harness RPC. This is the
124
149
  * only staged-file data path for per-play Workers; there is intentionally no
@@ -50,10 +50,10 @@ export type SdkRelease = {
50
50
  };
51
51
 
52
52
  export const SDK_RELEASE = {
53
- version: '0.1.60',
53
+ version: '0.1.62',
54
54
  apiContract: '2026-05-play-bootstrap-dataset-summary',
55
55
  supportPolicy: {
56
- latest: '0.1.60',
56
+ latest: '0.1.62',
57
57
  minimumSupported: '0.1.53',
58
58
  deprecatedBelow: '0.1.53',
59
59
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {