deepline 0.1.211 → 0.1.213
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +317 -340
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +23 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +1 -1
- package/dist/bundling-sources/sdk/src/client.ts +118 -3
- package/dist/bundling-sources/sdk/src/index.ts +2 -0
- package/dist/bundling-sources/sdk/src/play.ts +8 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +11 -1
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +302 -30
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-placement.ts +14 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +57 -23
- package/dist/bundling-sources/shared_libs/play-runtime/completed-receipt-cache.ts +166 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +164 -121
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -4
- package/dist/bundling-sources/shared_libs/play-runtime/db-session.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +163 -7
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-auth-session.ts +93 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +14 -2
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +4 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-call-execution.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +16 -1
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +21 -1
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +80 -3
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +128 -6
- package/dist/bundling-sources/shared_libs/play-runtime/run-snapshot-stream.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +94 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +91 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +19 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +540 -92
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-postgres-admission.ts +162 -0
- package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +18 -4
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +16 -17
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +29 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +22 -1
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +79 -1
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +2 -4
- package/dist/cli/index.js +637 -229
- package/dist/cli/index.mjs +637 -229
- package/dist/{compiler-manifest-j6z8qzpd.d.mts → compiler-manifest-BhgZ23A4.d.mts} +1 -0
- package/dist/{compiler-manifest-j6z8qzpd.d.ts → compiler-manifest-BhgZ23A4.d.ts} +1 -0
- package/dist/index.d.mts +84 -11
- package/dist/index.d.ts +84 -11
- package/dist/index.js +225 -43
- package/dist/index.mjs +225 -43
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -4
- package/package.json +1 -1
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
} from './child-play-await';
|
|
69
69
|
import { submitChildPlayThroughCoordinator } from './child-play-submit';
|
|
70
70
|
import { isRetryableWorkerRuntimeApiError } from './runtime-api-retry';
|
|
71
|
+
import { formatCustomerConsoleValue } from './runtime/customer-console';
|
|
71
72
|
import {
|
|
72
73
|
attachToolResultListDataset,
|
|
73
74
|
createToolExecuteResult,
|
|
@@ -116,6 +117,7 @@ import {
|
|
|
116
117
|
isQueryResultDatasetTool,
|
|
117
118
|
} from '../../../shared_libs/play-runtime/query-result-dataset';
|
|
118
119
|
import { DEDUPE_DUPLICATE_KEY_SAMPLE_CAP } from '../../../shared_libs/play-runtime/map-row-identity';
|
|
120
|
+
import { createRuntimeDatasetId } from '../../../shared_libs/play-runtime/dataset-id';
|
|
119
121
|
import {
|
|
120
122
|
getTopLevelPipelineSubsteps,
|
|
121
123
|
getCompiledPipelineSubsteps,
|
|
@@ -196,12 +198,12 @@ import {
|
|
|
196
198
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
197
199
|
runtimeLeaseHeartbeatIntervalMs,
|
|
198
200
|
} from '../../../shared_libs/play-runtime/lease-policy';
|
|
199
|
-
import { RuntimeSheetRowsBlockedError } from '../../../shared_libs/play-runtime/runtime-sheet-errors';
|
|
200
201
|
import {
|
|
201
202
|
resolveRuntimeMapRowAdmission,
|
|
202
203
|
runtimeMapJsonByteLength,
|
|
203
204
|
} from '../../../shared_libs/play-runtime/map-memory-limits';
|
|
204
205
|
import { createSerialTaskQueue } from '../../../shared_libs/play-runtime/serial-task-queue';
|
|
206
|
+
import { resolveChildExecutionPlacement as resolveChildExecutionStrategy } from '../../../shared_libs/play-runtime/child-execution-placement';
|
|
205
207
|
// The harness stub forwards leaf calls (validation, runtime-api HTTP) into
|
|
206
208
|
// the long-lived Play Harness Worker via env.HARNESS. We import the
|
|
207
209
|
// `setHarnessBinding` setter eagerly so it's available the moment
|
|
@@ -214,12 +216,10 @@ import { createSerialTaskQueue } from '../../../shared_libs/play-runtime/serial-
|
|
|
214
216
|
// re-bundle harness internals into per-play. Keep that in mind.
|
|
215
217
|
import {
|
|
216
218
|
harnessPersistCompletedSheetRows,
|
|
217
|
-
harnessHeartbeatSheetAttempt,
|
|
218
219
|
harnessReadSheetDatasetRowKeys,
|
|
219
220
|
harnessReadSheetDatasetRows,
|
|
220
221
|
harnessReadStagedFileChunk,
|
|
221
222
|
harnessReleaseRuntimeReceipts,
|
|
222
|
-
harnessReleaseSheetAttempt,
|
|
223
223
|
harnessStartSheetDataset,
|
|
224
224
|
setHarnessBinding,
|
|
225
225
|
} from '../../../sdk/src/plays/harness-stub';
|
|
@@ -300,7 +300,7 @@ import type {
|
|
|
300
300
|
LiveNodeProgressMap,
|
|
301
301
|
LiveNodeProgressSnapshot,
|
|
302
302
|
} from './runtime/live-progress';
|
|
303
|
-
import { extractErrorBilling } from './runtime/tool-http-errors';
|
|
303
|
+
import { extractErrorBilling, ToolHttpError } from './runtime/tool-http-errors';
|
|
304
304
|
import {
|
|
305
305
|
WorkflowAbortError,
|
|
306
306
|
isAbortLikeError,
|
|
@@ -324,6 +324,55 @@ import type { PlayRunInputPayload } from '../../../shared_libs/play-runtime/sche
|
|
|
324
324
|
// @ts-ignore - virtual module substituted by esbuild plugin at bundle time.
|
|
325
325
|
import playFn from 'deepline-play-entry';
|
|
326
326
|
|
|
327
|
+
const customerConsoleStorage = new AsyncLocalStorage<
|
|
328
|
+
(message: string) => void
|
|
329
|
+
>();
|
|
330
|
+
|
|
331
|
+
type CustomerConsoleGlobal = typeof globalThis & {
|
|
332
|
+
__deeplineCaptureCustomerConsole?: (
|
|
333
|
+
method: string,
|
|
334
|
+
args: unknown[],
|
|
335
|
+
) => unknown;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
(globalThis as CustomerConsoleGlobal).__deeplineCaptureCustomerConsole = (
|
|
339
|
+
method,
|
|
340
|
+
args,
|
|
341
|
+
) => {
|
|
342
|
+
const log = customerConsoleStorage.getStore();
|
|
343
|
+
if (log) {
|
|
344
|
+
assertNoSecretTaint(args, `console.${method}`);
|
|
345
|
+
const message = args.map(formatCustomerConsoleValue).join(' ');
|
|
346
|
+
log(`[console.${method}] ${message}`.slice(0, 2_000));
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
function runCustomerPlay(
|
|
351
|
+
ctx: unknown,
|
|
352
|
+
input: PlayRunInputPayload,
|
|
353
|
+
): Promise<unknown> {
|
|
354
|
+
return customerConsoleStorage.run(
|
|
355
|
+
(ctx as { log(message: string): void }).log,
|
|
356
|
+
() => (playFn as typeof runCustomerPlay)(ctx, input),
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function runtimeDatasetIdentity(playName: string, tableNamespace: string) {
|
|
361
|
+
return {
|
|
362
|
+
datasetId: createRuntimeDatasetId(playName, tableNamespace),
|
|
363
|
+
path: `datasets.${tableNamespace}`,
|
|
364
|
+
tableNamespace,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function datasetAvailableLog(
|
|
369
|
+
runId: string,
|
|
370
|
+
dataset: ReturnType<typeof runtimeDatasetIdentity>,
|
|
371
|
+
count: number,
|
|
372
|
+
): string {
|
|
373
|
+
return `Dataset ${dataset.path} available: ${count} persisted rows; export: deepline runs export ${runId} --dataset ${dataset.path} --out ${dataset.tableNamespace}.csv`;
|
|
374
|
+
}
|
|
375
|
+
|
|
327
376
|
type RunRequest = {
|
|
328
377
|
runId: string;
|
|
329
378
|
callbackUrl: string;
|
|
@@ -999,6 +1048,17 @@ type WorkerCtxCallbacks = {
|
|
|
999
1048
|
}) => void | Promise<void>;
|
|
1000
1049
|
onMapStarted?: (nodeId: string, at?: number) => void;
|
|
1001
1050
|
onMapCompleted?: (nodeId: string, at?: number) => void;
|
|
1051
|
+
onDatasetLifecycle?: (event: {
|
|
1052
|
+
datasetId: string;
|
|
1053
|
+
path: string;
|
|
1054
|
+
tableNamespace: string;
|
|
1055
|
+
phase: 'registered' | 'available' | 'failed';
|
|
1056
|
+
persistedRows?: number;
|
|
1057
|
+
succeededRows?: number;
|
|
1058
|
+
failedRows?: number;
|
|
1059
|
+
complete?: boolean;
|
|
1060
|
+
at: number;
|
|
1061
|
+
}) => void | Promise<void>;
|
|
1002
1062
|
onToolCalled?: (toolId: string, at?: number) => void;
|
|
1003
1063
|
onToolFailed?: (toolId: string, at?: number) => void;
|
|
1004
1064
|
};
|
|
@@ -1201,136 +1261,6 @@ function makeRuntimeSheetAttempt(input: {
|
|
|
1201
1261
|
};
|
|
1202
1262
|
}
|
|
1203
1263
|
|
|
1204
|
-
class RuntimeSheetAttemptLeaseLostError extends Error {
|
|
1205
|
-
readonly rowKeys: string[];
|
|
1206
|
-
readonly runId: string;
|
|
1207
|
-
readonly attemptId: string;
|
|
1208
|
-
readonly tableNamespace: string;
|
|
1209
|
-
|
|
1210
|
-
constructor(input: {
|
|
1211
|
-
rowKeys: string[];
|
|
1212
|
-
runId: string;
|
|
1213
|
-
attemptId: string;
|
|
1214
|
-
tableNamespace: string;
|
|
1215
|
-
cause?: unknown;
|
|
1216
|
-
}) {
|
|
1217
|
-
const rowSummary =
|
|
1218
|
-
input.rowKeys.length === 1
|
|
1219
|
-
? input.rowKeys[0]!
|
|
1220
|
-
: `${input.rowKeys.length} rows`;
|
|
1221
|
-
super(
|
|
1222
|
-
`Runtime sheet attempt ${input.attemptId} lost ownership of ${rowSummary} in ${input.tableNamespace}.`,
|
|
1223
|
-
{ cause: input.cause },
|
|
1224
|
-
);
|
|
1225
|
-
this.name = 'RuntimeSheetAttemptLeaseLostError';
|
|
1226
|
-
this.rowKeys = input.rowKeys;
|
|
1227
|
-
this.runId = input.runId;
|
|
1228
|
-
this.attemptId = input.attemptId;
|
|
1229
|
-
this.tableNamespace = input.tableNamespace;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
async function executeWithRuntimeSheetAttemptHeartbeat<T>(input: {
|
|
1234
|
-
req: RunRequest;
|
|
1235
|
-
tableNamespace: string;
|
|
1236
|
-
sheetContract: PlaySheetContract;
|
|
1237
|
-
attemptId?: string | null;
|
|
1238
|
-
attemptOwnerRunId?: string | null;
|
|
1239
|
-
attemptSeq?: number | null;
|
|
1240
|
-
rowKeys: string[];
|
|
1241
|
-
execute: () => Promise<T> | T;
|
|
1242
|
-
}): Promise<T> {
|
|
1243
|
-
const attemptId = input.attemptId?.trim();
|
|
1244
|
-
const rowKeys = [
|
|
1245
|
-
...new Set(input.rowKeys.map((key) => key.trim()).filter(Boolean)),
|
|
1246
|
-
];
|
|
1247
|
-
if (!attemptId || rowKeys.length === 0) {
|
|
1248
|
-
return await input.execute();
|
|
1249
|
-
}
|
|
1250
|
-
const heartbeatIntervalMs = runtimePolicyHeartbeatIntervalMs(
|
|
1251
|
-
input.req,
|
|
1252
|
-
runtimeSheetAttemptLeaseTtlMs(input.req) ??
|
|
1253
|
-
PLAY_RUNTIME_SHEET_ATTEMPT_LEASE_TTL_MS,
|
|
1254
|
-
);
|
|
1255
|
-
|
|
1256
|
-
let stopped = false;
|
|
1257
|
-
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
1258
|
-
let rejectLeaseLost!: (error: RuntimeSheetAttemptLeaseLostError) => void;
|
|
1259
|
-
const leaseLost = new Promise<never>((_, reject) => {
|
|
1260
|
-
rejectLeaseLost = reject;
|
|
1261
|
-
});
|
|
1262
|
-
|
|
1263
|
-
const stop = () => {
|
|
1264
|
-
stopped = true;
|
|
1265
|
-
if (timeout !== null) {
|
|
1266
|
-
clearTimeout(timeout);
|
|
1267
|
-
timeout = null;
|
|
1268
|
-
}
|
|
1269
|
-
};
|
|
1270
|
-
|
|
1271
|
-
const heartbeatOnce = async () => {
|
|
1272
|
-
const result = await harnessHeartbeatSheetAttempt({
|
|
1273
|
-
...runtimeSheetSessionScope(input.req),
|
|
1274
|
-
tableNamespace: input.tableNamespace,
|
|
1275
|
-
sheetContract: input.sheetContract,
|
|
1276
|
-
runId: input.req.runId,
|
|
1277
|
-
attemptId,
|
|
1278
|
-
attemptOwnerRunId: input.attemptOwnerRunId ?? input.req.runId,
|
|
1279
|
-
attemptSeq: input.attemptSeq ?? input.req.runAttempt ?? 0,
|
|
1280
|
-
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(input.req),
|
|
1281
|
-
keys: rowKeys,
|
|
1282
|
-
});
|
|
1283
|
-
const renewed = new Set(result.renewedKeys);
|
|
1284
|
-
const lostKeys = rowKeys.filter((key) => !renewed.has(key));
|
|
1285
|
-
if (lostKeys.length > 0) {
|
|
1286
|
-
throw new RuntimeSheetAttemptLeaseLostError({
|
|
1287
|
-
rowKeys: lostKeys,
|
|
1288
|
-
runId: input.req.runId,
|
|
1289
|
-
attemptId,
|
|
1290
|
-
tableNamespace: input.tableNamespace,
|
|
1291
|
-
});
|
|
1292
|
-
}
|
|
1293
|
-
};
|
|
1294
|
-
|
|
1295
|
-
const schedule = () => {
|
|
1296
|
-
timeout = setTimeout(() => {
|
|
1297
|
-
void (async () => {
|
|
1298
|
-
try {
|
|
1299
|
-
await heartbeatOnce();
|
|
1300
|
-
} catch (error) {
|
|
1301
|
-
stop();
|
|
1302
|
-
if (error instanceof RuntimeSheetAttemptLeaseLostError) {
|
|
1303
|
-
rejectLeaseLost(error);
|
|
1304
|
-
} else {
|
|
1305
|
-
rejectLeaseLost(
|
|
1306
|
-
new RuntimeSheetAttemptLeaseLostError({
|
|
1307
|
-
rowKeys,
|
|
1308
|
-
runId: input.req.runId,
|
|
1309
|
-
attemptId,
|
|
1310
|
-
tableNamespace: input.tableNamespace,
|
|
1311
|
-
cause: error,
|
|
1312
|
-
}),
|
|
1313
|
-
);
|
|
1314
|
-
}
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
if (!stopped) {
|
|
1318
|
-
schedule();
|
|
1319
|
-
}
|
|
1320
|
-
})();
|
|
1321
|
-
}, heartbeatIntervalMs);
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
schedule();
|
|
1325
|
-
const execution = Promise.resolve().then(input.execute);
|
|
1326
|
-
execution.catch(() => {});
|
|
1327
|
-
try {
|
|
1328
|
-
return await Promise.race([execution, leaseLost]);
|
|
1329
|
-
} finally {
|
|
1330
|
-
stop();
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
1264
|
/**
|
|
1335
1265
|
* Strip credentials and JWT-shaped tokens from any string before it lands in
|
|
1336
1266
|
* a log buffer or upstream error message. The harness routinely echoes
|
|
@@ -2073,6 +2003,7 @@ async function callToolDirect(
|
|
|
2073
2003
|
while (true) {
|
|
2074
2004
|
requestAttempt += 1;
|
|
2075
2005
|
let res: Response;
|
|
2006
|
+
const providerCallStartedAt = nowMs();
|
|
2076
2007
|
try {
|
|
2077
2008
|
const runtimeApiTimeout = resolveToolRuntimeApiTimeout({
|
|
2078
2009
|
requestInput: input,
|
|
@@ -2161,8 +2092,11 @@ async function callToolDirect(
|
|
|
2161
2092
|
} catch (error) {
|
|
2162
2093
|
transportAttempt += 1;
|
|
2163
2094
|
const message = error instanceof Error ? error.message : String(error);
|
|
2164
|
-
lastError = new
|
|
2095
|
+
lastError = new ToolHttpError(
|
|
2165
2096
|
`Tool ${toolId} transport failed calling ${path} for run ${req.runId} on attempt ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
|
|
2097
|
+
null,
|
|
2098
|
+
0,
|
|
2099
|
+
'repairable',
|
|
2166
2100
|
);
|
|
2167
2101
|
if (
|
|
2168
2102
|
transportAttempt >= TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS ||
|
|
@@ -2212,6 +2146,7 @@ async function callToolDirect(
|
|
|
2212
2146
|
bodyText: text,
|
|
2213
2147
|
retryAfterHeader: res.headers.get('retry-after'),
|
|
2214
2148
|
transientHttpRetrySafe,
|
|
2149
|
+
providerLatencyMs: nowMs() - providerCallStartedAt,
|
|
2215
2150
|
});
|
|
2216
2151
|
lastError = failure.error;
|
|
2217
2152
|
if (failure.backpressureDelayMs !== null) {
|
|
@@ -2458,10 +2393,7 @@ function isRunFatalWorkerRowError(error: unknown): boolean {
|
|
|
2458
2393
|
isRowIsolationExemptError(error) ||
|
|
2459
2394
|
isRuntimeReceiptPersistenceError(error) ||
|
|
2460
2395
|
isRuntimePersistenceCircuitOpenError(error) ||
|
|
2461
|
-
error instanceof
|
|
2462
|
-
error instanceof RuntimeLeaseLostError ||
|
|
2463
|
-
(error instanceof Error &&
|
|
2464
|
-
error.name === 'RuntimeSheetAttemptLeaseLostError')
|
|
2396
|
+
error instanceof RuntimeLeaseLostError
|
|
2465
2397
|
);
|
|
2466
2398
|
}
|
|
2467
2399
|
|
|
@@ -3541,53 +3473,18 @@ function resolveSheetContractFromReq(
|
|
|
3541
3473
|
}
|
|
3542
3474
|
}
|
|
3543
3475
|
|
|
3544
|
-
/**
|
|
3545
|
-
* Release attempt-scoped leases after terminal settlement. Completed and failed
|
|
3546
|
-
* rows are durable; retaining their live fences only delays the next repair run.
|
|
3547
|
-
*/
|
|
3476
|
+
/** Release temporary work-receipt ownership after terminal settlement. */
|
|
3548
3477
|
async function releaseRuntimeLeasesOnSettlement(input: {
|
|
3549
3478
|
req: RunRequest;
|
|
3550
3479
|
leasedSheetNamespaces: Set<string>;
|
|
3551
3480
|
emit: (event: RunnerEvent) => void;
|
|
3552
3481
|
reason: 'completed' | 'run-fatal';
|
|
3553
3482
|
}): Promise<void> {
|
|
3554
|
-
const { req,
|
|
3483
|
+
const { req, emit, reason } = input;
|
|
3484
|
+
void input.leasedSheetNamespaces;
|
|
3555
3485
|
const reasonLabel =
|
|
3556
3486
|
reason === 'completed' ? 'completed settlement' : 'run-fatal teardown';
|
|
3557
3487
|
const scope = runtimeSheetSessionScope(req);
|
|
3558
|
-
for (const tableNamespace of leasedSheetNamespaces) {
|
|
3559
|
-
const sheetContract = resolveSheetContractFromReq(req, tableNamespace);
|
|
3560
|
-
if (!sheetContract) continue;
|
|
3561
|
-
try {
|
|
3562
|
-
const released = await harnessReleaseSheetAttempt({
|
|
3563
|
-
...scope,
|
|
3564
|
-
tableNamespace,
|
|
3565
|
-
sheetContract,
|
|
3566
|
-
runId: req.runId,
|
|
3567
|
-
attemptOwnerRunId: req.runId,
|
|
3568
|
-
attemptSeq: req.runAttempt ?? 0,
|
|
3569
|
-
});
|
|
3570
|
-
if (released.released > 0) {
|
|
3571
|
-
emit({
|
|
3572
|
-
type: 'log',
|
|
3573
|
-
level: 'info',
|
|
3574
|
-
message: `Released ${released.released} runtime sheet row lease(s) for ctx.dataset("${tableNamespace}") on ${reasonLabel}.`,
|
|
3575
|
-
ts: nowMs(),
|
|
3576
|
-
});
|
|
3577
|
-
}
|
|
3578
|
-
} catch (releaseError) {
|
|
3579
|
-
emit({
|
|
3580
|
-
type: 'log',
|
|
3581
|
-
level: 'warn',
|
|
3582
|
-
message: `Runtime sheet attempt release failed for ctx.dataset("${tableNamespace}") on ${reasonLabel} (rerun will fall back to lease-TTL expiry): ${
|
|
3583
|
-
releaseError instanceof Error
|
|
3584
|
-
? releaseError.message
|
|
3585
|
-
: String(releaseError)
|
|
3586
|
-
}`,
|
|
3587
|
-
ts: nowMs(),
|
|
3588
|
-
});
|
|
3589
|
-
}
|
|
3590
|
-
}
|
|
3591
3488
|
try {
|
|
3592
3489
|
const released = await harnessReleaseRuntimeReceipts({
|
|
3593
3490
|
...scope,
|
|
@@ -3662,6 +3559,7 @@ async function persistCompletedMapRows(input: {
|
|
|
3662
3559
|
attemptOwnerRunId?: string | null;
|
|
3663
3560
|
attemptExpiresAt?: string | null;
|
|
3664
3561
|
attemptSeq?: number | null;
|
|
3562
|
+
writeVersion?: number | null;
|
|
3665
3563
|
budgetMeter?: WorkerWorkBudgetMeter;
|
|
3666
3564
|
force?: boolean;
|
|
3667
3565
|
forceFailedRows?: boolean;
|
|
@@ -3671,6 +3569,8 @@ async function persistCompletedMapRows(input: {
|
|
|
3671
3569
|
visible: number;
|
|
3672
3570
|
retryWritten: number | null;
|
|
3673
3571
|
retryVisible: number | null;
|
|
3572
|
+
staleDropped: number;
|
|
3573
|
+
staleDroppedKeys: string[];
|
|
3674
3574
|
}> {
|
|
3675
3575
|
if (input.rows.length === 0) {
|
|
3676
3576
|
return {
|
|
@@ -3679,6 +3579,8 @@ async function persistCompletedMapRows(input: {
|
|
|
3679
3579
|
visible: 0,
|
|
3680
3580
|
retryWritten: null,
|
|
3681
3581
|
retryVisible: null,
|
|
3582
|
+
staleDropped: 0,
|
|
3583
|
+
staleDroppedKeys: [],
|
|
3682
3584
|
};
|
|
3683
3585
|
}
|
|
3684
3586
|
const { req, tableNamespace } = input;
|
|
@@ -3714,6 +3616,7 @@ async function persistCompletedMapRows(input: {
|
|
|
3714
3616
|
attemptOwnerRunId: input.attemptOwnerRunId ?? null,
|
|
3715
3617
|
attemptExpiresAt: input.attemptExpiresAt ?? null,
|
|
3716
3618
|
attemptSeq: input.attemptSeq ?? req.runAttempt ?? 0,
|
|
3619
|
+
writeVersion: input.writeVersion ?? null,
|
|
3717
3620
|
forceFailedRows: input.forceFailedRows === true,
|
|
3718
3621
|
};
|
|
3719
3622
|
input.budgetMeter?.count('sheet');
|
|
@@ -3747,24 +3650,8 @@ async function persistCompletedMapRows(input: {
|
|
|
3747
3650
|
return result.rows.length;
|
|
3748
3651
|
};
|
|
3749
3652
|
const result = await harnessPersistCompletedSheetRows(persistRequest);
|
|
3750
|
-
const
|
|
3751
|
-
const
|
|
3752
|
-
written: number,
|
|
3753
|
-
visible: number | null,
|
|
3754
|
-
) => {
|
|
3755
|
-
// Recovery finalization may include rows completed by an earlier attempt.
|
|
3756
|
-
// A complete terminal readback proves they were not lost.
|
|
3757
|
-
if (
|
|
3758
|
-
!hasAttemptFence ||
|
|
3759
|
-
written === rows.length ||
|
|
3760
|
-
(visible !== null && visible >= expectedVisibleRows)
|
|
3761
|
-
) {
|
|
3762
|
-
return;
|
|
3763
|
-
}
|
|
3764
|
-
throw new Error(
|
|
3765
|
-
`Runtime sheet attempt lost ownership for ${tableNamespace}: wrote ${written}/${rows.length}; run ${req.runId}; attempt ${input.attemptId}.`,
|
|
3766
|
-
);
|
|
3767
|
-
};
|
|
3653
|
+
const staleDropped = result.staleDropped ?? 0;
|
|
3654
|
+
const staleDroppedKeys = result.staleDroppedKeys ?? result.fencedKeys ?? [];
|
|
3768
3655
|
console.warn('[play-runner.persist_completed_map_rows.result]', {
|
|
3769
3656
|
runId: req.runId,
|
|
3770
3657
|
playName: req.playName,
|
|
@@ -3793,6 +3680,7 @@ async function persistCompletedMapRows(input: {
|
|
|
3793
3680
|
attemptOwnerRunId: input.attemptOwnerRunId ?? null,
|
|
3794
3681
|
attemptExpiresAt: input.attemptExpiresAt ?? null,
|
|
3795
3682
|
attemptSeq: input.attemptSeq ?? req.runAttempt ?? 0,
|
|
3683
|
+
writeVersion: input.writeVersion ?? null,
|
|
3796
3684
|
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
|
|
3797
3685
|
inputOffset: 0,
|
|
3798
3686
|
});
|
|
@@ -3801,7 +3689,6 @@ async function persistCompletedMapRows(input: {
|
|
|
3801
3689
|
input.budgetMeter?.count('sheet');
|
|
3802
3690
|
retryWritten = retry.rowsWritten;
|
|
3803
3691
|
retryVisible = await readVisibleRowCount();
|
|
3804
|
-
assertAttemptStillOwnsTerminalWrites(retryWritten, retryVisible);
|
|
3805
3692
|
if (retryVisible >= expectedVisibleRows) {
|
|
3806
3693
|
return {
|
|
3807
3694
|
rows: rows.length,
|
|
@@ -3809,13 +3696,19 @@ async function persistCompletedMapRows(input: {
|
|
|
3809
3696
|
visible: visibleRows,
|
|
3810
3697
|
retryWritten,
|
|
3811
3698
|
retryVisible,
|
|
3699
|
+
staleDropped: staleDropped + (retry.staleDropped ?? 0),
|
|
3700
|
+
staleDroppedKeys: [
|
|
3701
|
+
...new Set([
|
|
3702
|
+
...staleDroppedKeys,
|
|
3703
|
+
...(retry.staleDroppedKeys ?? retry.fencedKeys ?? []),
|
|
3704
|
+
]),
|
|
3705
|
+
],
|
|
3812
3706
|
};
|
|
3813
3707
|
}
|
|
3814
3708
|
throw new Error(
|
|
3815
3709
|
`Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}/${expectedVisibleRows}; retry wrote ${retryWritten}/${rows.length}; retry visible ${retryVisible}/${expectedVisibleRows}; run ${req.runId}.`,
|
|
3816
3710
|
);
|
|
3817
3711
|
}
|
|
3818
|
-
assertAttemptStillOwnsTerminalWrites(result.rowsWritten, visibleRows);
|
|
3819
3712
|
}
|
|
3820
3713
|
if (result.rowsWritten !== rows.length && visibleRows < expectedVisibleRows) {
|
|
3821
3714
|
throw new Error(
|
|
@@ -3828,6 +3721,8 @@ async function persistCompletedMapRows(input: {
|
|
|
3828
3721
|
visible: visibleRows,
|
|
3829
3722
|
retryWritten,
|
|
3830
3723
|
retryVisible,
|
|
3724
|
+
staleDropped,
|
|
3725
|
+
staleDroppedKeys,
|
|
3831
3726
|
};
|
|
3832
3727
|
}
|
|
3833
3728
|
|
|
@@ -3841,6 +3736,7 @@ async function applyLiveMapRowUpdates(input: {
|
|
|
3841
3736
|
attemptOwnerRunId?: string | null;
|
|
3842
3737
|
attemptExpiresAt?: string | null;
|
|
3843
3738
|
attemptSeq?: number | null;
|
|
3739
|
+
writeVersion?: number | null;
|
|
3844
3740
|
budgetMeter?: WorkerWorkBudgetMeter;
|
|
3845
3741
|
}): Promise<void> {
|
|
3846
3742
|
if (input.updates.length === 0) return;
|
|
@@ -3891,6 +3787,7 @@ async function prepareMapRows(input: {
|
|
|
3891
3787
|
attemptOwnerRunId?: string | null;
|
|
3892
3788
|
attemptExpiresAt?: string | null;
|
|
3893
3789
|
attemptSeq?: number | null;
|
|
3790
|
+
writeVersion?: number | null;
|
|
3894
3791
|
budgetMeter?: WorkerWorkBudgetMeter;
|
|
3895
3792
|
force?: boolean;
|
|
3896
3793
|
}): Promise<{
|
|
@@ -3903,6 +3800,7 @@ async function prepareMapRows(input: {
|
|
|
3903
3800
|
attemptOwnerRunId?: string | null;
|
|
3904
3801
|
attemptExpiresAt?: string | null;
|
|
3905
3802
|
attemptSeq?: number | null;
|
|
3803
|
+
writeVersion?: number | null;
|
|
3906
3804
|
}> {
|
|
3907
3805
|
if (input.rows.length === 0) {
|
|
3908
3806
|
return {
|
|
@@ -3915,6 +3813,7 @@ async function prepareMapRows(input: {
|
|
|
3915
3813
|
attemptOwnerRunId: input.attemptOwnerRunId ?? null,
|
|
3916
3814
|
attemptExpiresAt: input.attemptExpiresAt ?? null,
|
|
3917
3815
|
attemptSeq: input.attemptSeq ?? input.req.runAttempt ?? 0,
|
|
3816
|
+
writeVersion: input.writeVersion ?? null,
|
|
3918
3817
|
};
|
|
3919
3818
|
}
|
|
3920
3819
|
const sessionScope = runtimeSheetSessionScope(input.req);
|
|
@@ -3934,6 +3833,7 @@ async function prepareMapRows(input: {
|
|
|
3934
3833
|
attemptOwnerRunId: input.attemptOwnerRunId ?? null,
|
|
3935
3834
|
attemptExpiresAt: input.attemptExpiresAt ?? null,
|
|
3936
3835
|
attemptSeq: input.attemptSeq ?? input.req.runAttempt ?? 0,
|
|
3836
|
+
writeVersion: input.writeVersion ?? null,
|
|
3937
3837
|
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(input.req),
|
|
3938
3838
|
...(input.force ? { force: true } : {}),
|
|
3939
3839
|
});
|
|
@@ -3971,6 +3871,7 @@ async function prepareMapRows(input: {
|
|
|
3971
3871
|
attemptExpiresAt: result.attemptExpiresAt ?? input.attemptExpiresAt ?? null,
|
|
3972
3872
|
attemptSeq:
|
|
3973
3873
|
result.attemptSeq ?? input.attemptSeq ?? input.req.runAttempt ?? 0,
|
|
3874
|
+
writeVersion: result.writeVersion ?? input.writeVersion ?? null,
|
|
3974
3875
|
};
|
|
3975
3876
|
}
|
|
3976
3877
|
|
|
@@ -4024,18 +3925,6 @@ function childPipelineUsesCtxDataset(
|
|
|
4024
3925
|
});
|
|
4025
3926
|
}
|
|
4026
3927
|
|
|
4027
|
-
function childPipelineNeedsWorkflowScheduler(
|
|
4028
|
-
pipeline: PlayStaticPipeline | null | undefined,
|
|
4029
|
-
): boolean {
|
|
4030
|
-
if (!pipeline) return false;
|
|
4031
|
-
return flattenStaticPipeline(pipeline).some(
|
|
4032
|
-
(substep) =>
|
|
4033
|
-
substep.type === 'tool' &&
|
|
4034
|
-
(substep.isEventWait === true ||
|
|
4035
|
-
substep.toolId === 'test_wait_for_event'),
|
|
4036
|
-
);
|
|
4037
|
-
}
|
|
4038
|
-
|
|
4039
3928
|
/**
|
|
4040
3929
|
* Build the per-(org,provider) rate port the distributed Rate State Backend
|
|
4041
3930
|
* RPCs through. When the coordinator binding (or its rate RPCs) is absent we
|
|
@@ -4864,7 +4753,75 @@ function createMinimalWorkerCtx(
|
|
|
4864
4753
|
return kept;
|
|
4865
4754
|
};
|
|
4866
4755
|
|
|
4756
|
+
// Reserve the whole logical map before dispatching any provider work. One
|
|
4757
|
+
// schedule-time version represents the user's intent across every chunk;
|
|
4758
|
+
// a later run can supersede it without waiting, and an older chunk can
|
|
4759
|
+
// never allocate a newer version halfway through the map.
|
|
4760
|
+
const inputCapabilities = datasetHandleCapabilities(inputRows);
|
|
4761
|
+
if (!inputCapabilities.replayable) {
|
|
4762
|
+
throw new Error(
|
|
4763
|
+
`ctx.dataset("${name}") requires a replayable input so all row write intents can be reserved before execution.`,
|
|
4764
|
+
);
|
|
4765
|
+
}
|
|
4766
|
+
const reservationRows: Record<string, unknown>[] = [];
|
|
4767
|
+
const reservationExplicitKeys =
|
|
4768
|
+
opts?.key === undefined ? null : new Set<string>();
|
|
4769
|
+
let reservationOffset = 0;
|
|
4770
|
+
let reservationChunkIndex = 0;
|
|
4771
|
+
for await (const chunk of iterDatasetChunks(inputRows, rowsPerChunk)) {
|
|
4772
|
+
assertNotAborted(abortSignal);
|
|
4773
|
+
for (let localIndex = 0; localIndex < chunk.length; localIndex += 1) {
|
|
4774
|
+
const row = chunk[localIndex]!;
|
|
4775
|
+
const absoluteIndex = baseOffset + reservationOffset + localIndex;
|
|
4776
|
+
const explicitKeyValue = resolveExplicitKeyValue(row, absoluteIndex);
|
|
4777
|
+
if (explicitKeyValue !== null && reservationExplicitKeys) {
|
|
4778
|
+
if (reservationExplicitKeys.has(explicitKeyValue)) continue;
|
|
4779
|
+
reservationExplicitKeys.add(explicitKeyValue);
|
|
4780
|
+
}
|
|
4781
|
+
const rowKey = resolveRowKey(
|
|
4782
|
+
row,
|
|
4783
|
+
absoluteIndex,
|
|
4784
|
+
reservationChunkIndex,
|
|
4785
|
+
localIndex,
|
|
4786
|
+
);
|
|
4787
|
+
reservationRows.push({
|
|
4788
|
+
...runtimeCsvStorageRow(row),
|
|
4789
|
+
...mapRowOutcomeRuntimeFields({
|
|
4790
|
+
key: rowKey,
|
|
4791
|
+
inputIndex: absoluteIndex,
|
|
4792
|
+
}),
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
reservationOffset += chunk.length;
|
|
4796
|
+
reservationChunkIndex += 1;
|
|
4797
|
+
}
|
|
4798
|
+
const mapWriteAttempt = makeRuntimeSheetAttempt({
|
|
4799
|
+
runId: req.runId,
|
|
4800
|
+
runAttempt: req.runAttempt,
|
|
4801
|
+
mapName: name,
|
|
4802
|
+
chunkIndex: 'finalize',
|
|
4803
|
+
});
|
|
4804
|
+
const mapReservation =
|
|
4805
|
+
reservationRows.length === 0
|
|
4806
|
+
? null
|
|
4807
|
+
: await harnessStartSheetDataset({
|
|
4808
|
+
...runtimeSheetSessionScope(req),
|
|
4809
|
+
tableNamespace: name,
|
|
4810
|
+
sheetContract: augmentSheetContractWithDatasetFields({
|
|
4811
|
+
contract: requireSheetContract(req, name),
|
|
4812
|
+
rows: reservationRows,
|
|
4813
|
+
outputFields,
|
|
4814
|
+
}),
|
|
4815
|
+
rows: reservationRows,
|
|
4816
|
+
runId: req.runId,
|
|
4817
|
+
inputOffset: 0,
|
|
4818
|
+
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
|
|
4819
|
+
...mapWriteAttempt,
|
|
4820
|
+
});
|
|
4821
|
+
const mapWriteVersion = mapReservation?.writeVersion ?? null;
|
|
4822
|
+
|
|
4867
4823
|
let totalRowsWritten = 0;
|
|
4824
|
+
let datasetLifecycleRegistered = false;
|
|
4868
4825
|
|
|
4869
4826
|
const volatileWorkflowChunkRows = new Map<
|
|
4870
4827
|
number,
|
|
@@ -4912,9 +4869,26 @@ function createMinimalWorkerCtx(
|
|
|
4912
4869
|
})),
|
|
4913
4870
|
inputOffset: baseOffset + chunkStart,
|
|
4914
4871
|
...rowAttempt,
|
|
4872
|
+
writeVersion: mapWriteVersion,
|
|
4915
4873
|
budgetMeter: workBudgetMeter,
|
|
4916
4874
|
force: !!req.force,
|
|
4917
4875
|
});
|
|
4876
|
+
if (!datasetLifecycleRegistered) {
|
|
4877
|
+
datasetLifecycleRegistered = true;
|
|
4878
|
+
const dataset = runtimeDatasetIdentity(req.playName, name);
|
|
4879
|
+
await callbacks?.onDatasetLifecycle?.({
|
|
4880
|
+
...dataset,
|
|
4881
|
+
phase: 'registered',
|
|
4882
|
+
complete: false,
|
|
4883
|
+
at: nowMs(),
|
|
4884
|
+
});
|
|
4885
|
+
emitEvent({
|
|
4886
|
+
type: 'log',
|
|
4887
|
+
level: 'info',
|
|
4888
|
+
message: `Dataset ${dataset.path} registered`,
|
|
4889
|
+
ts: nowMs(),
|
|
4890
|
+
});
|
|
4891
|
+
}
|
|
4918
4892
|
const activeRowAttempt = {
|
|
4919
4893
|
attemptId: prepared.attemptId ?? rowAttempt.attemptId,
|
|
4920
4894
|
attemptOwnerRunId:
|
|
@@ -4922,10 +4896,8 @@ function createMinimalWorkerCtx(
|
|
|
4922
4896
|
attemptExpiresAt:
|
|
4923
4897
|
prepared.attemptExpiresAt ?? rowAttempt.attemptExpiresAt,
|
|
4924
4898
|
attemptSeq: prepared.attemptSeq ?? rowAttempt.attemptSeq,
|
|
4899
|
+
writeVersion: prepared.writeVersion ?? null,
|
|
4925
4900
|
};
|
|
4926
|
-
// Record that this run holds attempt leases in `name`'s sheet so a
|
|
4927
|
-
// run-fatal teardown releases them (the table now exists).
|
|
4928
|
-
leasedSheetNamespaces?.add(name);
|
|
4929
4901
|
recordRunnerPerfTrace({
|
|
4930
4902
|
req,
|
|
4931
4903
|
phase: 'runner.map_chunk.prepare_rows',
|
|
@@ -4961,8 +4933,6 @@ function createMinimalWorkerCtx(
|
|
|
4961
4933
|
const pendingRowsByKey = new Map<string, Record<string, unknown>>();
|
|
4962
4934
|
const completedKeys = new Set<string>();
|
|
4963
4935
|
const completedRowsByKey = new Map<string, Record<string, unknown>>();
|
|
4964
|
-
const blockedKeys = new Set<string>();
|
|
4965
|
-
const blockedRowsByKey = new Map<string, Record<string, unknown>>();
|
|
4966
4936
|
const preparedKeys = new Set<string>();
|
|
4967
4937
|
for (const row of prepared.pendingRows) {
|
|
4968
4938
|
const key =
|
|
@@ -4989,66 +4959,12 @@ function createMinimalWorkerCtx(
|
|
|
4989
4959
|
resolveMapRowOutcomeKey(row) ??
|
|
4990
4960
|
deriveDefaultMapRowIdentity(row, resolveRuntimeInputIndex(row) ?? 0);
|
|
4991
4961
|
if (key) {
|
|
4992
|
-
blockedKeys.add(key);
|
|
4993
|
-
blockedRowsByKey.set(key, row);
|
|
4994
4962
|
preparedKeys.add(key);
|
|
4995
4963
|
}
|
|
4996
4964
|
}
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
MAP_ROW_FAILURE_SAMPLE_LIMIT,
|
|
5001
|
-
);
|
|
5002
|
-
const message =
|
|
5003
|
-
`ctx.dataset("${name}") chunk ${chunkIndex} is blocked by ` +
|
|
5004
|
-
`${blockedKeys.size} active runtime sheet row lease(s) owned by another attempt. ` +
|
|
5005
|
-
`Retry after the owning attempt finishes or the row leases expire.` +
|
|
5006
|
-
(rowKeySamples.length > 0
|
|
5007
|
-
? ` Blocked row keys: ${rowKeySamples.join(', ')}`
|
|
5008
|
-
: '');
|
|
5009
|
-
recordRunnerPerfTrace({
|
|
5010
|
-
req,
|
|
5011
|
-
phase: 'runner.map_chunk.blocked_rows',
|
|
5012
|
-
ms: nowMs() - prepareStartedAt,
|
|
5013
|
-
extra: {
|
|
5014
|
-
mapName: name,
|
|
5015
|
-
chunkIndex,
|
|
5016
|
-
rowsBlocked: blockedKeys.size,
|
|
5017
|
-
rowKeySamples,
|
|
5018
|
-
},
|
|
5019
|
-
});
|
|
5020
|
-
enqueueMapProgressUpdate({
|
|
5021
|
-
completed: preparedCompletedRows,
|
|
5022
|
-
total: progressTotalRows,
|
|
5023
|
-
waitingRows: blockedKeys.size,
|
|
5024
|
-
startedAt: mapStartedAt,
|
|
5025
|
-
message,
|
|
5026
|
-
});
|
|
5027
|
-
return {
|
|
5028
|
-
status: 'blocked',
|
|
5029
|
-
chunkIndex,
|
|
5030
|
-
rangeStart: baseOffset + chunkStart,
|
|
5031
|
-
rangeEnd: baseOffset + chunkStart + chunkRows.length,
|
|
5032
|
-
rowsRead: chunkRows.length,
|
|
5033
|
-
rowsBlocked: blockedKeys.size,
|
|
5034
|
-
rowKeySamples,
|
|
5035
|
-
blockedRowSamples: rowKeySamples.map((rowKey) => {
|
|
5036
|
-
const blockedRow = blockedRowsByKey.get(rowKey);
|
|
5037
|
-
const attemptId =
|
|
5038
|
-
typeof blockedRow?.__deeplineAttemptId === 'string'
|
|
5039
|
-
? blockedRow.__deeplineAttemptId
|
|
5040
|
-
: typeof blockedRow?._attempt_id === 'string'
|
|
5041
|
-
? blockedRow._attempt_id
|
|
5042
|
-
: activeRowAttempt.attemptId;
|
|
5043
|
-
return {
|
|
5044
|
-
__deeplineRowKey: rowKey,
|
|
5045
|
-
__deeplineAttemptId: attemptId,
|
|
5046
|
-
};
|
|
5047
|
-
}),
|
|
5048
|
-
outputDatasetId: `map:${name}`,
|
|
5049
|
-
message,
|
|
5050
|
-
};
|
|
5051
|
-
}
|
|
4965
|
+
// Versioned scheduling never waits for row ownership. Compatibility
|
|
4966
|
+
// adapters may still report an old `blockedRows` disposition; treat it
|
|
4967
|
+
// as a stale/non-executable row and keep it out of this run's output.
|
|
5052
4968
|
const missingPreparedRows = chunkEntries.filter(
|
|
5053
4969
|
({ rowKey }) => !preparedKeys.has(rowKey),
|
|
5054
4970
|
);
|
|
@@ -5375,12 +5291,29 @@ function createMinimalWorkerCtx(
|
|
|
5375
5291
|
ts: nowMs(),
|
|
5376
5292
|
});
|
|
5377
5293
|
}
|
|
5294
|
+
const staleKeys = new Set(persistStats.staleDroppedKeys);
|
|
5378
5295
|
for (const { executedIndex } of rowsToPersist) {
|
|
5379
|
-
|
|
5296
|
+
const rowKey = uniqueRowsToExecuteEntries[executedIndex]!.rowKey;
|
|
5297
|
+
if (staleKeys.has(rowKey)) continue;
|
|
5298
|
+
if (!persistedExecutedIndexes.has(executedIndex)) {
|
|
5299
|
+
persistedExecutedIndexes.add(executedIndex);
|
|
5300
|
+
completedExecutedRows += 1;
|
|
5301
|
+
}
|
|
5380
5302
|
}
|
|
5381
5303
|
for (const { executedIndex } of failedRowsToPersist) {
|
|
5304
|
+
const rowKey = uniqueRowsToExecuteEntries[executedIndex]!.rowKey;
|
|
5305
|
+
if (staleKeys.has(rowKey)) continue;
|
|
5382
5306
|
persistedFailedIndexes.add(executedIndex);
|
|
5383
5307
|
}
|
|
5308
|
+
if (persistStats.staleDropped > 0) {
|
|
5309
|
+
emitEvent({
|
|
5310
|
+
type: 'log',
|
|
5311
|
+
level: 'info',
|
|
5312
|
+
message: `Dropped ${persistStats.staleDropped} stale runtime sheet write(s) for ctx.dataset("${name}"); a newer scheduled row version is visible.`,
|
|
5313
|
+
ts: nowMs(),
|
|
5314
|
+
});
|
|
5315
|
+
}
|
|
5316
|
+
reportChunkProgress(false);
|
|
5384
5317
|
};
|
|
5385
5318
|
|
|
5386
5319
|
const enqueuePersistExecutedRows = (): Promise<void> => {
|
|
@@ -5678,7 +5611,6 @@ function createMinimalWorkerCtx(
|
|
|
5678
5611
|
: undefined;
|
|
5679
5612
|
executedRows[myIndex] = enriched as T &
|
|
5680
5613
|
Record<string, unknown>;
|
|
5681
|
-
completedExecutedRows += 1;
|
|
5682
5614
|
await notePersistableRow(enriched);
|
|
5683
5615
|
reportChunkProgress(false);
|
|
5684
5616
|
} catch (rowError) {
|
|
@@ -5788,25 +5720,14 @@ function createMinimalWorkerCtx(
|
|
|
5788
5720
|
return results;
|
|
5789
5721
|
},
|
|
5790
5722
|
);
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
await Promise.race([
|
|
5800
|
-
workerResultsPromise,
|
|
5801
|
-
sleepWorkerMs(MAP_EXECUTION_HEARTBEAT_INTERVAL_MS),
|
|
5802
|
-
]);
|
|
5803
|
-
if (!workerSettled) {
|
|
5804
|
-
reportExecutionHeartbeat(false);
|
|
5805
|
-
}
|
|
5806
|
-
}
|
|
5807
|
-
return await workerResultsPromise;
|
|
5808
|
-
},
|
|
5809
|
-
});
|
|
5723
|
+
while (!workerSettled) {
|
|
5724
|
+
await Promise.race([
|
|
5725
|
+
workerResultsPromise,
|
|
5726
|
+
sleepWorkerMs(MAP_EXECUTION_HEARTBEAT_INTERVAL_MS),
|
|
5727
|
+
]);
|
|
5728
|
+
if (!workerSettled) reportExecutionHeartbeat(false);
|
|
5729
|
+
}
|
|
5730
|
+
const workerResults = await workerResultsPromise;
|
|
5810
5731
|
recordRunnerPerfTrace({
|
|
5811
5732
|
req,
|
|
5812
5733
|
phase: 'runner.map_chunk.execute_workers',
|
|
@@ -6004,6 +5925,7 @@ function createMinimalWorkerCtx(
|
|
|
6004
5925
|
executedIndex < executedRows.length;
|
|
6005
5926
|
executedIndex += 1
|
|
6006
5927
|
) {
|
|
5928
|
+
if (!persistedExecutedIndexes.has(executedIndex)) continue;
|
|
6007
5929
|
const executedRow = executedRows[executedIndex];
|
|
6008
5930
|
const key = uniqueRowsToExecuteEntries[executedIndex]!.rowKey;
|
|
6009
5931
|
// Failed rows have no executed result; they stay out of the map output
|
|
@@ -6039,6 +5961,8 @@ function createMinimalWorkerCtx(
|
|
|
6039
5961
|
...mapRowOutcomeRuntimeFields({ key, inputIndex }),
|
|
6040
5962
|
}));
|
|
6041
5963
|
const completedRowsReused = completedChunkEntries.length;
|
|
5964
|
+
const committedExecutedRows =
|
|
5965
|
+
persistedExecutedIndexes.size + persistedFailedIndexes.size;
|
|
6042
5966
|
const hashStartedAt = nowMs();
|
|
6043
5967
|
const hash = await hashJson(publicOut);
|
|
6044
5968
|
const includeCachedRowsInChunkResult = !workflowStep;
|
|
@@ -6068,8 +5992,8 @@ function createMinimalWorkerCtx(
|
|
|
6068
5992
|
chunkIndex,
|
|
6069
5993
|
rowsRead: chunkRows.length,
|
|
6070
5994
|
rowsWritten: out.length,
|
|
6071
|
-
rowsExecuted:
|
|
6072
|
-
rowsFailed:
|
|
5995
|
+
rowsExecuted: committedExecutedRows,
|
|
5996
|
+
rowsFailed: persistedFailedIndexes.size,
|
|
6073
5997
|
rowsCached: completedRowsReused,
|
|
6074
5998
|
},
|
|
6075
5999
|
});
|
|
@@ -6080,12 +6004,12 @@ function createMinimalWorkerCtx(
|
|
|
6080
6004
|
rangeEnd: baseOffset + chunkStart + out.length,
|
|
6081
6005
|
rowsRead: chunkRows.length,
|
|
6082
6006
|
rowsWritten: out.length,
|
|
6083
|
-
rowsExecuted:
|
|
6007
|
+
rowsExecuted: committedExecutedRows,
|
|
6084
6008
|
rowsCached: completedRowsReused,
|
|
6085
6009
|
rowsDuplicateReused: duplicateInputReuseCount,
|
|
6086
6010
|
rowsInserted,
|
|
6087
6011
|
rowsSkipped,
|
|
6088
|
-
rowsFailed:
|
|
6012
|
+
rowsFailed: persistedFailedIndexes.size,
|
|
6089
6013
|
rowFailureSamples,
|
|
6090
6014
|
stepCellsCompleted,
|
|
6091
6015
|
stepCellsSkipped,
|
|
@@ -6117,12 +6041,9 @@ function createMinimalWorkerCtx(
|
|
|
6117
6041
|
let totalRowsInserted = 0;
|
|
6118
6042
|
let totalRowsSkipped = 0;
|
|
6119
6043
|
let totalRowsFailed = 0;
|
|
6120
|
-
let totalRowsBlocked = 0;
|
|
6121
6044
|
let totalStepCellsCompleted = 0;
|
|
6122
6045
|
let totalStepCellsSkipped = 0;
|
|
6123
6046
|
const totalRowFailureSamples: Array<{ rowKey: string; error: string }> = [];
|
|
6124
|
-
const blockedRowKeySamples: string[] = [];
|
|
6125
|
-
const blockedRowSamples: Record<string, unknown>[] = [];
|
|
6126
6047
|
|
|
6127
6048
|
const runChunkStep = async (
|
|
6128
6049
|
chunkRows: T[],
|
|
@@ -6261,6 +6182,23 @@ function createMinimalWorkerCtx(
|
|
|
6261
6182
|
finalizedRowsWritten,
|
|
6262
6183
|
),
|
|
6263
6184
|
});
|
|
6185
|
+
const dataset = runtimeDatasetIdentity(req.playName, name);
|
|
6186
|
+
const persistedRows = finalizedRowsWritten + totalRowsFailed;
|
|
6187
|
+
await callbacks?.onDatasetLifecycle?.({
|
|
6188
|
+
...dataset,
|
|
6189
|
+
phase: 'available',
|
|
6190
|
+
persistedRows,
|
|
6191
|
+
succeededRows: finalizedRowsWritten,
|
|
6192
|
+
failedRows: totalRowsFailed,
|
|
6193
|
+
complete: true,
|
|
6194
|
+
at: completedAt,
|
|
6195
|
+
});
|
|
6196
|
+
emitEvent({
|
|
6197
|
+
type: 'log',
|
|
6198
|
+
level: totalRowsFailed > 0 ? 'warn' : 'info',
|
|
6199
|
+
message: datasetAvailableLog(req.runId, dataset, persistedRows),
|
|
6200
|
+
ts: completedAt,
|
|
6201
|
+
});
|
|
6264
6202
|
callbacks?.onMapCompleted?.(mapNodeId, completedAt);
|
|
6265
6203
|
emitEvent({
|
|
6266
6204
|
type: 'log',
|
|
@@ -6299,6 +6237,7 @@ function createMinimalWorkerCtx(
|
|
|
6299
6237
|
runId: req.runId,
|
|
6300
6238
|
inputOffset: 0,
|
|
6301
6239
|
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
|
|
6240
|
+
writeVersion: mapWriteVersion,
|
|
6302
6241
|
...repairAttempt,
|
|
6303
6242
|
});
|
|
6304
6243
|
const activeRepairAttempt = {
|
|
@@ -6308,6 +6247,7 @@ function createMinimalWorkerCtx(
|
|
|
6308
6247
|
attemptExpiresAt:
|
|
6309
6248
|
repairStart.attemptExpiresAt ?? repairAttempt.attemptExpiresAt,
|
|
6310
6249
|
attemptSeq: repairStart.attemptSeq ?? repairAttempt.attemptSeq,
|
|
6250
|
+
writeVersion: repairStart.writeVersion ?? null,
|
|
6311
6251
|
};
|
|
6312
6252
|
const repair = await persistCompletedMapRows({
|
|
6313
6253
|
req,
|
|
@@ -6509,32 +6449,12 @@ function createMinimalWorkerCtx(
|
|
|
6509
6449
|
);
|
|
6510
6450
|
},
|
|
6511
6451
|
onBatchComplete: async (chunkResult) => {
|
|
6512
|
-
if (chunkResult.status === 'blocked')
|
|
6513
|
-
totalRowsBlocked += chunkResult.rowsBlocked;
|
|
6514
|
-
for (const key of chunkResult.rowKeySamples) {
|
|
6515
|
-
if (blockedRowKeySamples.length >= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
|
|
6516
|
-
break;
|
|
6517
|
-
}
|
|
6518
|
-
blockedRowKeySamples.push(key);
|
|
6519
|
-
}
|
|
6520
|
-
for (const row of chunkResult.blockedRowSamples) {
|
|
6521
|
-
if (blockedRowSamples.length >= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
|
|
6522
|
-
break;
|
|
6523
|
-
}
|
|
6524
|
-
blockedRowSamples.push(row);
|
|
6525
|
-
}
|
|
6526
|
-
emitEvent({
|
|
6527
|
-
type: 'log',
|
|
6528
|
-
level: 'warn',
|
|
6529
|
-
message: chunkResult.message,
|
|
6530
|
-
ts: nowMs(),
|
|
6531
|
-
});
|
|
6532
|
-
return true;
|
|
6533
|
-
}
|
|
6452
|
+
if (chunkResult.status === 'blocked') return true;
|
|
6534
6453
|
if (chunkResult.fatalError) {
|
|
6535
6454
|
throw new Error(
|
|
6536
6455
|
`ctx.dataset("${name}") stopped after a runtime persistence failure ` +
|
|
6537
|
-
`outside the retryable chunk step.
|
|
6456
|
+
`outside the retryable chunk step. Circuit breaker prevented queued row ` +
|
|
6457
|
+
`dispatch. Provider calls already executed for ` +
|
|
6538
6458
|
`${chunkResult.rowsExecuted} row(s), so the chunk was not retried. ` +
|
|
6539
6459
|
`Fix the runtime persistence cause and re-run to resume. ` +
|
|
6540
6460
|
`First error: ${chunkResult.fatalError}`,
|
|
@@ -6621,13 +6541,6 @@ function createMinimalWorkerCtx(
|
|
|
6621
6541
|
ts: nowMs(),
|
|
6622
6542
|
});
|
|
6623
6543
|
}
|
|
6624
|
-
if (totalRowsBlocked > 0) {
|
|
6625
|
-
await flushQueuedMapProgressUpdates();
|
|
6626
|
-
throw new RuntimeSheetRowsBlockedError({
|
|
6627
|
-
tableNamespace: name,
|
|
6628
|
-
blockedRows: blockedRowSamples,
|
|
6629
|
-
});
|
|
6630
|
-
}
|
|
6631
6544
|
if (failFastRowErrors && totalRowsFailed > 0 && totalRowsWritten > 0) {
|
|
6632
6545
|
// onRowError:'fail', PARTIAL failure (some rows committed): fail the run
|
|
6633
6546
|
// without finalizing the dataset. The committed rows already persisted
|
|
@@ -6992,6 +6905,7 @@ function createMinimalWorkerCtx(
|
|
|
6992
6905
|
options?: {
|
|
6993
6906
|
description?: string;
|
|
6994
6907
|
timeoutMs?: number;
|
|
6908
|
+
execution?: 'inline' | 'child-workflow';
|
|
6995
6909
|
},
|
|
6996
6910
|
): Promise<unknown> {
|
|
6997
6911
|
const normalizedKey = normalizeContextKey(key, 'runPlay');
|
|
@@ -7066,8 +6980,35 @@ function createMinimalWorkerCtx(
|
|
|
7066
6980
|
const childIsDatasetBacked = childPipelineUsesCtxDataset(
|
|
7067
6981
|
childManifest.staticPipeline,
|
|
7068
6982
|
);
|
|
6983
|
+
const childWaitsForEvent = (
|
|
6984
|
+
childManifest.staticPipeline
|
|
6985
|
+
? flattenStaticPipeline(childManifest.staticPipeline)
|
|
6986
|
+
: []
|
|
6987
|
+
).some(
|
|
6988
|
+
(step) =>
|
|
6989
|
+
step.type === 'tool' &&
|
|
6990
|
+
(step.isEventWait || step.toolId === 'test_wait_for_event'),
|
|
6991
|
+
);
|
|
6992
|
+
const authoredWorkflow =
|
|
6993
|
+
options?.timeoutMs != null ||
|
|
6994
|
+
childIsDatasetBacked ||
|
|
6995
|
+
childWaitsForEvent;
|
|
6996
|
+
if (options?.execution === 'inline' && authoredWorkflow) {
|
|
6997
|
+
throw new Error(
|
|
6998
|
+
`ctx.runPlay("${resolvedName}") requested inline execution for a child that requires a workflow.`,
|
|
6999
|
+
);
|
|
7000
|
+
}
|
|
7001
|
+
const childExecutionDecision = resolveChildExecutionStrategy({
|
|
7002
|
+
requiresWorkflow: authoredWorkflow,
|
|
7003
|
+
execution: options?.execution,
|
|
7004
|
+
});
|
|
7069
7005
|
const childNeedsWorkflowScheduler =
|
|
7070
|
-
|
|
7006
|
+
childExecutionDecision.strategy === 'scheduled';
|
|
7007
|
+
if (rowScope && childNeedsWorkflowScheduler) {
|
|
7008
|
+
throw new Error(
|
|
7009
|
+
`ctx.runPlay("${resolvedName}") cannot start a child workflow per dataset row.`,
|
|
7010
|
+
);
|
|
7011
|
+
}
|
|
7071
7012
|
console.info('[play.runtime.span]', {
|
|
7072
7013
|
event: 'play.runtime.span',
|
|
7073
7014
|
phase: 'child_route',
|
|
@@ -7116,8 +7057,7 @@ function createMinimalWorkerCtx(
|
|
|
7116
7057
|
coordinatorBinding: cachedCoordinatorBinding,
|
|
7117
7058
|
makeRequestId,
|
|
7118
7059
|
coordinatorRequestHeaders,
|
|
7119
|
-
allowInline:
|
|
7120
|
-
options?.timeoutMs == null && !childNeedsWorkflowScheduler,
|
|
7060
|
+
allowInline: childExecutionDecision.strategy === 'inline',
|
|
7121
7061
|
body: {
|
|
7122
7062
|
name: resolvedName,
|
|
7123
7063
|
childIdempotencyKey: childInvocationId,
|
|
@@ -7607,9 +7547,7 @@ async function handleRun(request: Request, env: WorkerEnv): Promise<Response> {
|
|
|
7607
7547
|
captureHarnessBinding(env);
|
|
7608
7548
|
await probeHarnessOnce(env, runPrefix);
|
|
7609
7549
|
const ctx = createMinimalWorkerCtx(req, emit, env);
|
|
7610
|
-
const result = await (
|
|
7611
|
-
playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
|
|
7612
|
-
)(ctx, req.runtimeInput);
|
|
7550
|
+
const result = await runCustomerPlay(ctx, req.runtimeInput);
|
|
7613
7551
|
emit({
|
|
7614
7552
|
type: 'result',
|
|
7615
7553
|
result,
|
|
@@ -8023,6 +7961,17 @@ async function executeRunRequest(
|
|
|
8023
7961
|
},
|
|
8024
7962
|
onMapStarted: (nodeId, at) => stepLifecycle?.onMapStarted(nodeId, at),
|
|
8025
7963
|
onMapCompleted: (nodeId, at) => stepLifecycle?.onMapCompleted(nodeId, at),
|
|
7964
|
+
onDatasetLifecycle: async (event) => {
|
|
7965
|
+
const { at, ...dataset } = event;
|
|
7966
|
+
pendingLedgerEvents.push({
|
|
7967
|
+
...dataset,
|
|
7968
|
+
type: 'dataset.lifecycle',
|
|
7969
|
+
runId: req.runId,
|
|
7970
|
+
source: 'worker',
|
|
7971
|
+
occurredAt: at,
|
|
7972
|
+
});
|
|
7973
|
+
await flushLedgerEvents(event.complete === true);
|
|
7974
|
+
},
|
|
8026
7975
|
onToolCalled: (toolId, at) => stepLifecycle?.onToolCalled(toolId, at),
|
|
8027
7976
|
onToolFailed: (toolId, at) => stepLifecycle?.onToolFailed(toolId, at),
|
|
8028
7977
|
};
|
|
@@ -8089,9 +8038,7 @@ async function executeRunRequest(
|
|
|
8089
8038
|
: null;
|
|
8090
8039
|
try {
|
|
8091
8040
|
const playStartedAt = nowMs();
|
|
8092
|
-
const result = await (
|
|
8093
|
-
playFn as (ctx: unknown, input: PlayRunInputPayload) => Promise<unknown>
|
|
8094
|
-
)(ctx, req.runtimeInput);
|
|
8041
|
+
const result = await runCustomerPlay(ctx, req.runtimeInput);
|
|
8095
8042
|
recordRunnerPerfTrace({
|
|
8096
8043
|
req,
|
|
8097
8044
|
phase: 'runner.play_function',
|
|
@@ -8135,7 +8082,33 @@ async function executeRunRequest(
|
|
|
8135
8082
|
ms: nowMs() - ledgerFlushWaitStartedAt,
|
|
8136
8083
|
});
|
|
8137
8084
|
const resultDatasetStartedAt = nowMs();
|
|
8138
|
-
await persistResultDatasets(
|
|
8085
|
+
const persistedResultCounts = await persistResultDatasets(
|
|
8086
|
+
req,
|
|
8087
|
+
promoted.handles,
|
|
8088
|
+
serializedResult,
|
|
8089
|
+
);
|
|
8090
|
+
for (const dataset of promoted.handles) {
|
|
8091
|
+
if (dataset.datasetKind === 'map') continue;
|
|
8092
|
+
const identity = runtimeDatasetIdentity(
|
|
8093
|
+
req.playName,
|
|
8094
|
+
dataset.tableNamespace,
|
|
8095
|
+
);
|
|
8096
|
+
const count = persistedResultCounts.get(dataset.tableNamespace) ?? 0;
|
|
8097
|
+
pendingLedgerEvents.push({
|
|
8098
|
+
...identity,
|
|
8099
|
+
type: 'dataset.lifecycle',
|
|
8100
|
+
runId: req.runId,
|
|
8101
|
+
source: 'worker',
|
|
8102
|
+
occurredAt: nowMs(),
|
|
8103
|
+
phase: 'available',
|
|
8104
|
+
persistedRows: count,
|
|
8105
|
+
succeededRows: count,
|
|
8106
|
+
failedRows: 0,
|
|
8107
|
+
complete: true,
|
|
8108
|
+
});
|
|
8109
|
+
appendRunLogLine(datasetAvailableLog(req.runId, identity, count));
|
|
8110
|
+
}
|
|
8111
|
+
await flushLedgerEvents(true);
|
|
8139
8112
|
resultDatasetsPersisted = true;
|
|
8140
8113
|
recordRunnerPerfTrace({
|
|
8141
8114
|
req,
|
|
@@ -8599,8 +8572,9 @@ async function persistResultDatasets(
|
|
|
8599
8572
|
req: RunRequest,
|
|
8600
8573
|
resultDatasets: ProjectedResultDatasetHandle[],
|
|
8601
8574
|
serializedResult: unknown,
|
|
8602
|
-
): Promise<
|
|
8575
|
+
): Promise<Map<string, number>> {
|
|
8603
8576
|
const persistedNamespaces = new Set<string>();
|
|
8577
|
+
const persistedCounts = new Map<string, number>();
|
|
8604
8578
|
for (const dataset of resultDatasets) {
|
|
8605
8579
|
if (dataset.datasetKind === 'map') continue;
|
|
8606
8580
|
const handle = dataset.handle as WorkerDatasetHandle<
|
|
@@ -8639,6 +8613,7 @@ async function persistResultDatasets(
|
|
|
8639
8613
|
inputOffset += chunk.length;
|
|
8640
8614
|
}
|
|
8641
8615
|
persistedNamespaces.add(dataset.tableNamespace);
|
|
8616
|
+
persistedCounts.set(dataset.tableNamespace, inputOffset);
|
|
8642
8617
|
}
|
|
8643
8618
|
|
|
8644
8619
|
const datasets = collectDatasetEnvelopes(serializedResult);
|
|
@@ -8672,7 +8647,9 @@ async function persistResultDatasets(
|
|
|
8672
8647
|
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
|
|
8673
8648
|
userEmail: req.userEmail,
|
|
8674
8649
|
});
|
|
8650
|
+
persistedCounts.set(dataset.tableNamespace, dataset.rows.length);
|
|
8675
8651
|
}
|
|
8652
|
+
return persistedCounts;
|
|
8676
8653
|
}
|
|
8677
8654
|
|
|
8678
8655
|
const RESULT_DATASET_PERSIST_CHUNK_ROWS = 5_000;
|