deepline 0.1.65 → 0.1.66
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 +475 -69
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +134 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +37 -49
- package/dist/repo/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +13 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +10 -136
- package/dist/repo/sdk/src/plays/harness-stub.ts +4 -0
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +312 -0
- package/dist/repo/shared_libs/play-runtime/db-session-plan.ts +69 -0
- package/dist/repo/shared_libs/play-runtime/db-session.ts +439 -0
- package/dist/repo/shared_libs/play-runtime/work-receipts.ts +92 -0
- package/package.json +1 -1
|
@@ -17,6 +17,41 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { sanitizeLiveLogLines } from './runtime/live-progress';
|
|
20
|
+
import type { PreloadedRuntimeDbSession } from '../../../shared_libs/play-runtime/db-session';
|
|
21
|
+
|
|
22
|
+
type DurableObjectStorage = {
|
|
23
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
24
|
+
get<T>(keys: string[]): Promise<Map<string, T>>;
|
|
25
|
+
put<T>(key: string, value: T): Promise<void>;
|
|
26
|
+
put<T>(entries: Record<string, T>): Promise<void>;
|
|
27
|
+
delete(key: string | string[]): Promise<unknown>;
|
|
28
|
+
deleteAll(): Promise<void>;
|
|
29
|
+
list<T>(options?: { prefix?: string }): Promise<Map<string, T>>;
|
|
30
|
+
setAlarm(scheduledTime: number): Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type DurableObjectState = {
|
|
34
|
+
storage: DurableObjectStorage;
|
|
35
|
+
blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type DurableObject = {
|
|
39
|
+
fetch(request: Request): Promise<Response>;
|
|
40
|
+
alarm?(): Promise<void>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type DurableObjectId = {
|
|
44
|
+
toString(): string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type DurableObjectStub = {
|
|
48
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type DurableObjectNamespace = {
|
|
52
|
+
idFromName(name: string): DurableObjectId;
|
|
53
|
+
get(id: DurableObjectId): DurableObjectStub;
|
|
54
|
+
};
|
|
20
55
|
|
|
21
56
|
type DedupEntry =
|
|
22
57
|
| { status: 'in_flight'; claimedBy: string; claimedAt: number }
|
|
@@ -83,6 +118,13 @@ type WorkflowRunRetryState = {
|
|
|
83
118
|
expiresAt: number;
|
|
84
119
|
};
|
|
85
120
|
|
|
121
|
+
type WorkflowDbSessionsState = {
|
|
122
|
+
runId: string;
|
|
123
|
+
sessions: PreloadedRuntimeDbSession[];
|
|
124
|
+
storedAt: number;
|
|
125
|
+
expiresAt: number;
|
|
126
|
+
};
|
|
127
|
+
|
|
86
128
|
type CoordinatorTraceEntry = {
|
|
87
129
|
ts: number;
|
|
88
130
|
source: 'coordinator' | 'dynamic_worker';
|
|
@@ -166,6 +208,7 @@ const DEDUP_KEY_PREFIX = 'd:';
|
|
|
166
208
|
const WORKFLOW_POOL_KEY_PREFIX = 'p:';
|
|
167
209
|
const WORKFLOW_POOL_RUN_KEY_PREFIX = 'm:';
|
|
168
210
|
const WORKFLOW_RUN_RETRY_KEY_PREFIX = 'r:';
|
|
211
|
+
const WORKFLOW_DB_SESSIONS_KEY = 'db-sessions';
|
|
169
212
|
const COORDINATOR_TRACE_KEY_PREFIX = 't:';
|
|
170
213
|
const COORDINATOR_RUN_EVENT_KEY_PREFIX = 'e:';
|
|
171
214
|
const COORDINATOR_TERMINAL_KEY = 'terminal';
|
|
@@ -177,11 +220,27 @@ const WORKFLOW_POOL_DEFAULT_TTL_MS = 8 * 60 * 1000;
|
|
|
177
220
|
const WORKFLOW_POOL_RUN_MAPPING_TTL_MS = 60 * 60 * 1000;
|
|
178
221
|
const WORKFLOW_POOL_READY_MAX_AGE_MS = 7 * 60_000;
|
|
179
222
|
const WORKFLOW_RUN_RETRY_STATE_MAX_BYTES = 110_000;
|
|
223
|
+
const WORKFLOW_DB_SESSIONS_TTL_MS = 10 * 60_000;
|
|
180
224
|
|
|
181
225
|
function jsonByteLength(value: unknown): number {
|
|
182
226
|
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
183
227
|
}
|
|
184
228
|
|
|
229
|
+
function assertEncryptedDbSessionsForStorage(
|
|
230
|
+
sessions: PreloadedRuntimeDbSession[],
|
|
231
|
+
): void {
|
|
232
|
+
for (const session of sessions) {
|
|
233
|
+
if (session.session.postgresUrl) {
|
|
234
|
+
throw new Error('preloaded db session storage rejects raw Postgres URLs');
|
|
235
|
+
}
|
|
236
|
+
if (!session.session.encryptedPostgresUrl) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
'preloaded db session storage requires encrypted Postgres URLs',
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
185
244
|
interface DedupEnv {
|
|
186
245
|
PLAY_DEDUP: DurableObjectNamespace;
|
|
187
246
|
}
|
|
@@ -252,6 +311,10 @@ export class PlayDedup implements DurableObject {
|
|
|
252
311
|
return await this.handleRunRetryStatePut(req);
|
|
253
312
|
case '/run-retry-claim':
|
|
254
313
|
return await this.handleRunRetryClaim(req);
|
|
314
|
+
case '/db-sessions-put':
|
|
315
|
+
return await this.handleDbSessionsPut(req);
|
|
316
|
+
case '/db-sessions-get':
|
|
317
|
+
return await this.handleDbSessionsGet(req);
|
|
255
318
|
case '/pool-clear':
|
|
256
319
|
return await this.handlePoolClear(req);
|
|
257
320
|
case '/trace-add':
|
|
@@ -1086,6 +1149,77 @@ export class PlayDedup implements DurableObject {
|
|
|
1086
1149
|
});
|
|
1087
1150
|
}
|
|
1088
1151
|
|
|
1152
|
+
private async handleDbSessionsPut(req: Request): Promise<Response> {
|
|
1153
|
+
const body = (await req.json().catch(() => null)) as {
|
|
1154
|
+
runId?: unknown;
|
|
1155
|
+
sessions?: unknown;
|
|
1156
|
+
ttlMs?: unknown;
|
|
1157
|
+
} | null;
|
|
1158
|
+
const runId = typeof body?.runId === 'string' ? body.runId : '';
|
|
1159
|
+
const sessions = Array.isArray(body?.sessions)
|
|
1160
|
+
? (body.sessions as PreloadedRuntimeDbSession[])
|
|
1161
|
+
: null;
|
|
1162
|
+
if (!runId || !sessions) {
|
|
1163
|
+
return new Response('runId and sessions are required', { status: 400 });
|
|
1164
|
+
}
|
|
1165
|
+
assertEncryptedDbSessionsForStorage(sessions);
|
|
1166
|
+
const now = Date.now();
|
|
1167
|
+
const ttlMs =
|
|
1168
|
+
typeof body?.ttlMs === 'number' &&
|
|
1169
|
+
Number.isFinite(body.ttlMs) &&
|
|
1170
|
+
body.ttlMs > 0
|
|
1171
|
+
? Math.min(Math.max(Math.floor(body.ttlMs), 60_000), 30 * 60_000)
|
|
1172
|
+
: WORKFLOW_DB_SESSIONS_TTL_MS;
|
|
1173
|
+
const state: WorkflowDbSessionsState = {
|
|
1174
|
+
runId,
|
|
1175
|
+
sessions,
|
|
1176
|
+
storedAt: now,
|
|
1177
|
+
expiresAt: now + ttlMs,
|
|
1178
|
+
};
|
|
1179
|
+
await this.state.storage.put(WORKFLOW_DB_SESSIONS_KEY, state);
|
|
1180
|
+
return new Response(
|
|
1181
|
+
JSON.stringify({
|
|
1182
|
+
ok: true,
|
|
1183
|
+
sessionCount: sessions.length,
|
|
1184
|
+
expiresAt: state.expiresAt,
|
|
1185
|
+
}),
|
|
1186
|
+
{ headers: { 'content-type': 'application/json' } },
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
private async handleDbSessionsGet(req: Request): Promise<Response> {
|
|
1191
|
+
const url = new URL(req.url);
|
|
1192
|
+
const runId = url.searchParams.get('runId') ?? '';
|
|
1193
|
+
if (!runId) {
|
|
1194
|
+
return new Response('runId is required', { status: 400 });
|
|
1195
|
+
}
|
|
1196
|
+
const state = await this.state.storage.get<WorkflowDbSessionsState>(
|
|
1197
|
+
WORKFLOW_DB_SESSIONS_KEY,
|
|
1198
|
+
);
|
|
1199
|
+
if (!state || state.runId !== runId) {
|
|
1200
|
+
return new Response(JSON.stringify({ error: 'db sessions not found' }), {
|
|
1201
|
+
status: 404,
|
|
1202
|
+
headers: { 'content-type': 'application/json' },
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
if (state.expiresAt <= Date.now()) {
|
|
1206
|
+
await this.state.storage.delete(WORKFLOW_DB_SESSIONS_KEY);
|
|
1207
|
+
return new Response(JSON.stringify({ error: 'db sessions expired' }), {
|
|
1208
|
+
status: 410,
|
|
1209
|
+
headers: { 'content-type': 'application/json' },
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
assertEncryptedDbSessionsForStorage(state.sessions);
|
|
1213
|
+
return new Response(
|
|
1214
|
+
JSON.stringify({
|
|
1215
|
+
sessions: state.sessions,
|
|
1216
|
+
sessionCount: state.sessions.length,
|
|
1217
|
+
expiresAt: state.expiresAt,
|
|
1218
|
+
}),
|
|
1219
|
+
{ headers: { 'content-type': 'application/json' } },
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1089
1223
|
private async handlePoolClear(req: Request): Promise<Response> {
|
|
1090
1224
|
const version = this.workflowPoolVersion(req);
|
|
1091
1225
|
const [pool, mappings, retries] = await Promise.all([
|
|
@@ -86,6 +86,7 @@ import type {
|
|
|
86
86
|
PlayRunLedgerStepProgress,
|
|
87
87
|
PlayRunLedgerStepStatus,
|
|
88
88
|
} from '../../../shared_libs/play-runtime/run-ledger';
|
|
89
|
+
import type { PlayHarnessRpc } from '../../play-harness-worker/src/rpc-types';
|
|
89
90
|
import {
|
|
90
91
|
createCsvDatasetHandle,
|
|
91
92
|
createInlineDatasetHandle,
|
|
@@ -272,18 +273,11 @@ type WorkerEnv = {
|
|
|
272
273
|
): Promise<void>;
|
|
273
274
|
};
|
|
274
275
|
/**
|
|
275
|
-
*
|
|
276
|
+
* Required service binding to the long-lived Play Harness Worker
|
|
276
277
|
* (apps/play-harness-worker). Wired by the coordinator's WorkerLoader
|
|
277
|
-
* factory — see apps/play-runner-workers/src/coordinator-entry.ts.
|
|
278
|
-
* binding's typed RPC surface is `PlayHarnessRpc`, defined in
|
|
279
|
-
* apps/play-harness-worker/src/rpc-types.ts.
|
|
280
|
-
*
|
|
281
|
-
* Optional: if absent (older coordinator deploy that hasn't been
|
|
282
|
-
* upgraded, or a dev environment running without the harness Worker),
|
|
283
|
-
* SDK call sites that try to reach into the harness will throw a
|
|
284
|
-
* loud error. Loud failures > silent fallbacks.
|
|
278
|
+
* factory — see apps/play-runner-workers/src/coordinator-entry.ts.
|
|
285
279
|
*/
|
|
286
|
-
HARNESS
|
|
280
|
+
HARNESS: PlayHarnessRpc;
|
|
287
281
|
VERCEL_PROTECTION_BYPASS_TOKEN?: string;
|
|
288
282
|
};
|
|
289
283
|
|
|
@@ -314,7 +308,18 @@ function captureCoordinatorBinding(env: WorkerEnv): void {
|
|
|
314
308
|
* across plays.
|
|
315
309
|
*/
|
|
316
310
|
function captureHarnessBinding(env: WorkerEnv): void {
|
|
317
|
-
setHarnessBinding(env
|
|
311
|
+
setHarnessBinding(requireHarnessBinding(env));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function requireHarnessBinding(env: WorkerEnv): PlayHarnessRpc {
|
|
315
|
+
const harness = env.HARNESS;
|
|
316
|
+
if (!harness) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
'Cloudflare play Workers require the HARNESS service binding. ' +
|
|
319
|
+
'Start apps/play-harness-worker before the coordinator or fix wrangler.toml services.',
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return harness;
|
|
318
323
|
}
|
|
319
324
|
|
|
320
325
|
/**
|
|
@@ -325,17 +330,12 @@ function captureHarnessBinding(env: WorkerEnv): void {
|
|
|
325
330
|
* to plumb a separate health route or instrument SDK call sites.
|
|
326
331
|
*
|
|
327
332
|
* Behavior on fail:
|
|
328
|
-
* - Binding
|
|
329
|
-
*
|
|
330
|
-
* loop yet.
|
|
331
|
-
* - Binding present but ping throws → log the error. Don't re-throw —
|
|
332
|
-
* a probe failure must NEVER block the play; if a real call site
|
|
333
|
-
* later needs HARNESS and the binding is broken, that call site's
|
|
334
|
-
* own throw is the loud failure (see harness-stub → requireBinding).
|
|
333
|
+
* - Binding or ping failure → fail the run. HARNESS is required in every
|
|
334
|
+
* deployed environment; no fallback should hide a miswired harness.
|
|
335
335
|
*
|
|
336
336
|
* This is intentionally idempotent and cheap (a single ping RPC,
|
|
337
337
|
* deduplicated per isolate via the module-level guard) so the probe
|
|
338
|
-
* itself never adds
|
|
338
|
+
* itself never adds meaningful latency to a run.
|
|
339
339
|
*/
|
|
340
340
|
let harnessProbeFiredForIsolate = false;
|
|
341
341
|
async function probeHarnessOnce(
|
|
@@ -343,33 +343,16 @@ async function probeHarnessOnce(
|
|
|
343
343
|
runPrefix: string,
|
|
344
344
|
): Promise<void> {
|
|
345
345
|
if (harnessProbeFiredForIsolate) return;
|
|
346
|
-
|
|
347
|
-
if (!env.HARNESS) {
|
|
348
|
-
console.log(
|
|
349
|
-
`${runPrefix} [harness-probe] env.HARNESS unwired — coordinator did not pass the binding. ` +
|
|
350
|
-
`Per-play SDK call sites that reach into the harness will throw clearly. ` +
|
|
351
|
-
`See apps/play-harness-worker/README.md.`,
|
|
352
|
-
);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (
|
|
356
|
-
typeof (env.HARNESS as { ping?: () => Promise<{ ok: true; ts: number }> })
|
|
357
|
-
.ping !== 'function'
|
|
358
|
-
) {
|
|
359
|
-
console.log(
|
|
360
|
-
`${runPrefix} [harness-probe] env.HARNESS is present but does not expose ping(); ` +
|
|
361
|
-
`continuing and relying on the first real call to fail if the contract changed.`,
|
|
362
|
-
);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
346
|
+
const harness = requireHarnessBinding(env);
|
|
365
347
|
try {
|
|
366
|
-
const result = await
|
|
348
|
+
const result = await harness.ping();
|
|
349
|
+
harnessProbeFiredForIsolate = true;
|
|
367
350
|
console.log(
|
|
368
351
|
`${runPrefix} [harness-probe] env.HARNESS connected ts=${result.ts}`,
|
|
369
352
|
);
|
|
370
353
|
} catch (error) {
|
|
371
354
|
const message = error instanceof Error ? error.message : String(error);
|
|
372
|
-
|
|
355
|
+
throw new Error(
|
|
373
356
|
`${runPrefix} [harness-probe] env.HARNESS resolved but ping failed: ${message}`,
|
|
374
357
|
);
|
|
375
358
|
}
|
|
@@ -3028,6 +3011,7 @@ async function persistCompletedMapRows(input: {
|
|
|
3028
3011
|
await harnessPersistCompletedSheetRows({
|
|
3029
3012
|
baseUrl: input.req.baseUrl,
|
|
3030
3013
|
executorToken: input.req.executorToken,
|
|
3014
|
+
orgId: input.req.orgId,
|
|
3031
3015
|
preloadedDbSessions: input.req.preloadedDbSessions ?? null,
|
|
3032
3016
|
playName: input.req.playName,
|
|
3033
3017
|
tableNamespace: input.tableNamespace,
|
|
@@ -3060,6 +3044,7 @@ async function prepareMapRows(input: {
|
|
|
3060
3044
|
const result = await harnessStartSheetDataset({
|
|
3061
3045
|
baseUrl: input.req.baseUrl,
|
|
3062
3046
|
executorToken: input.req.executorToken,
|
|
3047
|
+
orgId: input.req.orgId,
|
|
3063
3048
|
preloadedDbSessions: input.req.preloadedDbSessions ?? null,
|
|
3064
3049
|
playName: input.req.playName,
|
|
3065
3050
|
tableNamespace: input.tableNamespace,
|
|
@@ -3233,22 +3218,22 @@ function createMinimalWorkerCtx(
|
|
|
3233
3218
|
};
|
|
3234
3219
|
const rootGovernance = req.playCallGovernance;
|
|
3235
3220
|
const rootRunId = rootGovernance?.rootRunId ?? req.runId;
|
|
3236
|
-
const receiptStore =
|
|
3237
|
-
|
|
3238
|
-
:
|
|
3221
|
+
const receiptStore = createHarnessWorkerReceiptStore({
|
|
3222
|
+
executorToken: req.executorToken,
|
|
3223
|
+
orgId: req.orgId,
|
|
3224
|
+
preloadedDbSessions: req.preloadedDbSessions ?? null,
|
|
3225
|
+
userEmail: req.userEmail,
|
|
3226
|
+
});
|
|
3239
3227
|
const executeWithRuntimeReceipt = async <T>(
|
|
3240
3228
|
key: string,
|
|
3241
3229
|
execute: () => Promise<T> | T,
|
|
3242
3230
|
repairRunningReceiptForSameRun = false,
|
|
3243
3231
|
): Promise<T> => {
|
|
3244
3232
|
const serialized = await runWorkerRuntimeReceiptBoundary<unknown>({
|
|
3245
|
-
baseUrl: req.baseUrl,
|
|
3246
|
-
executorToken: req.executorToken,
|
|
3247
3233
|
orgId: req.orgId,
|
|
3248
3234
|
playName: req.playName,
|
|
3249
3235
|
runId: req.runId,
|
|
3250
3236
|
key,
|
|
3251
|
-
postRuntimeApi,
|
|
3252
3237
|
receiptStore,
|
|
3253
3238
|
execute: async () => serializeDurableStepValue(await execute()),
|
|
3254
3239
|
repairRunningReceiptForSameRun,
|
|
@@ -3897,6 +3882,7 @@ function createMinimalWorkerCtx(
|
|
|
3897
3882
|
const result = await harnessReadSheetDatasetRows({
|
|
3898
3883
|
baseUrl: req.baseUrl,
|
|
3899
3884
|
executorToken: req.executorToken,
|
|
3885
|
+
orgId: req.orgId,
|
|
3900
3886
|
playName: req.playName,
|
|
3901
3887
|
tableNamespace: name,
|
|
3902
3888
|
runId: req.runId,
|
|
@@ -5273,6 +5259,7 @@ async function persistResultDatasets(
|
|
|
5273
5259
|
await harnessStartSheetDataset({
|
|
5274
5260
|
baseUrl: req.baseUrl,
|
|
5275
5261
|
executorToken: req.executorToken,
|
|
5262
|
+
orgId: req.orgId,
|
|
5276
5263
|
playName: req.playName,
|
|
5277
5264
|
tableNamespace: dataset.tableNamespace,
|
|
5278
5265
|
sheetContract: requireSheetContract(req, dataset.tableNamespace),
|
|
@@ -5295,6 +5282,7 @@ async function persistResultDatasets(
|
|
|
5295
5282
|
await harnessStartSheetDataset({
|
|
5296
5283
|
baseUrl: req.baseUrl,
|
|
5297
5284
|
executorToken: req.executorToken,
|
|
5285
|
+
orgId: req.orgId,
|
|
5298
5286
|
preloadedDbSessions: req.preloadedDbSessions ?? null,
|
|
5299
5287
|
playName: req.playName,
|
|
5300
5288
|
tableNamespace: dataset.tableNamespace,
|
|
@@ -5502,14 +5490,14 @@ export class TenantWorkflow extends WorkflowEntrypoint<
|
|
|
5502
5490
|
);
|
|
5503
5491
|
captureCoordinatorBinding(this.env);
|
|
5504
5492
|
captureRuntimeApiBinding(this.env);
|
|
5505
|
-
// Hand the harness service binding
|
|
5493
|
+
// Hand the required harness service binding to the SDK-side stub.
|
|
5506
5494
|
// Must run BEFORE any SDK call site that would reach into HARNESS,
|
|
5507
5495
|
// i.e. before user play code is invoked. Idempotent within a run.
|
|
5508
5496
|
captureHarnessBinding(this.env);
|
|
5509
5497
|
// Fire the one-time wiring probe (deduplicated across runs in the
|
|
5510
5498
|
// same isolate). Awaited so the result is in the log before user code
|
|
5511
|
-
// begins
|
|
5512
|
-
//
|
|
5499
|
+
// begins. A missing or unhealthy HARNESS fails the run before user code
|
|
5500
|
+
// can accidentally take a slower fallback path.
|
|
5513
5501
|
await probeHarnessOnce(this.env, runPrefix);
|
|
5514
5502
|
const abortController = new AbortController();
|
|
5515
5503
|
try {
|
|
@@ -3,27 +3,40 @@ import {
|
|
|
3
3
|
harnessCompleteRuntimeReceipt,
|
|
4
4
|
harnessFailRuntimeReceipt,
|
|
5
5
|
} from '../../../../sdk/src/plays/harness-stub';
|
|
6
|
+
import type { PreloadedRuntimeDbSessionInput } from '../../../play-harness-worker/src/rpc-types';
|
|
6
7
|
import type { WorkerRuntimeReceiptStore } from './receipts';
|
|
7
8
|
|
|
8
9
|
export function createHarnessWorkerReceiptStore(input: {
|
|
9
10
|
executorToken: string;
|
|
11
|
+
orgId: string;
|
|
12
|
+
preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
|
|
13
|
+
userEmail?: string | null;
|
|
10
14
|
}): WorkerRuntimeReceiptStore {
|
|
11
15
|
return {
|
|
12
16
|
claimReceipt(command) {
|
|
13
17
|
return harnessClaimRuntimeReceipt({
|
|
14
18
|
executorToken: input.executorToken,
|
|
19
|
+
orgId: input.orgId,
|
|
20
|
+
preloadedDbSessions: input.preloadedDbSessions ?? null,
|
|
21
|
+
userEmail: input.userEmail ?? null,
|
|
15
22
|
...command,
|
|
16
23
|
});
|
|
17
24
|
},
|
|
18
25
|
completeReceipt(command) {
|
|
19
26
|
return harnessCompleteRuntimeReceipt({
|
|
20
27
|
executorToken: input.executorToken,
|
|
28
|
+
orgId: input.orgId,
|
|
29
|
+
preloadedDbSessions: input.preloadedDbSessions ?? null,
|
|
30
|
+
userEmail: input.userEmail ?? null,
|
|
21
31
|
...command,
|
|
22
32
|
});
|
|
23
33
|
},
|
|
24
34
|
failReceipt(command) {
|
|
25
35
|
return harnessFailRuntimeReceipt({
|
|
26
36
|
executorToken: input.executorToken,
|
|
37
|
+
orgId: input.orgId,
|
|
38
|
+
preloadedDbSessions: input.preloadedDbSessions ?? null,
|
|
39
|
+
userEmail: input.userEmail ?? null,
|
|
27
40
|
...command,
|
|
28
41
|
});
|
|
29
42
|
},
|
|
@@ -1,68 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import {
|
|
2
|
+
buildScopedWorkReceiptKey,
|
|
3
|
+
type WorkReceipt,
|
|
4
|
+
type WorkReceiptClaim,
|
|
5
|
+
type WorkReceiptCommand,
|
|
6
|
+
type WorkReceiptStatus,
|
|
7
|
+
type WorkReceiptStore,
|
|
7
8
|
} from '../../../../shared_libs/play-runtime/work-receipts';
|
|
8
9
|
|
|
9
10
|
export type RuntimeReceiptStatus = WorkReceiptStatus;
|
|
10
11
|
|
|
11
12
|
export type WorkerRuntimeReceipt = WorkReceipt;
|
|
12
13
|
|
|
13
|
-
export type WorkerRuntimeReceiptResponse = {
|
|
14
|
-
receipt?: WorkerRuntimeReceipt | null;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type WorkerRuntimeReceiptAction =
|
|
18
|
-
| {
|
|
19
|
-
action: 'get_runtime_step_receipt';
|
|
20
|
-
playName: string;
|
|
21
|
-
runId: string;
|
|
22
|
-
key: string;
|
|
23
|
-
}
|
|
24
|
-
| {
|
|
25
|
-
action: 'claim_runtime_step_receipt';
|
|
26
|
-
playName: string;
|
|
27
|
-
runId: string;
|
|
28
|
-
key: string;
|
|
29
|
-
}
|
|
30
|
-
| {
|
|
31
|
-
action: 'complete_runtime_step_receipt';
|
|
32
|
-
playName: string;
|
|
33
|
-
runId: string;
|
|
34
|
-
key: string;
|
|
35
|
-
output: unknown;
|
|
36
|
-
}
|
|
37
|
-
| {
|
|
38
|
-
action: 'fail_runtime_step_receipt';
|
|
39
|
-
playName: string;
|
|
40
|
-
runId: string;
|
|
41
|
-
key: string;
|
|
42
|
-
error: string;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
14
|
export type WorkerRuntimeReceiptCommand = WorkReceiptCommand;
|
|
46
15
|
|
|
47
16
|
export type WorkerRuntimeReceiptClaim = WorkReceiptClaim;
|
|
48
17
|
|
|
49
18
|
export type WorkerRuntimeReceiptStore = WorkReceiptStore;
|
|
50
19
|
|
|
51
|
-
type PostRuntimeApi = (
|
|
52
|
-
baseUrl: string,
|
|
53
|
-
executorToken: string,
|
|
54
|
-
body: WorkerRuntimeReceiptAction,
|
|
55
|
-
) => Promise<WorkerRuntimeReceiptResponse>;
|
|
56
|
-
|
|
57
20
|
type RuntimeReceiptContext = {
|
|
58
|
-
baseUrl?: string;
|
|
59
|
-
executorToken?: string;
|
|
60
21
|
orgId?: string | null;
|
|
61
22
|
playName: string;
|
|
62
23
|
runId: string;
|
|
63
24
|
key: string;
|
|
64
|
-
|
|
65
|
-
receiptStore?: WorkerRuntimeReceiptStore;
|
|
25
|
+
receiptStore: WorkerRuntimeReceiptStore;
|
|
66
26
|
};
|
|
67
27
|
|
|
68
28
|
function scopedReceiptKey(input: {
|
|
@@ -70,7 +30,7 @@ function scopedReceiptKey(input: {
|
|
|
70
30
|
playName: string;
|
|
71
31
|
key: string;
|
|
72
32
|
}): string {
|
|
73
|
-
return
|
|
33
|
+
return buildScopedWorkReceiptKey(input);
|
|
74
34
|
}
|
|
75
35
|
|
|
76
36
|
function receiptOutput<T>(receipt: WorkerRuntimeReceipt): T {
|
|
@@ -90,92 +50,6 @@ function runningReceiptError(
|
|
|
90
50
|
);
|
|
91
51
|
}
|
|
92
52
|
|
|
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 {
|
|
102
|
-
const postRuntimeReceiptAction = (body: WorkerRuntimeReceiptAction) =>
|
|
103
|
-
input.postRuntimeApi(input.baseUrl, input.executorToken, body);
|
|
104
|
-
|
|
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
|
-
}
|
|
132
|
-
throw new Error(
|
|
133
|
-
`Runtime receipt ${command.key} claim did not return execution ownership.`,
|
|
134
|
-
);
|
|
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) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
'Runtime receipts require either a receiptStore or Runtime API transport.',
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
return createRuntimeApiWorkerReceiptStore({
|
|
173
|
-
baseUrl: input.baseUrl,
|
|
174
|
-
executorToken: input.executorToken,
|
|
175
|
-
postRuntimeApi: input.postRuntimeApi,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
53
|
async function executeAndPersistReceipt<T>(input: {
|
|
180
54
|
key: string;
|
|
181
55
|
playName: string;
|
|
@@ -223,7 +97,7 @@ export async function runWorkerRuntimeReceiptBoundary<T>(
|
|
|
223
97
|
},
|
|
224
98
|
): Promise<T> {
|
|
225
99
|
const key = scopedReceiptKey(input);
|
|
226
|
-
const receiptStore =
|
|
100
|
+
const receiptStore = input.receiptStore;
|
|
227
101
|
const claimed = await receiptStore.claimReceipt({
|
|
228
102
|
playName: input.playName,
|
|
229
103
|
runId: input.runId,
|
|
@@ -168,6 +168,8 @@ export async function harnessReadSheetDatasetRows(
|
|
|
168
168
|
*/
|
|
169
169
|
export async function harnessPrewarmPostgresSessions(input: {
|
|
170
170
|
executorToken: string;
|
|
171
|
+
orgId: string;
|
|
172
|
+
playName: string;
|
|
171
173
|
sessions: PreloadedRuntimeDbSessionInput[];
|
|
172
174
|
}): Promise<{ ok: true; sessions: number }> {
|
|
173
175
|
return requireBinding().prewarmPostgresSessions(input);
|
|
@@ -180,6 +182,7 @@ export async function harnessPrewarmPostgresSessions(input: {
|
|
|
180
182
|
export async function harnessStartSheetDataset(input: {
|
|
181
183
|
baseUrl: string;
|
|
182
184
|
executorToken: string;
|
|
185
|
+
orgId: string;
|
|
183
186
|
preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
|
|
184
187
|
playName: string;
|
|
185
188
|
tableNamespace: string;
|
|
@@ -206,6 +209,7 @@ export async function harnessStartSheetDataset(input: {
|
|
|
206
209
|
export async function harnessPersistCompletedSheetRows(input: {
|
|
207
210
|
baseUrl: string;
|
|
208
211
|
executorToken: string;
|
|
212
|
+
orgId: string;
|
|
209
213
|
preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
|
|
210
214
|
playName: string;
|
|
211
215
|
tableNamespace: string;
|
|
@@ -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.66',
|
|
54
54
|
apiContract: '2026-05-play-bootstrap-dataset-summary',
|
|
55
55
|
supportPolicy: {
|
|
56
|
-
latest: '0.1.
|
|
56
|
+
latest: '0.1.66',
|
|
57
57
|
minimumSupported: '0.1.53',
|
|
58
58
|
deprecatedBelow: '0.1.53',
|
|
59
59
|
},
|