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
|
@@ -66,6 +66,22 @@ export type RuntimeStatusUpdate = {
|
|
|
66
66
|
* positional cursor stay correct once the buffer has rotated.
|
|
67
67
|
*/
|
|
68
68
|
liveLogTotalCount?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Monotonic scheduler attempt that owns the positional log channel. A new
|
|
71
|
+
* attempt starts at offset zero without colliding with earlier attempts.
|
|
72
|
+
*/
|
|
73
|
+
liveLogProducerAttempt?: number;
|
|
74
|
+
datasetLifecycleEvents?: Array<{
|
|
75
|
+
datasetId: string;
|
|
76
|
+
path: string;
|
|
77
|
+
tableNamespace: string;
|
|
78
|
+
phase: 'registered' | 'available' | 'failed';
|
|
79
|
+
persistedRows?: number;
|
|
80
|
+
succeededRows?: number;
|
|
81
|
+
failedRows?: number;
|
|
82
|
+
complete?: boolean;
|
|
83
|
+
at: number;
|
|
84
|
+
}>;
|
|
69
85
|
/**
|
|
70
86
|
* Explicit terminal-output replay for final runner logs when the caller must
|
|
71
87
|
* keep `status` nonterminal. Direct-worker/Postgres progress finalizers do this so
|
|
@@ -75,6 +91,8 @@ export type RuntimeStatusUpdate = {
|
|
|
75
91
|
terminalLogReplay?: {
|
|
76
92
|
lines: string[];
|
|
77
93
|
totalCount?: number;
|
|
94
|
+
dedupeMode?: 'semantic' | 'exact';
|
|
95
|
+
producerAttempt?: number;
|
|
78
96
|
} | null;
|
|
79
97
|
liveTimeline?: PlayRunTimelineEntry[];
|
|
80
98
|
liveNodeProgress?: PlayVisualNodeProgressMap;
|
|
@@ -189,11 +207,27 @@ type RuntimeApiRequest =
|
|
|
189
207
|
runId: string;
|
|
190
208
|
key: string;
|
|
191
209
|
}
|
|
210
|
+
| {
|
|
211
|
+
action: 'acquire_runtime_receipt_execution_lock';
|
|
212
|
+
playName: string;
|
|
213
|
+
runId: string;
|
|
214
|
+
key: string;
|
|
215
|
+
ownerExecutionId: string;
|
|
216
|
+
ttlMs: number;
|
|
217
|
+
}
|
|
218
|
+
| {
|
|
219
|
+
action: 'release_runtime_receipt_execution_lock';
|
|
220
|
+
playName: string;
|
|
221
|
+
runId: string;
|
|
222
|
+
key: string;
|
|
223
|
+
ownerExecutionId: string;
|
|
224
|
+
}
|
|
192
225
|
| {
|
|
193
226
|
action: 'claim_runtime_step_receipt';
|
|
194
227
|
playName: string;
|
|
195
228
|
runId: string;
|
|
196
229
|
key: string;
|
|
230
|
+
leaseId?: string;
|
|
197
231
|
runAttempt?: number | null;
|
|
198
232
|
leaseAware?: boolean;
|
|
199
233
|
reclaimRunning?: boolean;
|
|
@@ -247,6 +281,7 @@ type RuntimeApiRequest =
|
|
|
247
281
|
playName: string;
|
|
248
282
|
runId: string;
|
|
249
283
|
keys: string[];
|
|
284
|
+
leaseIds?: string[];
|
|
250
285
|
runAttempt?: number | null;
|
|
251
286
|
leaseAware?: boolean;
|
|
252
287
|
reclaimRunning?: boolean;
|
|
@@ -347,12 +382,84 @@ function applyRetryJitter(delayMs: number): number {
|
|
|
347
382
|
const APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
348
383
|
const RUN_STATUS_LEDGER_SNAPSHOT_CACHE_LIMIT = 1_000;
|
|
349
384
|
const runStatusLedgerSnapshots = new Map<string, PlayRunLedgerSnapshot>();
|
|
350
|
-
// Positional log cursor per run: count of liveLogs lines already
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
// positionally (exactly-once on the channel, repeated identical lines kept).
|
|
385
|
+
// Positional log cursor per run attempt: count of liveLogs lines already
|
|
386
|
+
// forwarded from this process. A fresh process re-sends from offset 0; durable
|
|
387
|
+
// ingestion skips overlap within that same attempt.
|
|
354
388
|
const runLogChannelSentCounts = new Map<string, number>();
|
|
355
389
|
const runStatusUpdateChains = new Map<string, Promise<void>>();
|
|
390
|
+
const APP_RUNTIME_LOG_EVENT_MAX_JSON_BYTES = 128 * 1024;
|
|
391
|
+
const APP_RUNTIME_EVENT_BATCH_MAX_JSON_BYTES = 512 * 1024;
|
|
392
|
+
const appRuntimeTextEncoder = new TextEncoder();
|
|
393
|
+
|
|
394
|
+
function splitRunEventForAppRuntime(
|
|
395
|
+
event: PlayRunLedgerEvent,
|
|
396
|
+
): PlayRunLedgerEvent[] {
|
|
397
|
+
if (event.type !== 'log.appended' || event.lines.length === 0) return [event];
|
|
398
|
+
const split: PlayRunLedgerEvent[] = [];
|
|
399
|
+
let index = 0;
|
|
400
|
+
while (index < event.lines.length) {
|
|
401
|
+
const start = index;
|
|
402
|
+
let bytes = 2;
|
|
403
|
+
while (index < event.lines.length) {
|
|
404
|
+
const lineBytes = appRuntimeTextEncoder.encode(
|
|
405
|
+
JSON.stringify(event.lines[index]!),
|
|
406
|
+
).length;
|
|
407
|
+
if (
|
|
408
|
+
index > start &&
|
|
409
|
+
bytes + lineBytes + 1 > APP_RUNTIME_LOG_EVENT_MAX_JSON_BYTES
|
|
410
|
+
) {
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
bytes += lineBytes + 1;
|
|
414
|
+
index += 1;
|
|
415
|
+
}
|
|
416
|
+
split.push({
|
|
417
|
+
...event,
|
|
418
|
+
lines: event.lines.slice(start, index),
|
|
419
|
+
...(typeof event.channelOffset === 'number'
|
|
420
|
+
? { channelOffset: event.channelOffset + start }
|
|
421
|
+
: {}),
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return split;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export function partitionRunEventsForAppRuntime(
|
|
428
|
+
events: readonly PlayRunLedgerEvent[],
|
|
429
|
+
): PlayRunLedgerEvent[][] {
|
|
430
|
+
const batches: PlayRunLedgerEvent[][] = [];
|
|
431
|
+
let current: PlayRunLedgerEvent[] = [];
|
|
432
|
+
let currentBytes = 2;
|
|
433
|
+
for (const event of events.flatMap(splitRunEventForAppRuntime)) {
|
|
434
|
+
const eventBytes = appRuntimeTextEncoder.encode(
|
|
435
|
+
JSON.stringify(event),
|
|
436
|
+
).length;
|
|
437
|
+
if (
|
|
438
|
+
current.length > 0 &&
|
|
439
|
+
currentBytes + eventBytes + 1 > APP_RUNTIME_EVENT_BATCH_MAX_JSON_BYTES
|
|
440
|
+
) {
|
|
441
|
+
batches.push(current);
|
|
442
|
+
current = [];
|
|
443
|
+
currentBytes = 2;
|
|
444
|
+
}
|
|
445
|
+
current.push(event);
|
|
446
|
+
currentBytes += eventBytes + 1;
|
|
447
|
+
}
|
|
448
|
+
if (current.length > 0) batches.push(current);
|
|
449
|
+
return batches;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function appendRunEventBatchesViaAppRuntime(
|
|
453
|
+
context: WorkerRuntimeApiContext,
|
|
454
|
+
input: { playId: string; events: readonly PlayRunLedgerEvent[] },
|
|
455
|
+
): Promise<void> {
|
|
456
|
+
for (const events of partitionRunEventsForAppRuntime(input.events)) {
|
|
457
|
+
await appendRunEventsViaAppRuntime(context, {
|
|
458
|
+
playId: input.playId,
|
|
459
|
+
events,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
356
463
|
|
|
357
464
|
function isTerminalRuntimeStatus(status: string): boolean {
|
|
358
465
|
return (
|
|
@@ -364,6 +471,10 @@ function isTerminalRuntimeStatus(status: string): boolean {
|
|
|
364
471
|
);
|
|
365
472
|
}
|
|
366
473
|
|
|
474
|
+
function runLogChannelKey(runId: string, producerAttempt?: number): string {
|
|
475
|
+
return `${runId}:${producerAttempt ?? 'legacy'}`;
|
|
476
|
+
}
|
|
477
|
+
|
|
367
478
|
function rememberRunStatusLedgerSnapshot(
|
|
368
479
|
runId: string,
|
|
369
480
|
snapshot: PlayRunLedgerSnapshot,
|
|
@@ -376,7 +487,11 @@ function rememberRunStatusLedgerSnapshot(
|
|
|
376
487
|
const oldestRunId = runStatusLedgerSnapshots.keys().next().value;
|
|
377
488
|
if (!oldestRunId) break;
|
|
378
489
|
runStatusLedgerSnapshots.delete(oldestRunId);
|
|
379
|
-
runLogChannelSentCounts.
|
|
490
|
+
for (const key of runLogChannelSentCounts.keys()) {
|
|
491
|
+
if (key.startsWith(`${oldestRunId}:`)) {
|
|
492
|
+
runLogChannelSentCounts.delete(key);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
380
495
|
runStatusUpdateChains.delete(oldestRunId);
|
|
381
496
|
}
|
|
382
497
|
}
|
|
@@ -399,12 +514,16 @@ function isRetryableAppRuntimeResponse(input: {
|
|
|
399
514
|
if (input.status === 500 && isTransientAppRuntimeFailureBody(input.body)) {
|
|
400
515
|
return true;
|
|
401
516
|
}
|
|
517
|
+
return isRetryableAppRuntimeResponseStatus(input.status);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function isRetryableAppRuntimeResponseStatus(status: number): boolean {
|
|
402
521
|
return (
|
|
403
|
-
|
|
404
|
-
(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
522
|
+
status === 429 ||
|
|
523
|
+
(status >= 520 && status <= 524) ||
|
|
524
|
+
status === 502 ||
|
|
525
|
+
status === 503 ||
|
|
526
|
+
status === 504
|
|
408
527
|
);
|
|
409
528
|
}
|
|
410
529
|
|
|
@@ -601,6 +720,38 @@ function sleep(ms: number): Promise<void> {
|
|
|
601
720
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
602
721
|
}
|
|
603
722
|
|
|
723
|
+
async function retryAppRuntimeBodyTimeoutOrThrow(input: {
|
|
724
|
+
action: RuntimeApiRequest['action'];
|
|
725
|
+
attempt: number;
|
|
726
|
+
maxAttempts: number;
|
|
727
|
+
error: unknown;
|
|
728
|
+
retryAfterMs?: number | null;
|
|
729
|
+
}): Promise<void> {
|
|
730
|
+
if (!isRequestTimeoutAbort(input.error)) {
|
|
731
|
+
throw input.error;
|
|
732
|
+
}
|
|
733
|
+
if (
|
|
734
|
+
input.attempt < input.maxAttempts &&
|
|
735
|
+
isRetryableAppRuntimeFetchError({
|
|
736
|
+
action: input.action,
|
|
737
|
+
error: input.error,
|
|
738
|
+
})
|
|
739
|
+
) {
|
|
740
|
+
await sleep(
|
|
741
|
+
Math.max(
|
|
742
|
+
applyRetryJitter(appRuntimeRetryDelayMs(input.action, input.attempt)),
|
|
743
|
+
input.retryAfterMs ?? 0,
|
|
744
|
+
),
|
|
745
|
+
);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
throw new AppRuntimeApiTransportError({
|
|
749
|
+
action: input.action,
|
|
750
|
+
attempts: input.attempt,
|
|
751
|
+
cause: input.error,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
604
755
|
export class AppRuntimeApiTransportError extends Error {
|
|
605
756
|
readonly action: RuntimeApiRequest['action'];
|
|
606
757
|
readonly attempts: number;
|
|
@@ -691,9 +842,40 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
691
842
|
});
|
|
692
843
|
}
|
|
693
844
|
if (response.ok) {
|
|
694
|
-
|
|
845
|
+
try {
|
|
846
|
+
return (await response.json()) as TResponse;
|
|
847
|
+
} catch (error) {
|
|
848
|
+
await retryAppRuntimeBodyTimeoutOrThrow({
|
|
849
|
+
action: body.action,
|
|
850
|
+
attempt,
|
|
851
|
+
maxAttempts,
|
|
852
|
+
error,
|
|
853
|
+
});
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
let responseText: string;
|
|
858
|
+
try {
|
|
859
|
+
responseText = await response.text();
|
|
860
|
+
} catch (error) {
|
|
861
|
+
if (
|
|
862
|
+
isRequestTimeoutAbort(error) &&
|
|
863
|
+
!isRetryableAppRuntimeResponseStatus(response.status)
|
|
864
|
+
) {
|
|
865
|
+
throw new Error(
|
|
866
|
+
`App runtime API ${body.action} failed with status ${response.status}: response body timed out`,
|
|
867
|
+
{ cause: error },
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
await retryAppRuntimeBodyTimeoutOrThrow({
|
|
871
|
+
action: body.action,
|
|
872
|
+
attempt,
|
|
873
|
+
maxAttempts,
|
|
874
|
+
error,
|
|
875
|
+
retryAfterMs: appRuntimeRetryAfterMs(response, ''),
|
|
876
|
+
});
|
|
877
|
+
continue;
|
|
695
878
|
}
|
|
696
|
-
const responseText = await response.text();
|
|
697
879
|
if (
|
|
698
880
|
attempt < maxAttempts &&
|
|
699
881
|
isRetryableAppRuntimeResponse({
|
|
@@ -917,7 +1099,9 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
917
1099
|
// durable stream can recover any progress flush that was lost in transit.
|
|
918
1100
|
const sentCount = isTerminalRuntimeStatus(update.status)
|
|
919
1101
|
? 0
|
|
920
|
-
: (runLogChannelSentCounts.get(
|
|
1102
|
+
: (runLogChannelSentCounts.get(
|
|
1103
|
+
runLogChannelKey(update.playId, update.liveLogProducerAttempt),
|
|
1104
|
+
) ?? 0);
|
|
921
1105
|
const logSlice = Array.isArray(update.liveLogs)
|
|
922
1106
|
? slicePositionalLogLines({
|
|
923
1107
|
bufferLines: update.liveLogs,
|
|
@@ -932,6 +1116,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
932
1116
|
? {
|
|
933
1117
|
lines: update.liveLogs,
|
|
934
1118
|
totalCount: update.liveLogTotalCount,
|
|
1119
|
+
producerAttempt: update.liveLogProducerAttempt,
|
|
935
1120
|
}
|
|
936
1121
|
: null;
|
|
937
1122
|
const statusPatchLogSlice = terminalLogReplay ? null : logSlice;
|
|
@@ -944,6 +1129,7 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
944
1129
|
lastCheckpointAt: update.lastCheckpointAt ?? null,
|
|
945
1130
|
liveLogs: statusPatchLogSlice?.lines ?? null,
|
|
946
1131
|
liveLogsChannelOffset: statusPatchLogSlice?.channelOffset ?? null,
|
|
1132
|
+
liveLogsProducerAttempt: update.liveLogProducerAttempt ?? null,
|
|
947
1133
|
liveNodeProgress: update.liveNodeProgress ?? null,
|
|
948
1134
|
result: update.result,
|
|
949
1135
|
},
|
|
@@ -951,33 +1137,87 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
951
1137
|
now: update.lastCheckpointAt ?? Date.now(),
|
|
952
1138
|
source: 'worker',
|
|
953
1139
|
});
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1140
|
+
for (const event of update.datasetLifecycleEvents ?? []) {
|
|
1141
|
+
const current = previousSnapshot.datasetsById[event.datasetId];
|
|
1142
|
+
const changesSnapshot =
|
|
1143
|
+
!current ||
|
|
1144
|
+
current.path !== event.path ||
|
|
1145
|
+
current.tableNamespace !== event.tableNamespace ||
|
|
1146
|
+
current.phase !== event.phase ||
|
|
1147
|
+
(event.persistedRows ?? 0) > current.persistedRows ||
|
|
1148
|
+
(event.succeededRows ?? 0) > current.succeededRows ||
|
|
1149
|
+
(event.failedRows ?? 0) > current.failedRows ||
|
|
1150
|
+
(event.complete === true && current.complete !== true);
|
|
1151
|
+
if (!changesSnapshot) continue;
|
|
1152
|
+
events.push({
|
|
1153
|
+
type: 'dataset.lifecycle',
|
|
1154
|
+
runId: update.playId,
|
|
1155
|
+
source: 'worker',
|
|
1156
|
+
occurredAt: event.at,
|
|
1157
|
+
datasetId: event.datasetId,
|
|
1158
|
+
path: event.path,
|
|
1159
|
+
tableNamespace: event.tableNamespace,
|
|
1160
|
+
phase: event.phase,
|
|
1161
|
+
...(event.persistedRows === undefined
|
|
1162
|
+
? {}
|
|
1163
|
+
: { persistedRows: event.persistedRows }),
|
|
1164
|
+
...(event.succeededRows === undefined
|
|
1165
|
+
? {}
|
|
1166
|
+
: { succeededRows: event.succeededRows }),
|
|
1167
|
+
...(event.failedRows === undefined
|
|
1168
|
+
? {}
|
|
1169
|
+
: { failedRows: event.failedRows }),
|
|
1170
|
+
...(event.complete === undefined ? {} : { complete: event.complete }),
|
|
1171
|
+
});
|
|
964
1172
|
}
|
|
965
|
-
|
|
1173
|
+
const terminalLogEvents =
|
|
1174
|
+
terminalLogReplay && terminalLogReplay.lines.length > 0
|
|
1175
|
+
? buildTerminalLogReplayEvents({
|
|
1176
|
+
runId: update.playId,
|
|
1177
|
+
source: 'worker',
|
|
1178
|
+
occurredAt: update.lastCheckpointAt ?? Date.now(),
|
|
1179
|
+
lines: terminalLogReplay.lines,
|
|
1180
|
+
liveLogTotalCount: terminalLogReplay.totalCount,
|
|
1181
|
+
dedupeMode: terminalLogReplay.dedupeMode,
|
|
1182
|
+
producerAttempt: terminalLogReplay.producerAttempt,
|
|
1183
|
+
})
|
|
1184
|
+
: [];
|
|
1185
|
+
if (events.length === 0 && terminalLogEvents.length === 0) {
|
|
966
1186
|
return;
|
|
967
1187
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1188
|
+
const combinedEvents = [...events, ...terminalLogEvents];
|
|
1189
|
+
if (partitionRunEventsForAppRuntime(combinedEvents).length === 1) {
|
|
1190
|
+
// Preserve the single-mutation fast path for ordinary bounded updates.
|
|
1191
|
+
await appendRunEventsViaAppRuntime(context, {
|
|
1192
|
+
playId: update.playId,
|
|
1193
|
+
events: combinedEvents,
|
|
1194
|
+
});
|
|
1195
|
+
} else {
|
|
1196
|
+
// Persist terminal lifecycle first so an oversized or interrupted
|
|
1197
|
+
// best-effort log heal can never prevent settlement. Each bounded log
|
|
1198
|
+
// batch is a separate mutation; retries are safe because ingestion owns
|
|
1199
|
+
// positional reconciliation.
|
|
1200
|
+
await appendRunEventBatchesViaAppRuntime(context, {
|
|
1201
|
+
playId: update.playId,
|
|
1202
|
+
events,
|
|
1203
|
+
});
|
|
1204
|
+
await appendRunEventBatchesViaAppRuntime(context, {
|
|
1205
|
+
playId: update.playId,
|
|
1206
|
+
events: terminalLogEvents,
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
972
1209
|
if (statusPatchLogSlice) {
|
|
973
1210
|
runLogChannelSentCounts.set(
|
|
974
|
-
update.playId,
|
|
1211
|
+
runLogChannelKey(update.playId, update.liveLogProducerAttempt),
|
|
975
1212
|
statusPatchLogSlice.channelOffset + statusPatchLogSlice.lines.length,
|
|
976
1213
|
);
|
|
977
1214
|
}
|
|
978
1215
|
rememberRunStatusLedgerSnapshot(
|
|
979
1216
|
update.playId,
|
|
980
|
-
reducePlayRunLedgerEvents(previousSnapshot,
|
|
1217
|
+
reducePlayRunLedgerEvents(previousSnapshot, [
|
|
1218
|
+
...events,
|
|
1219
|
+
...terminalLogEvents,
|
|
1220
|
+
]),
|
|
981
1221
|
);
|
|
982
1222
|
}
|
|
983
1223
|
|
|
@@ -1109,6 +1349,38 @@ export async function getRuntimeStepReceiptViaAppRuntime(
|
|
|
1109
1349
|
});
|
|
1110
1350
|
}
|
|
1111
1351
|
|
|
1352
|
+
export async function acquireRuntimeReceiptExecutionLockViaAppRuntime(
|
|
1353
|
+
context: WorkerRuntimeApiContext,
|
|
1354
|
+
input: Omit<
|
|
1355
|
+
Extract<
|
|
1356
|
+
RuntimeApiRequest,
|
|
1357
|
+
{ action: 'acquire_runtime_receipt_execution_lock' }
|
|
1358
|
+
>,
|
|
1359
|
+
'action'
|
|
1360
|
+
>,
|
|
1361
|
+
): Promise<{ ownerExecutionId: string; expiresAt: string } | null> {
|
|
1362
|
+
return await postAppRuntimeApi(context, {
|
|
1363
|
+
action: 'acquire_runtime_receipt_execution_lock',
|
|
1364
|
+
...input,
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
export async function releaseRuntimeReceiptExecutionLockViaAppRuntime(
|
|
1369
|
+
context: WorkerRuntimeApiContext,
|
|
1370
|
+
input: Omit<
|
|
1371
|
+
Extract<
|
|
1372
|
+
RuntimeApiRequest,
|
|
1373
|
+
{ action: 'release_runtime_receipt_execution_lock' }
|
|
1374
|
+
>,
|
|
1375
|
+
'action'
|
|
1376
|
+
>,
|
|
1377
|
+
): Promise<boolean> {
|
|
1378
|
+
return await postAppRuntimeApi(context, {
|
|
1379
|
+
action: 'release_runtime_receipt_execution_lock',
|
|
1380
|
+
...input,
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1112
1384
|
export async function getRuntimeStepReceiptsViaAppRuntime(
|
|
1113
1385
|
context: WorkerRuntimeApiContext,
|
|
1114
1386
|
input: Omit<
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PlayCallExecution } from './play-call-execution';
|
|
2
|
+
|
|
3
|
+
export function resolveChildExecutionPlacement(input: {
|
|
4
|
+
requiresWorkflow: boolean;
|
|
5
|
+
execution?: PlayCallExecution | null;
|
|
6
|
+
}): { strategy: 'inline' | 'scheduled' } {
|
|
7
|
+
return {
|
|
8
|
+
strategy:
|
|
9
|
+
input.execution === 'child-workflow' ||
|
|
10
|
+
(input.execution !== 'inline' && input.requiresWorkflow)
|
|
11
|
+
? 'scheduled'
|
|
12
|
+
: 'inline',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
flattenStaticPipeline,
|
|
3
3
|
type PlayStaticPipeline,
|
|
4
4
|
} from '../plays/static-pipeline';
|
|
5
|
+
import type { PlayCallExecution } from './play-call-execution';
|
|
6
|
+
import { resolveChildExecutionPlacement } from './child-execution-placement';
|
|
5
7
|
|
|
6
8
|
export type ChildExecutionStrategy = 'inline' | 'scheduled';
|
|
7
9
|
|
|
@@ -12,7 +14,9 @@ export type ChildExecutionDecision = {
|
|
|
12
14
|
| 'missing_static_contract'
|
|
13
15
|
| 'dataset_child'
|
|
14
16
|
| 'event_wait_child'
|
|
15
|
-
| 'explicit_timeout'
|
|
17
|
+
| 'explicit_timeout'
|
|
18
|
+
| 'explicit_inline'
|
|
19
|
+
| 'explicit_child_workflow';
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -23,31 +27,61 @@ export type ChildExecutionDecision = {
|
|
|
23
27
|
export function resolveChildExecutionStrategy(input: {
|
|
24
28
|
pipeline: PlayStaticPipeline | null | undefined;
|
|
25
29
|
timeoutMs?: number | null;
|
|
30
|
+
execution?: PlayCallExecution | null;
|
|
31
|
+
childPlayName?: string | null;
|
|
26
32
|
}): ChildExecutionDecision {
|
|
27
|
-
if (input.
|
|
28
|
-
return { strategy: 'scheduled', reason: '
|
|
33
|
+
if (input.execution === 'child-workflow') {
|
|
34
|
+
return { strategy: 'scheduled', reason: 'explicit_child_workflow' };
|
|
29
35
|
}
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
const steps = input.pipeline ? flattenStaticPipeline(input.pipeline) : [];
|
|
37
|
+
const authoredDecision: ChildExecutionDecision =
|
|
38
|
+
input.timeoutMs != null
|
|
39
|
+
? { strategy: 'scheduled', reason: 'explicit_timeout' }
|
|
40
|
+
: !input.pipeline
|
|
41
|
+
? { strategy: 'scheduled', reason: 'missing_static_contract' }
|
|
42
|
+
: input.pipeline.tableNamespace ||
|
|
43
|
+
input.pipeline.sheetContract ||
|
|
44
|
+
steps.some((step) => step.type === 'dataset' || step.type === 'csv')
|
|
45
|
+
? { strategy: 'scheduled', reason: 'dataset_child' }
|
|
46
|
+
: steps.some(
|
|
47
|
+
(step) =>
|
|
48
|
+
step.type === 'tool' &&
|
|
49
|
+
(step.isEventWait || step.toolId === 'test_wait_for_event'),
|
|
50
|
+
)
|
|
51
|
+
? { strategy: 'scheduled', reason: 'event_wait_child' }
|
|
52
|
+
: { strategy: 'inline', reason: 'scalar_child' };
|
|
53
|
+
if (input.execution === 'inline') {
|
|
54
|
+
if (
|
|
55
|
+
authoredDecision.strategy === 'scheduled' &&
|
|
56
|
+
authoredDecision.reason !== 'missing_static_contract'
|
|
57
|
+
) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
inlineExecutionRequiresScalarChildMessage(
|
|
60
|
+
input.childPlayName?.trim() || 'child',
|
|
61
|
+
authoredDecision.reason,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return { strategy: 'inline', reason: 'explicit_inline' };
|
|
32
66
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
input.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return { strategy: 'scheduled', reason: 'dataset_child' };
|
|
67
|
+
const placement = resolveChildExecutionPlacement({
|
|
68
|
+
requiresWorkflow: authoredDecision.strategy === 'scheduled',
|
|
69
|
+
execution: input.execution,
|
|
70
|
+
});
|
|
71
|
+
if (placement.strategy === 'inline') {
|
|
72
|
+
return { strategy: 'inline', reason: 'scalar_child' };
|
|
40
73
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
return authoredDecision;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function inlineExecutionRequiresScalarChildMessage(
|
|
78
|
+
childPlayName: string,
|
|
79
|
+
reason: ChildExecutionDecision['reason'],
|
|
80
|
+
): string {
|
|
81
|
+
return (
|
|
82
|
+
`ctx.runPlay("${childPlayName}") requested execution: "inline", but the child requires a child workflow ` +
|
|
83
|
+
`(${reason}). Remove the explicit execution option or use execution: "child-workflow".`
|
|
84
|
+
);
|
|
51
85
|
}
|
|
52
86
|
|
|
53
87
|
/**
|
|
@@ -63,7 +97,7 @@ export function inlineChildScheduledInRowMessage(
|
|
|
63
97
|
return (
|
|
64
98
|
`ctx.runPlay("${childPlayName}") runs per row in the "${rowContextField}" batch, ` +
|
|
65
99
|
'but it is a scheduling-required child (it uses ctx.dataset()/ctx.csv(), waits on an ' +
|
|
66
|
-
'event,
|
|
100
|
+
'event, sets an explicit timeout, or explicitly requests a child workflow). Row-scoped children must be scalar. ' +
|
|
67
101
|
'Give the batch ONE dataset child at play level instead of fanning a scheduled run out per row.'
|
|
68
102
|
);
|
|
69
103
|
}
|