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 +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +8 -5
- package/dist/repo/apps/play-runner-workers/src/entry.ts +62 -44
- package/dist/repo/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +31 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +173 -76
- package/dist/repo/sdk/src/plays/harness-stub.ts +25 -0
- package/dist/repo/sdk/src/release.ts +2 -2
- package/package.json +1 -1
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.
|
|
223
|
+
version: "0.1.62",
|
|
224
224
|
apiContract: "2026-05-play-bootstrap-dataset-summary",
|
|
225
225
|
supportPolicy: {
|
|
226
|
-
latest: "0.1.
|
|
226
|
+
latest: "0.1.62",
|
|
227
227
|
minimumSupported: "0.1.53",
|
|
228
228
|
deprecatedBelow: "0.1.53"
|
|
229
229
|
}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -197,10 +197,10 @@ function resolveConfig(options) {
|
|
|
197
197
|
|
|
198
198
|
// src/release.ts
|
|
199
199
|
var SDK_RELEASE = {
|
|
200
|
-
version: "0.1.
|
|
200
|
+
version: "0.1.62",
|
|
201
201
|
apiContract: "2026-05-play-bootstrap-dataset-summary",
|
|
202
202
|
supportPolicy: {
|
|
203
|
-
latest: "0.1.
|
|
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.
|
|
235
|
+
version: "0.1.62",
|
|
236
236
|
apiContract: "2026-05-play-bootstrap-dataset-summary",
|
|
237
237
|
supportPolicy: {
|
|
238
|
-
latest: "0.1.
|
|
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.
|
|
173
|
+
version: "0.1.62",
|
|
174
174
|
apiContract: "2026-05-play-bootstrap-dataset-summary",
|
|
175
175
|
supportPolicy: {
|
|
176
|
-
latest: "0.1.
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
3956
|
-
|
|
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
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
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 =
|
|
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
|
|
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
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
50
|
-
executorToken
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
156
|
-
|
|
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
|
|
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.
|
|
53
|
+
version: '0.1.62',
|
|
54
54
|
apiContract: '2026-05-play-bootstrap-dataset-summary',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.62',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|