deepline 0.1.205 → 0.1.207
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 +25 -16
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +104 -13
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +51 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-lifecycle.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +321 -175
- package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +13 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +16 -4
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +161 -81
- package/dist/bundling-sources/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +26 -9
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +109 -1
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-execution-scope.ts +256 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +17 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +103 -18
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +74 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +5 -12
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +2 -1
- package/dist/bundling-sources/shared_libs/runtime-env.ts +55 -0
- package/dist/cli/index.js +27 -6
- package/dist/cli/index.mjs +27 -6
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
|
|
41
41
|
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
|
|
42
42
|
} from '../../../shared_libs/play-runtime/runtime-constants';
|
|
43
|
+
import { childSubmitIdempotencyKey } from '../../../shared_libs/play-runtime/child-run-id';
|
|
43
44
|
import {
|
|
44
45
|
createPlayExecutionGovernor,
|
|
45
46
|
type GovernanceSnapshot,
|
|
@@ -3545,18 +3546,18 @@ function resolveSheetContractFromReq(
|
|
|
3545
3546
|
}
|
|
3546
3547
|
|
|
3547
3548
|
/**
|
|
3548
|
-
*
|
|
3549
|
-
*
|
|
3550
|
-
* out the 10-minute lease TTL. Every release is attempt/owner fenced downstream,
|
|
3551
|
-
* so it can never weaken a different run's live lease. Failures are logged loud
|
|
3552
|
-
* and swallowed here — the caller still throws the original run-fatal error.
|
|
3549
|
+
* Release attempt-scoped leases after terminal settlement. Completed and failed
|
|
3550
|
+
* rows are durable; retaining their live fences only delays the next repair run.
|
|
3553
3551
|
*/
|
|
3554
|
-
async function
|
|
3552
|
+
async function releaseRuntimeLeasesOnSettlement(input: {
|
|
3555
3553
|
req: RunRequest;
|
|
3556
3554
|
leasedSheetNamespaces: Set<string>;
|
|
3557
3555
|
emit: (event: RunnerEvent) => void;
|
|
3556
|
+
reason: 'completed' | 'run-fatal';
|
|
3558
3557
|
}): Promise<void> {
|
|
3559
|
-
const { req, leasedSheetNamespaces, emit } = input;
|
|
3558
|
+
const { req, leasedSheetNamespaces, emit, reason } = input;
|
|
3559
|
+
const reasonLabel =
|
|
3560
|
+
reason === 'completed' ? 'completed settlement' : 'run-fatal teardown';
|
|
3560
3561
|
const scope = runtimeSheetSessionScope(req);
|
|
3561
3562
|
for (const tableNamespace of leasedSheetNamespaces) {
|
|
3562
3563
|
const sheetContract = resolveSheetContractFromReq(req, tableNamespace);
|
|
@@ -3574,7 +3575,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3574
3575
|
emit({
|
|
3575
3576
|
type: 'log',
|
|
3576
3577
|
level: 'info',
|
|
3577
|
-
message: `Released ${released.released} runtime sheet row lease(s) for ctx.dataset("${tableNamespace}") on
|
|
3578
|
+
message: `Released ${released.released} runtime sheet row lease(s) for ctx.dataset("${tableNamespace}") on ${reasonLabel}.`,
|
|
3578
3579
|
ts: nowMs(),
|
|
3579
3580
|
});
|
|
3580
3581
|
}
|
|
@@ -3582,7 +3583,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3582
3583
|
emit({
|
|
3583
3584
|
type: 'log',
|
|
3584
3585
|
level: 'warn',
|
|
3585
|
-
message: `Runtime sheet attempt release failed for ctx.dataset("${tableNamespace}") on
|
|
3586
|
+
message: `Runtime sheet attempt release failed for ctx.dataset("${tableNamespace}") on ${reasonLabel} (rerun will fall back to lease-TTL expiry): ${
|
|
3586
3587
|
releaseError instanceof Error
|
|
3587
3588
|
? releaseError.message
|
|
3588
3589
|
: String(releaseError)
|
|
@@ -3601,7 +3602,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3601
3602
|
emit({
|
|
3602
3603
|
type: 'log',
|
|
3603
3604
|
level: 'info',
|
|
3604
|
-
message: `Released ${released.released} work-receipt lease(s) on
|
|
3605
|
+
message: `Released ${released.released} work-receipt lease(s) on ${reasonLabel}.`,
|
|
3605
3606
|
ts: nowMs(),
|
|
3606
3607
|
});
|
|
3607
3608
|
}
|
|
@@ -3609,7 +3610,7 @@ async function releaseRuntimeLeasesOnTeardown(input: {
|
|
|
3609
3610
|
emit({
|
|
3610
3611
|
type: 'log',
|
|
3611
3612
|
level: 'warn',
|
|
3612
|
-
message: `Work-receipt release failed on
|
|
3613
|
+
message: `Work-receipt release failed on ${reasonLabel} (rerun will fall back to lease-TTL expiry): ${
|
|
3613
3614
|
releaseError instanceof Error
|
|
3614
3615
|
? releaseError.message
|
|
3615
3616
|
: String(releaseError)
|
|
@@ -7046,10 +7047,11 @@ function createMinimalWorkerCtx(
|
|
|
7046
7047
|
options?.timeoutMs == null && !childNeedsWorkflowScheduler,
|
|
7047
7048
|
body: {
|
|
7048
7049
|
name: resolvedName,
|
|
7049
|
-
childIdempotencyKey:
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7050
|
+
childIdempotencyKey: childSubmitIdempotencyKey({
|
|
7051
|
+
receiptKey,
|
|
7052
|
+
wasFailed,
|
|
7053
|
+
leaseId: receiptContext.leaseId,
|
|
7054
|
+
}),
|
|
7053
7055
|
input: isRecord(input) ? input : {},
|
|
7054
7056
|
orgId: req.orgId,
|
|
7055
7057
|
callbackUrl: req.callbackUrl,
|
|
@@ -8154,6 +8156,12 @@ async function executeRunRequest(
|
|
|
8154
8156
|
if (req.playCallGovernance && !options?.persistResultDatasets) {
|
|
8155
8157
|
await persistProjectedResultDatasets();
|
|
8156
8158
|
}
|
|
8159
|
+
await releaseRuntimeLeasesOnSettlement({
|
|
8160
|
+
req,
|
|
8161
|
+
leasedSheetNamespaces,
|
|
8162
|
+
emit: wrappedEmit,
|
|
8163
|
+
reason: 'completed',
|
|
8164
|
+
});
|
|
8157
8165
|
if (options?.persistResultDatasets) {
|
|
8158
8166
|
await persistProjectedResultDatasets();
|
|
8159
8167
|
// Capped runs settle compute billing BEFORE declaring run.completed: a
|
|
@@ -8274,10 +8282,11 @@ async function executeRunRequest(
|
|
|
8274
8282
|
// blocked for the full lease TTL. Best-effort but LOUD — a release failure
|
|
8275
8283
|
// is logged and never masks the original run-fatal error. TTL expiry stays
|
|
8276
8284
|
// the recovery path for true crashes (isolate death) where this never runs.
|
|
8277
|
-
await
|
|
8285
|
+
await releaseRuntimeLeasesOnSettlement({
|
|
8278
8286
|
req,
|
|
8279
8287
|
leasedSheetNamespaces,
|
|
8280
8288
|
emit: wrappedEmit,
|
|
8289
|
+
reason: 'run-fatal',
|
|
8281
8290
|
});
|
|
8282
8291
|
const errorBilling = extractErrorBilling(error);
|
|
8283
8292
|
const salvage: ReceiptSalvage | null = aborted
|
|
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
|
|
|
106
106
|
// 0.1.154 removes the short-lived generated enrich StepOptions recompute
|
|
107
107
|
// fields shipped in 0.1.153.
|
|
108
108
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
109
|
-
version: '0.1.
|
|
109
|
+
version: '0.1.207',
|
|
110
110
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
111
111
|
supportPolicy: {
|
|
112
|
-
latest: '0.1.
|
|
112
|
+
latest: '0.1.207',
|
|
113
113
|
minimumSupported: '0.1.53',
|
|
114
114
|
deprecatedBelow: '0.1.53',
|
|
115
115
|
commandMinimumSupported: [
|
|
@@ -108,9 +108,11 @@ type RuntimeApiRequest =
|
|
|
108
108
|
action: 'append_run_events';
|
|
109
109
|
playId: string;
|
|
110
110
|
events: PlayRunLedgerEvent[];
|
|
111
|
+
idempotencyKey?: string;
|
|
111
112
|
}
|
|
112
113
|
| {
|
|
113
114
|
action: 'start_inline_child_run';
|
|
115
|
+
idempotencyKey?: string;
|
|
114
116
|
playName: string;
|
|
115
117
|
runId: string;
|
|
116
118
|
parentRunId?: string | null;
|
|
@@ -127,6 +129,15 @@ type RuntimeApiRequest =
|
|
|
127
129
|
staticPipeline?: unknown;
|
|
128
130
|
source?: 'published' | 'ad_hoc' | 'draft';
|
|
129
131
|
}
|
|
132
|
+
| {
|
|
133
|
+
action: 'settle_inline_child_run';
|
|
134
|
+
parentRunId: string;
|
|
135
|
+
childRunId: string;
|
|
136
|
+
childPlayName: string;
|
|
137
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
138
|
+
result?: unknown;
|
|
139
|
+
error?: string | null;
|
|
140
|
+
}
|
|
130
141
|
| ({
|
|
131
142
|
action: 'save_results';
|
|
132
143
|
} & RuntimeSaveResults)
|
|
@@ -167,7 +178,7 @@ type RuntimeApiRequest =
|
|
|
167
178
|
workflowId?: string;
|
|
168
179
|
runId?: string;
|
|
169
180
|
maxCreditsPerRun?: number | null;
|
|
170
|
-
finalItem
|
|
181
|
+
finalItem?: ComputeBillingItem;
|
|
171
182
|
}
|
|
172
183
|
| ({
|
|
173
184
|
action: 'rate_state_acquire';
|
|
@@ -322,6 +333,7 @@ export type WorkerRuntimeApiContext = {
|
|
|
322
333
|
vercelProtectionBypassToken?: string | null;
|
|
323
334
|
runtimeTestFaultHeader?: string | null;
|
|
324
335
|
fetch?: typeof fetch;
|
|
336
|
+
requestTimeoutMs?: number | null;
|
|
325
337
|
};
|
|
326
338
|
|
|
327
339
|
const APP_RUNTIME_API_RETRY_DELAYS_MS = [100, 250, 500, 1_000] as const;
|
|
@@ -331,6 +343,7 @@ const APP_RUNTIME_API_RECEIPT_RETRY_DELAYS_MS = [
|
|
|
331
343
|
const APP_RUNTIME_API_APPEND_RETRY_DELAYS_MS = [
|
|
332
344
|
100, 250, 500, 1_000, 2_000, 4_000, 8_000,
|
|
333
345
|
] as const;
|
|
346
|
+
const APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
334
347
|
const RUN_STATUS_LEDGER_SNAPSHOT_CACHE_LIMIT = 1_000;
|
|
335
348
|
const runStatusLedgerSnapshots = new Map<string, PlayRunLedgerSnapshot>();
|
|
336
349
|
// Positional log cursor per run: count of liveLogs lines already forwarded to
|
|
@@ -418,11 +431,13 @@ function summarizeAppRuntimeErrorBody(body: string): string {
|
|
|
418
431
|
? record.debug_error
|
|
419
432
|
: typeof record.details === 'string'
|
|
420
433
|
? record.details
|
|
421
|
-
: typeof record.
|
|
422
|
-
? record.
|
|
423
|
-
: typeof record.
|
|
424
|
-
? record.
|
|
425
|
-
:
|
|
434
|
+
: typeof record.detail === 'string'
|
|
435
|
+
? record.detail
|
|
436
|
+
: typeof record.error === 'string'
|
|
437
|
+
? record.error
|
|
438
|
+
: typeof record.message === 'string'
|
|
439
|
+
? record.message
|
|
440
|
+
: null;
|
|
426
441
|
if (message?.trim()) {
|
|
427
442
|
return message.trim();
|
|
428
443
|
}
|
|
@@ -447,6 +462,13 @@ function isRetryableAppRuntimeFetchError(input: {
|
|
|
447
462
|
if (!isRetryableAppRuntimeAction(input.action)) {
|
|
448
463
|
return false;
|
|
449
464
|
}
|
|
465
|
+
// The only signal on the request is AbortSignal.timeout(requestTimeoutMs), so
|
|
466
|
+
// any abort here is our own per-request deadline firing against a slow app
|
|
467
|
+
// runtime API (common under heavy map load) — always safe to retry across the
|
|
468
|
+
// backoff ladder rather than failing the tool call on the first slow write.
|
|
469
|
+
if (isRequestTimeoutAbort(input.error)) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
450
472
|
const message =
|
|
451
473
|
input.error instanceof Error ? input.error.message : String(input.error);
|
|
452
474
|
return /fetch failed|connection (terminated|timeout|timed out|closed|reset)|econnreset|etimedout|econnrefused/i.test(
|
|
@@ -454,6 +476,24 @@ function isRetryableAppRuntimeFetchError(input: {
|
|
|
454
476
|
);
|
|
455
477
|
}
|
|
456
478
|
|
|
479
|
+
function isRequestTimeoutAbort(error: unknown): boolean {
|
|
480
|
+
if (!error || typeof error !== 'object') {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
// AbortSignal.timeout() — the only signal on the request — rejects with a
|
|
484
|
+
// DOMException named TimeoutError. Match that (and its message across
|
|
485
|
+
// runtimes) but NOT a bare AbortError / "the operation was aborted", so a
|
|
486
|
+
// genuine run cancellation still propagates instead of being retried.
|
|
487
|
+
if ((error as { name?: unknown }).name === 'TimeoutError') {
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
const message =
|
|
491
|
+
error instanceof Error
|
|
492
|
+
? error.message
|
|
493
|
+
: String((error as { message?: unknown }).message ?? '');
|
|
494
|
+
return /operation was aborted due to timeout|signal timed out/i.test(message);
|
|
495
|
+
}
|
|
496
|
+
|
|
457
497
|
function isRetryableAppRuntimeAction(
|
|
458
498
|
action: RuntimeApiRequest['action'],
|
|
459
499
|
): boolean {
|
|
@@ -484,7 +524,8 @@ function isRetryableAppRuntimeAction(
|
|
|
484
524
|
action === 'release_runtime_step_receipt' ||
|
|
485
525
|
action === 'save_results' ||
|
|
486
526
|
action === 'skip_runtime_step_receipt' ||
|
|
487
|
-
action === 'start_inline_child_run'
|
|
527
|
+
action === 'start_inline_child_run' ||
|
|
528
|
+
action === 'settle_inline_child_run'
|
|
488
529
|
);
|
|
489
530
|
}
|
|
490
531
|
|
|
@@ -560,6 +601,27 @@ function sleep(ms: number): Promise<void> {
|
|
|
560
601
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
561
602
|
}
|
|
562
603
|
|
|
604
|
+
export class AppRuntimeApiTransportError extends Error {
|
|
605
|
+
readonly action: RuntimeApiRequest['action'];
|
|
606
|
+
readonly attempts: number;
|
|
607
|
+
|
|
608
|
+
constructor(input: {
|
|
609
|
+
action: RuntimeApiRequest['action'];
|
|
610
|
+
attempts: number;
|
|
611
|
+
cause: unknown;
|
|
612
|
+
}) {
|
|
613
|
+
const causeMessage =
|
|
614
|
+
input.cause instanceof Error ? input.cause.message : String(input.cause);
|
|
615
|
+
super(
|
|
616
|
+
`App runtime API transport exhausted action=${input.action} attempts=${input.attempts}: ${causeMessage}`,
|
|
617
|
+
{ cause: input.cause },
|
|
618
|
+
);
|
|
619
|
+
this.name = 'AppRuntimeApiTransportError';
|
|
620
|
+
this.action = input.action;
|
|
621
|
+
this.attempts = input.attempts;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
563
625
|
function resolveAppRuntimeApiUrl(context: WorkerRuntimeApiContext): string {
|
|
564
626
|
const baseUrl = context.baseUrl.trim().replace(/\/$/, '');
|
|
565
627
|
return `${baseUrl}${PLAY_RUNTIME_API_COMPAT_PATH}`;
|
|
@@ -585,6 +647,12 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
585
647
|
});
|
|
586
648
|
|
|
587
649
|
const maxAttempts = appRuntimeMaxAttempts(body.action);
|
|
650
|
+
const requestTimeoutMs = Math.max(
|
|
651
|
+
1,
|
|
652
|
+
Math.floor(
|
|
653
|
+
context.requestTimeoutMs ?? APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS,
|
|
654
|
+
),
|
|
655
|
+
);
|
|
588
656
|
|
|
589
657
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
590
658
|
let response: Response;
|
|
@@ -604,6 +672,7 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
604
672
|
: {}),
|
|
605
673
|
},
|
|
606
674
|
body: JSON.stringify(body),
|
|
675
|
+
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
607
676
|
});
|
|
608
677
|
} catch (error) {
|
|
609
678
|
if (
|
|
@@ -613,11 +682,11 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
613
682
|
await sleep(appRuntimeRetryDelayMs(body.action, attempt));
|
|
614
683
|
continue;
|
|
615
684
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
);
|
|
685
|
+
throw new AppRuntimeApiTransportError({
|
|
686
|
+
action: body.action,
|
|
687
|
+
attempts: attempt,
|
|
688
|
+
cause: error,
|
|
689
|
+
});
|
|
621
690
|
}
|
|
622
691
|
if (response.ok) {
|
|
623
692
|
return (await response.json()) as TResponse;
|
|
@@ -912,7 +981,11 @@ async function updateRunStatusViaAppRuntimeUnlocked(
|
|
|
912
981
|
|
|
913
982
|
export async function appendRunEventsViaAppRuntime(
|
|
914
983
|
context: WorkerRuntimeApiContext,
|
|
915
|
-
input: {
|
|
984
|
+
input: {
|
|
985
|
+
playId: string;
|
|
986
|
+
events: PlayRunLedgerEvent[];
|
|
987
|
+
idempotencyKey?: string;
|
|
988
|
+
},
|
|
916
989
|
): Promise<void> {
|
|
917
990
|
await postAppRuntimeApi<{ ok: true }>(context, {
|
|
918
991
|
action: 'append_run_events',
|
|
@@ -938,6 +1011,7 @@ export async function startRunViaAppRuntime(
|
|
|
938
1011
|
maxCreditsPerRun?: number | null;
|
|
939
1012
|
staticPipeline?: unknown;
|
|
940
1013
|
source?: 'published' | 'ad_hoc' | 'draft';
|
|
1014
|
+
idempotencyKey?: string;
|
|
941
1015
|
},
|
|
942
1016
|
): Promise<void> {
|
|
943
1017
|
await postAppRuntimeApi<{ ok: true }>(context, {
|
|
@@ -946,6 +1020,23 @@ export async function startRunViaAppRuntime(
|
|
|
946
1020
|
});
|
|
947
1021
|
}
|
|
948
1022
|
|
|
1023
|
+
export async function settleInlineChildRunViaAppRuntime(
|
|
1024
|
+
context: WorkerRuntimeApiContext,
|
|
1025
|
+
input: {
|
|
1026
|
+
parentRunId: string;
|
|
1027
|
+
childRunId: string;
|
|
1028
|
+
childPlayName: string;
|
|
1029
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
1030
|
+
result?: unknown;
|
|
1031
|
+
error?: string | null;
|
|
1032
|
+
},
|
|
1033
|
+
): Promise<void> {
|
|
1034
|
+
await postAppRuntimeApi<{ ok: true }>(context, {
|
|
1035
|
+
action: 'settle_inline_child_run',
|
|
1036
|
+
...input,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
949
1040
|
export async function saveResultsViaAppRuntime(
|
|
950
1041
|
context: WorkerRuntimeApiContext,
|
|
951
1042
|
input: RuntimeSaveResults,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
flattenStaticPipeline,
|
|
3
|
+
type PlayStaticPipeline,
|
|
4
|
+
} from '../plays/static-pipeline';
|
|
5
|
+
|
|
6
|
+
export type ChildExecutionStrategy = 'inline' | 'scheduled';
|
|
7
|
+
|
|
8
|
+
export type ChildExecutionDecision = {
|
|
9
|
+
strategy: ChildExecutionStrategy;
|
|
10
|
+
reason:
|
|
11
|
+
| 'scalar_child'
|
|
12
|
+
| 'missing_static_contract'
|
|
13
|
+
| 'dataset_child'
|
|
14
|
+
| 'event_wait_child'
|
|
15
|
+
| 'explicit_timeout';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Choose execution placement from the child's authored contract. Scalar child
|
|
20
|
+
* plays are ordinary function composition. Dataset and event-wait children own
|
|
21
|
+
* independent durable state, so they remain scheduler jobs.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveChildExecutionStrategy(input: {
|
|
24
|
+
pipeline: PlayStaticPipeline | null | undefined;
|
|
25
|
+
timeoutMs?: number | null;
|
|
26
|
+
}): ChildExecutionDecision {
|
|
27
|
+
if (input.timeoutMs != null) {
|
|
28
|
+
return { strategy: 'scheduled', reason: 'explicit_timeout' };
|
|
29
|
+
}
|
|
30
|
+
if (!input.pipeline) {
|
|
31
|
+
return { strategy: 'scheduled', reason: 'missing_static_contract' };
|
|
32
|
+
}
|
|
33
|
+
const substeps = flattenStaticPipeline(input.pipeline);
|
|
34
|
+
if (
|
|
35
|
+
input.pipeline.tableNamespace ||
|
|
36
|
+
input.pipeline.sheetContract ||
|
|
37
|
+
substeps.some((step) => step.type === 'dataset' || step.type === 'csv')
|
|
38
|
+
) {
|
|
39
|
+
return { strategy: 'scheduled', reason: 'dataset_child' };
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
substeps.some(
|
|
43
|
+
(step) =>
|
|
44
|
+
step.type === 'tool' &&
|
|
45
|
+
(step.isEventWait === true || step.toolId === 'test_wait_for_event'),
|
|
46
|
+
)
|
|
47
|
+
) {
|
|
48
|
+
return { strategy: 'scheduled', reason: 'event_wait_child' };
|
|
49
|
+
}
|
|
50
|
+
return { strategy: 'inline', reason: 'scalar_child' };
|
|
51
|
+
}
|
|
@@ -56,6 +56,22 @@ export interface ChildRunIdInputs {
|
|
|
56
56
|
graphHash?: string | null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* A failed durable runPlay receipt must launch a fresh child attempt while
|
|
61
|
+
* ordinary submit retries keep attaching to the original child. The receipt
|
|
62
|
+
* lease is the attempt fence, so it is the only extra identity required.
|
|
63
|
+
*/
|
|
64
|
+
export function childSubmitIdempotencyKey(input: {
|
|
65
|
+
receiptKey: string;
|
|
66
|
+
wasFailed: boolean;
|
|
67
|
+
leaseId?: string | null;
|
|
68
|
+
}): string {
|
|
69
|
+
const leaseId = input.leaseId?.trim();
|
|
70
|
+
return input.wasFailed && leaseId
|
|
71
|
+
? `${input.receiptKey}:claim:${leaseId}`
|
|
72
|
+
: input.receiptKey;
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
60
76
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
61
77
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { RunExecutionScope } from './run-execution-scope';
|
|
2
|
+
|
|
3
|
+
export type ChildRunPlacement = 'inline' | 'scheduled';
|
|
4
|
+
|
|
5
|
+
export interface ChildRunLifecycleMetadata {
|
|
6
|
+
placement: ChildRunPlacement;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ChildRunLifecycleAdapter<
|
|
10
|
+
Metadata extends ChildRunLifecycleMetadata,
|
|
11
|
+
Result,
|
|
12
|
+
> {
|
|
13
|
+
open(scope: RunExecutionScope, metadata: Metadata): Promise<void> | void;
|
|
14
|
+
started(scope: RunExecutionScope, metadata: Metadata): Promise<void> | void;
|
|
15
|
+
complete(
|
|
16
|
+
scope: RunExecutionScope,
|
|
17
|
+
metadata: Metadata,
|
|
18
|
+
result: Result,
|
|
19
|
+
): Promise<void> | void;
|
|
20
|
+
fail(
|
|
21
|
+
scope: RunExecutionScope,
|
|
22
|
+
metadata: Metadata,
|
|
23
|
+
error: unknown,
|
|
24
|
+
): Promise<void> | void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ExecuteChildRunLifecycleInput<
|
|
28
|
+
Metadata extends ChildRunLifecycleMetadata,
|
|
29
|
+
Result,
|
|
30
|
+
> {
|
|
31
|
+
scope: RunExecutionScope;
|
|
32
|
+
metadata: Metadata;
|
|
33
|
+
adapter: ChildRunLifecycleAdapter<Metadata, Result>;
|
|
34
|
+
execute(
|
|
35
|
+
scope: RunExecutionScope,
|
|
36
|
+
metadata: Metadata,
|
|
37
|
+
): Promise<Result> | Result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Projects one logical child execution through its lifecycle. Placement is
|
|
42
|
+
* descriptive metadata; inline and scheduled children use the same protocol.
|
|
43
|
+
*/
|
|
44
|
+
export async function executeChildRunLifecycle<
|
|
45
|
+
Metadata extends ChildRunLifecycleMetadata,
|
|
46
|
+
Result,
|
|
47
|
+
>(input: ExecuteChildRunLifecycleInput<Metadata, Result>): Promise<Result> {
|
|
48
|
+
const { adapter, execute, metadata, scope } = input;
|
|
49
|
+
|
|
50
|
+
let result: Result;
|
|
51
|
+
try {
|
|
52
|
+
await adapter.open(scope, metadata);
|
|
53
|
+
await adapter.started(scope, metadata);
|
|
54
|
+
result = await execute(scope, metadata);
|
|
55
|
+
} catch (executionError) {
|
|
56
|
+
try {
|
|
57
|
+
await adapter.fail(scope, metadata, executionError);
|
|
58
|
+
} catch (failureProjectionError) {
|
|
59
|
+
throw new AggregateError(
|
|
60
|
+
[executionError, failureProjectionError],
|
|
61
|
+
'Child run execution and failure projection both failed',
|
|
62
|
+
{ cause: executionError },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
throw executionError;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await adapter.complete(scope, metadata, result);
|
|
69
|
+
return result;
|
|
70
|
+
}
|