deepline 0.1.181 → 0.1.183
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/child-play-await.ts +203 -6
- package/dist/bundling-sources/apps/play-runner-workers/src/child-play-submit.ts +8 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +633 -107
- package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +3 -2
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +1932 -1838
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +116 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +542 -78
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +124 -12
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +292 -11
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +519 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +2381 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +18 -5
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +130 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +39 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +2 -0
- package/dist/bundling-sources/sdk/src/client.ts +48 -5
- package/dist/bundling-sources/sdk/src/http.ts +23 -8
- package/dist/bundling-sources/sdk/src/play.ts +200 -26
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +56 -3
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +132 -10
- package/dist/bundling-sources/shared_libs/play-runtime/auth-scope-resolver.ts +92 -0
- package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -4
- package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +101 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1857 -314
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +106 -6
- package/dist/bundling-sources/shared_libs/play-runtime/dedup-backend.ts +0 -0
- package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +116 -16
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +186 -20
- package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +133 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +245 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -5
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +14 -0
- package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +52 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/query-result-dataset.ts +120 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +124 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +48 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +158 -15
- package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +45 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2037 -262
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +139 -17
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
- package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
- package/dist/cli/index.js +85 -23
- package/dist/cli/index.mjs +85 -23
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +274 -35
- package/dist/index.mjs +274 -35
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/package.json +1 -1
|
@@ -22,7 +22,10 @@ import {
|
|
|
22
22
|
type WorkflowRunner,
|
|
23
23
|
} from '@cloudflare/dynamic-workflows';
|
|
24
24
|
import type { ExecutionPlan } from '../../../shared_libs/play-runtime/execution-plan';
|
|
25
|
+
import { buildChildRunId as buildSharedChildRunId } from '../../../shared_libs/play-runtime/child-run-id';
|
|
25
26
|
import type { PlayCallGovernanceSnapshot } from '../../../shared_libs/play-runtime/scheduler-backend';
|
|
27
|
+
import type { RuntimeTestPolicyOverrides } from '../../../shared_libs/play-runtime/test-runtime-seams';
|
|
28
|
+
import { DYNAMIC_PLAY_WORKER_ARTIFACT_VERSION } from '../../../shared_libs/play-runtime/dynamic-worker-version';
|
|
26
29
|
import {
|
|
27
30
|
DB_SESSION_DEFAULT_TTL_SECONDS,
|
|
28
31
|
createDbSessionResponseSchema,
|
|
@@ -54,6 +57,10 @@ import {
|
|
|
54
57
|
COORDINATOR_INTERNAL_TOKEN_HEADER,
|
|
55
58
|
COORDINATOR_RUN_SCOPE_HEADER,
|
|
56
59
|
} from '../../../shared_libs/play-runtime/coordinator-headers';
|
|
60
|
+
import {
|
|
61
|
+
PLAY_RUNTIME_CONTRACT,
|
|
62
|
+
PLAY_RUNTIME_CONTRACT_HEADER,
|
|
63
|
+
} from '../../../shared_libs/play-runtime/runtime-contract';
|
|
57
64
|
import {
|
|
58
65
|
decideWorkflowPlatformRetry,
|
|
59
66
|
PLATFORM_DEPLOY_WORKFLOW_RETRY_LIMIT,
|
|
@@ -85,6 +92,8 @@ export type PlayWorkflowParams = {
|
|
|
85
92
|
artifactHash: string;
|
|
86
93
|
graphHash: string;
|
|
87
94
|
input: Record<string, unknown>;
|
|
95
|
+
force?: boolean;
|
|
96
|
+
runAttempt?: number;
|
|
88
97
|
inputFile?: {
|
|
89
98
|
name?: string;
|
|
90
99
|
r2Key?: string;
|
|
@@ -126,6 +135,8 @@ export type PlayWorkflowParams = {
|
|
|
126
135
|
totalRows?: number;
|
|
127
136
|
coordinatorUrl?: string | null;
|
|
128
137
|
coordinatorInternalToken?: string | null;
|
|
138
|
+
runtimeTestFaultHeader?: string | null;
|
|
139
|
+
testPolicyOverrides?: RuntimeTestPolicyOverrides | null;
|
|
129
140
|
submittedAt?: number | null;
|
|
130
141
|
};
|
|
131
142
|
|
|
@@ -649,6 +660,9 @@ const SUBMIT_INITIAL_STATE_POLL_MS = 50;
|
|
|
649
660
|
const WORKFLOW_RETRY_STATE_TTL_MS = 60 * 60 * 1000;
|
|
650
661
|
const WORKFLOW_SUBMIT_DUPLICATE_WAIT_MS = 55_000;
|
|
651
662
|
const WORKFLOW_SUBMIT_DUPLICATE_POLL_MS = 250;
|
|
663
|
+
const CHILD_WORKFLOW_SUBMIT_RETRY_DELAYS_MS = [
|
|
664
|
+
250, 750, 1500, 3000, 5000, 10000,
|
|
665
|
+
] as const;
|
|
652
666
|
const COORDINATOR_WORKER_MEMORY_LIMIT_MESSAGE =
|
|
653
667
|
'Worker memory limit hit before the run could finish. For CSV enrich, use --output or --in-place for automatic batches; otherwise rerun smaller --rows ranges.';
|
|
654
668
|
|
|
@@ -668,7 +682,7 @@ function buildDynamicWorkflowMetadata(
|
|
|
668
682
|
graphHash: params.graphHash,
|
|
669
683
|
artifactStorageKey: params.artifactStorageKey,
|
|
670
684
|
artifactHash: params.artifactHash ?? null,
|
|
671
|
-
dynamicWorkerCode:
|
|
685
|
+
dynamicWorkerCode: null,
|
|
672
686
|
integrationMode: normalizeIntegrationMode(params.integrationMode),
|
|
673
687
|
packagedFiles: normalizePackagedFiles(params.packagedFiles),
|
|
674
688
|
};
|
|
@@ -746,10 +760,11 @@ async function createDynamicWorkflowInstance(input: {
|
|
|
746
760
|
env: CoordinatorEnv;
|
|
747
761
|
id: string;
|
|
748
762
|
params: PlayWorkflowParams;
|
|
763
|
+
metadataParams?: PlayWorkflowParams;
|
|
749
764
|
}): Promise<WorkflowInstance> {
|
|
750
765
|
const workflow = createDispatcherWorkflowBinding(
|
|
751
766
|
() => input.env.PLAY_WORKFLOW as unknown as Workflow,
|
|
752
|
-
buildDynamicWorkflowMetadata(input.params),
|
|
767
|
+
buildDynamicWorkflowMetadata(input.metadataParams ?? input.params),
|
|
753
768
|
);
|
|
754
769
|
return workflow.create({
|
|
755
770
|
id: input.id,
|
|
@@ -1127,6 +1142,96 @@ async function hydrateWorkflowDbSessions(input: {
|
|
|
1127
1142
|
};
|
|
1128
1143
|
}
|
|
1129
1144
|
|
|
1145
|
+
async function hydrateWorkflowLaunchParams(input: {
|
|
1146
|
+
env: CoordinatorEnv;
|
|
1147
|
+
event: unknown;
|
|
1148
|
+
trace: CoordinatorPerfTraceSink;
|
|
1149
|
+
}): Promise<unknown> {
|
|
1150
|
+
if (!isRecord(input.event)) return input.event;
|
|
1151
|
+
const payload = isRecord(input.event.payload) ? input.event.payload : null;
|
|
1152
|
+
const envelopeParams = isRecord(payload?.params) ? payload.params : null;
|
|
1153
|
+
const params = envelopeParams ?? payload;
|
|
1154
|
+
if (
|
|
1155
|
+
!payload ||
|
|
1156
|
+
!params ||
|
|
1157
|
+
(!isRecord(params.workflowParams) && !isRecord(params.workflowParamsRef))
|
|
1158
|
+
) {
|
|
1159
|
+
return input.event;
|
|
1160
|
+
}
|
|
1161
|
+
const startedAt = Date.now();
|
|
1162
|
+
const hydrated = await hydrateWorkflowRetryParams({
|
|
1163
|
+
env: input.env,
|
|
1164
|
+
params: params.workflowParams,
|
|
1165
|
+
paramsRef: params.workflowParamsRef,
|
|
1166
|
+
});
|
|
1167
|
+
if (!hydrated) return input.event;
|
|
1168
|
+
const submittedAt =
|
|
1169
|
+
typeof params.submittedAt === 'number' &&
|
|
1170
|
+
Number.isFinite(params.submittedAt)
|
|
1171
|
+
? params.submittedAt
|
|
1172
|
+
: hydrated.submittedAt;
|
|
1173
|
+
const hydratedParams = {
|
|
1174
|
+
...hydrated,
|
|
1175
|
+
submittedAt,
|
|
1176
|
+
};
|
|
1177
|
+
input.trace({
|
|
1178
|
+
runId:
|
|
1179
|
+
typeof hydratedParams.runId === 'string'
|
|
1180
|
+
? hydratedParams.runId
|
|
1181
|
+
: 'unknown-workflow-run',
|
|
1182
|
+
phase: 'coordinator.workflow_launch_params_hydrated',
|
|
1183
|
+
ms: Date.now() - startedAt,
|
|
1184
|
+
graphHash:
|
|
1185
|
+
typeof hydratedParams.graphHash === 'string'
|
|
1186
|
+
? hydratedParams.graphHash
|
|
1187
|
+
: null,
|
|
1188
|
+
extra: {
|
|
1189
|
+
paramsBytes: isRecord(params.workflowParamsRef)
|
|
1190
|
+
? typeof params.workflowParamsRef.bytes === 'number'
|
|
1191
|
+
? params.workflowParamsRef.bytes
|
|
1192
|
+
: null
|
|
1193
|
+
: jsonByteLength(params.workflowParams),
|
|
1194
|
+
paramsStorage: isRecord(params.workflowParamsRef) ? 'r2' : 'inline',
|
|
1195
|
+
},
|
|
1196
|
+
});
|
|
1197
|
+
return {
|
|
1198
|
+
...input.event,
|
|
1199
|
+
payload: envelopeParams
|
|
1200
|
+
? {
|
|
1201
|
+
...payload,
|
|
1202
|
+
params: hydratedParams,
|
|
1203
|
+
}
|
|
1204
|
+
: {
|
|
1205
|
+
...payload,
|
|
1206
|
+
...hydratedParams,
|
|
1207
|
+
},
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function workflowCreateParams(
|
|
1212
|
+
params: PlayWorkflowParams,
|
|
1213
|
+
launchState: {
|
|
1214
|
+
params?: PlayWorkflowParams;
|
|
1215
|
+
paramsRef?: WorkflowRetryParamsRef;
|
|
1216
|
+
},
|
|
1217
|
+
): PlayWorkflowParams {
|
|
1218
|
+
const launchParams = launchState.params ?? params;
|
|
1219
|
+
const envelope: Record<string, unknown> = {
|
|
1220
|
+
runId: params.runId,
|
|
1221
|
+
playName: params.playName,
|
|
1222
|
+
graphHash: params.graphHash,
|
|
1223
|
+
artifactStorageKey: params.artifactStorageKey,
|
|
1224
|
+
artifactHash: params.artifactHash,
|
|
1225
|
+
submittedAt: params.submittedAt,
|
|
1226
|
+
};
|
|
1227
|
+
if (launchState.paramsRef) {
|
|
1228
|
+
envelope.workflowParamsRef = launchState.paramsRef;
|
|
1229
|
+
} else {
|
|
1230
|
+
envelope.workflowParams = launchParams;
|
|
1231
|
+
}
|
|
1232
|
+
return envelope as unknown as PlayWorkflowParams;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1130
1235
|
function sleep(ms: number): Promise<void> {
|
|
1131
1236
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1132
1237
|
}
|
|
@@ -1209,7 +1314,7 @@ async function markWorkflowRuntimeFailure(input: {
|
|
|
1209
1314
|
} satisfies PlayRunLedgerEvent,
|
|
1210
1315
|
],
|
|
1211
1316
|
});
|
|
1212
|
-
const url = `${baseUrl.replace(/\/$/, '')}/api/v2/
|
|
1317
|
+
const url = `${baseUrl.replace(/\/$/, '')}/api/v2/internal/play-runtime`;
|
|
1213
1318
|
const backoffMs = [200, 500, 1500];
|
|
1214
1319
|
let lastError: unknown = null;
|
|
1215
1320
|
for (let attempt = 0; attempt <= backoffMs.length; attempt += 1) {
|
|
@@ -1240,6 +1345,47 @@ async function markWorkflowRuntimeFailure(input: {
|
|
|
1240
1345
|
});
|
|
1241
1346
|
}
|
|
1242
1347
|
|
|
1348
|
+
async function appendWorkflowChildCompletedRunEvent(input: {
|
|
1349
|
+
env: CoordinatorEnv;
|
|
1350
|
+
event: unknown;
|
|
1351
|
+
result: unknown;
|
|
1352
|
+
}): Promise<boolean> {
|
|
1353
|
+
const payload = readWorkflowPayload(input.event);
|
|
1354
|
+
if (!payload || !isRecord(payload.playCallGovernance)) return false;
|
|
1355
|
+
const runId = typeof payload.runId === 'string' ? payload.runId : null;
|
|
1356
|
+
const baseUrl = typeof payload.baseUrl === 'string' ? payload.baseUrl : null;
|
|
1357
|
+
const executorToken =
|
|
1358
|
+
typeof payload.executorToken === 'string' ? payload.executorToken : null;
|
|
1359
|
+
if (!runId || !baseUrl || !executorToken) return false;
|
|
1360
|
+
const headers = new Headers({
|
|
1361
|
+
authorization: `Bearer ${executorToken}`,
|
|
1362
|
+
'content-type': 'application/json',
|
|
1363
|
+
});
|
|
1364
|
+
const bypass = input.env.VERCEL_PROTECTION_BYPASS_TOKEN?.trim();
|
|
1365
|
+
if (bypass) headers.set('x-vercel-protection-bypass', bypass);
|
|
1366
|
+
const body = JSON.stringify({
|
|
1367
|
+
action: 'append_run_events',
|
|
1368
|
+
playId: runId,
|
|
1369
|
+
events: [
|
|
1370
|
+
{
|
|
1371
|
+
type: 'run.completed',
|
|
1372
|
+
runId,
|
|
1373
|
+
source: 'worker',
|
|
1374
|
+
occurredAt: Date.now(),
|
|
1375
|
+
result: input.result,
|
|
1376
|
+
} satisfies PlayRunLedgerEvent,
|
|
1377
|
+
],
|
|
1378
|
+
});
|
|
1379
|
+
const url = `${baseUrl.replace(/\/$/, '')}/api/v2/internal/play-runtime`;
|
|
1380
|
+
const response = await fetch(url, { method: 'POST', headers, body });
|
|
1381
|
+
if (!response.ok) {
|
|
1382
|
+
throw new Error(
|
|
1383
|
+
`runtime API responded ${response.status}: ${(await response.text().catch(() => '')).slice(0, 400)}`,
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
return true;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1243
1389
|
type StoredPlayArtifactPayload = {
|
|
1244
1390
|
artifact?: {
|
|
1245
1391
|
bundledCode?: string;
|
|
@@ -1253,7 +1399,10 @@ async function persistWorkflowRetryState(input: {
|
|
|
1253
1399
|
env: CoordinatorEnv;
|
|
1254
1400
|
runId: string;
|
|
1255
1401
|
params: PlayWorkflowParams;
|
|
1256
|
-
}): Promise<
|
|
1402
|
+
}): Promise<{
|
|
1403
|
+
params?: PlayWorkflowParams;
|
|
1404
|
+
paramsRef?: WorkflowRetryParamsRef;
|
|
1405
|
+
}> {
|
|
1257
1406
|
const retryParams = buildWorkflowRetryParams(input.params);
|
|
1258
1407
|
const paramsBytes = jsonByteLength(retryParams);
|
|
1259
1408
|
if (paramsBytes > WORKFLOW_RETRY_PARAMS_MAX_BYTES) {
|
|
@@ -1275,21 +1424,32 @@ async function persistWorkflowRetryState(input: {
|
|
|
1275
1424
|
runId: input.runId,
|
|
1276
1425
|
hash,
|
|
1277
1426
|
});
|
|
1427
|
+
const paramsRef: WorkflowRetryParamsRef = {
|
|
1428
|
+
storageKind: 'r2',
|
|
1429
|
+
storageKey,
|
|
1430
|
+
bytes: paramsBytes,
|
|
1431
|
+
hash,
|
|
1432
|
+
expiresAt: Date.now() + WORKFLOW_RETRY_STATE_TTL_MS,
|
|
1433
|
+
};
|
|
1278
1434
|
await input.env.PLAYS_BUCKET.put(storageKey, serialized, {
|
|
1279
1435
|
httpMetadata: { contentType: 'application/json' },
|
|
1280
1436
|
});
|
|
1281
1437
|
body = {
|
|
1282
1438
|
runId: input.runId,
|
|
1283
|
-
paramsRef
|
|
1284
|
-
storageKind: 'r2',
|
|
1285
|
-
storageKey,
|
|
1286
|
-
bytes: paramsBytes,
|
|
1287
|
-
hash,
|
|
1288
|
-
expiresAt: Date.now() + WORKFLOW_RETRY_STATE_TTL_MS,
|
|
1289
|
-
},
|
|
1439
|
+
paramsRef,
|
|
1290
1440
|
paramsBytes,
|
|
1291
1441
|
ttlMs: WORKFLOW_RETRY_STATE_TTL_MS,
|
|
1292
1442
|
};
|
|
1443
|
+
await callRunScopedControl<{ ok?: unknown }>(
|
|
1444
|
+
input.env,
|
|
1445
|
+
input.runId,
|
|
1446
|
+
'/run-retry-state-put',
|
|
1447
|
+
{
|
|
1448
|
+
method: 'POST',
|
|
1449
|
+
body: JSON.stringify(body),
|
|
1450
|
+
},
|
|
1451
|
+
);
|
|
1452
|
+
return { paramsRef };
|
|
1293
1453
|
} else {
|
|
1294
1454
|
body = {
|
|
1295
1455
|
runId: input.runId,
|
|
@@ -1307,6 +1467,7 @@ async function persistWorkflowRetryState(input: {
|
|
|
1307
1467
|
body: JSON.stringify(body),
|
|
1308
1468
|
},
|
|
1309
1469
|
);
|
|
1470
|
+
return { params: retryParams };
|
|
1310
1471
|
}
|
|
1311
1472
|
|
|
1312
1473
|
async function persistWorkflowLaunchState(input: {
|
|
@@ -1318,14 +1479,16 @@ async function persistWorkflowLaunchState(input: {
|
|
|
1318
1479
|
retryExpiresAt?: number;
|
|
1319
1480
|
dbSessionsExpiresAt?: number;
|
|
1320
1481
|
sessionCount?: number;
|
|
1482
|
+
params?: PlayWorkflowParams;
|
|
1483
|
+
paramsRef?: WorkflowRetryParamsRef;
|
|
1321
1484
|
}> {
|
|
1322
1485
|
if (input.sessions.length === 0) {
|
|
1323
|
-
await persistWorkflowRetryState({
|
|
1486
|
+
const state = await persistWorkflowRetryState({
|
|
1324
1487
|
env: input.env,
|
|
1325
1488
|
runId: input.runId,
|
|
1326
1489
|
params: input.params,
|
|
1327
1490
|
});
|
|
1328
|
-
return
|
|
1491
|
+
return state;
|
|
1329
1492
|
}
|
|
1330
1493
|
const retryParams = buildWorkflowRetryParams(input.params);
|
|
1331
1494
|
const paramsBytes = jsonByteLength(retryParams);
|
|
@@ -1345,18 +1508,19 @@ async function persistWorkflowLaunchState(input: {
|
|
|
1345
1508
|
runId: input.runId,
|
|
1346
1509
|
hash,
|
|
1347
1510
|
});
|
|
1511
|
+
const paramsRef: WorkflowRetryParamsRef = {
|
|
1512
|
+
storageKind: 'r2',
|
|
1513
|
+
storageKey,
|
|
1514
|
+
bytes: paramsBytes,
|
|
1515
|
+
hash,
|
|
1516
|
+
expiresAt: Date.now() + WORKFLOW_RETRY_STATE_TTL_MS,
|
|
1517
|
+
};
|
|
1348
1518
|
await input.env.PLAYS_BUCKET.put(storageKey, serialized, {
|
|
1349
1519
|
httpMetadata: { contentType: 'application/json' },
|
|
1350
1520
|
});
|
|
1351
1521
|
body = {
|
|
1352
1522
|
runId: input.runId,
|
|
1353
|
-
paramsRef
|
|
1354
|
-
storageKind: 'r2',
|
|
1355
|
-
storageKey,
|
|
1356
|
-
bytes: paramsBytes,
|
|
1357
|
-
hash,
|
|
1358
|
-
expiresAt: Date.now() + WORKFLOW_RETRY_STATE_TTL_MS,
|
|
1359
|
-
},
|
|
1523
|
+
paramsRef,
|
|
1360
1524
|
paramsBytes,
|
|
1361
1525
|
sessions: input.sessions,
|
|
1362
1526
|
retryTtlMs: WORKFLOW_RETRY_STATE_TTL_MS,
|
|
@@ -1394,24 +1558,22 @@ async function persistWorkflowLaunchState(input: {
|
|
|
1394
1558
|
typeof response.sessionCount === 'number'
|
|
1395
1559
|
? response.sessionCount
|
|
1396
1560
|
: undefined,
|
|
1561
|
+
params: body.params,
|
|
1562
|
+
paramsRef: body.paramsRef,
|
|
1397
1563
|
};
|
|
1398
1564
|
}
|
|
1399
1565
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
paramsRef
|
|
1404
|
-
}): Promise<PlayWorkflowParams | null> {
|
|
1405
|
-
if (isRecord(input.params)) {
|
|
1406
|
-
return input.params as PlayWorkflowParams;
|
|
1407
|
-
}
|
|
1408
|
-
if (!isRecord(input.paramsRef)) {
|
|
1566
|
+
function parseWorkflowRetryParamsRef(
|
|
1567
|
+
paramsRef: unknown,
|
|
1568
|
+
): WorkflowRetryParamsRef | null {
|
|
1569
|
+
if (!isRecord(paramsRef)) {
|
|
1409
1570
|
return null;
|
|
1410
1571
|
}
|
|
1411
|
-
const storageKind =
|
|
1412
|
-
const storageKey =
|
|
1413
|
-
const expectedBytes =
|
|
1414
|
-
const expectedHash =
|
|
1572
|
+
const storageKind = paramsRef.storageKind;
|
|
1573
|
+
const storageKey = paramsRef.storageKey;
|
|
1574
|
+
const expectedBytes = paramsRef.bytes;
|
|
1575
|
+
const expectedHash = paramsRef.hash;
|
|
1576
|
+
const expiresAt = paramsRef.expiresAt;
|
|
1415
1577
|
if (
|
|
1416
1578
|
storageKind !== 'r2' ||
|
|
1417
1579
|
typeof storageKey !== 'string' ||
|
|
@@ -1419,10 +1581,37 @@ async function hydrateWorkflowRetryParams(input: {
|
|
|
1419
1581
|
typeof expectedBytes !== 'number' ||
|
|
1420
1582
|
!Number.isFinite(expectedBytes) ||
|
|
1421
1583
|
typeof expectedHash !== 'string' ||
|
|
1422
|
-
!expectedHash
|
|
1584
|
+
!expectedHash ||
|
|
1585
|
+
typeof expiresAt !== 'number' ||
|
|
1586
|
+
!Number.isFinite(expiresAt)
|
|
1423
1587
|
) {
|
|
1424
1588
|
throw new Error('Invalid workflow retry params reference.');
|
|
1425
1589
|
}
|
|
1590
|
+
return {
|
|
1591
|
+
storageKind,
|
|
1592
|
+
storageKey,
|
|
1593
|
+
bytes: expectedBytes,
|
|
1594
|
+
hash: expectedHash,
|
|
1595
|
+
expiresAt,
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
async function hydrateWorkflowRetryParams(input: {
|
|
1600
|
+
env: CoordinatorEnv;
|
|
1601
|
+
params: unknown;
|
|
1602
|
+
paramsRef: unknown;
|
|
1603
|
+
}): Promise<PlayWorkflowParams | null> {
|
|
1604
|
+
if (isRecord(input.params)) {
|
|
1605
|
+
return input.params as PlayWorkflowParams;
|
|
1606
|
+
}
|
|
1607
|
+
const paramsRef = parseWorkflowRetryParamsRef(input.paramsRef);
|
|
1608
|
+
if (!paramsRef) return null;
|
|
1609
|
+
const storageKey = paramsRef.storageKey;
|
|
1610
|
+
const expectedBytes = paramsRef.bytes;
|
|
1611
|
+
const expectedHash = paramsRef.hash;
|
|
1612
|
+
if (paramsRef.expiresAt <= Date.now()) {
|
|
1613
|
+
throw new Error('Workflow retry params reference expired.');
|
|
1614
|
+
}
|
|
1426
1615
|
const object = await input.env.PLAYS_BUCKET.get(storageKey);
|
|
1427
1616
|
if (!object) {
|
|
1428
1617
|
throw new Error(`Workflow retry params missing from R2: ${storageKey}`);
|
|
@@ -1470,6 +1659,7 @@ async function claimWorkflowPlatformRetry(input: {
|
|
|
1470
1659
|
claimed: boolean;
|
|
1471
1660
|
attempts: number;
|
|
1472
1661
|
params: PlayWorkflowParams | null;
|
|
1662
|
+
paramsRef?: WorkflowRetryParamsRef;
|
|
1473
1663
|
}> {
|
|
1474
1664
|
const body = await callRunScopedControl<{
|
|
1475
1665
|
claimed?: unknown;
|
|
@@ -1488,10 +1678,12 @@ async function claimWorkflowPlatformRetry(input: {
|
|
|
1488
1678
|
params: body.params,
|
|
1489
1679
|
paramsRef: body.paramsRef,
|
|
1490
1680
|
});
|
|
1681
|
+
const paramsRef = parseWorkflowRetryParamsRef(body.paramsRef) ?? undefined;
|
|
1491
1682
|
return {
|
|
1492
1683
|
claimed: body.claimed === true,
|
|
1493
1684
|
attempts: typeof body.attempts === 'number' ? body.attempts : 0,
|
|
1494
1685
|
params,
|
|
1686
|
+
paramsRef,
|
|
1495
1687
|
};
|
|
1496
1688
|
}
|
|
1497
1689
|
|
|
@@ -1502,6 +1694,12 @@ function workflowRetryInstanceId(runId: string, attempt: number): string {
|
|
|
1502
1694
|
return `${base.slice(0, maxBaseLength)}-${suffix}`;
|
|
1503
1695
|
}
|
|
1504
1696
|
|
|
1697
|
+
function normalizeWorkflowRunAttempt(value: unknown): number {
|
|
1698
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
1699
|
+
? Math.max(0, Math.floor(value))
|
|
1700
|
+
: 0;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1505
1703
|
async function restartWorkflowAfterPlatformReset(input: {
|
|
1506
1704
|
env: CoordinatorEnv;
|
|
1507
1705
|
ctx?: ExecutionContext;
|
|
@@ -1537,11 +1735,16 @@ async function restartWorkflowAfterPlatformReset(input: {
|
|
|
1537
1735
|
const retryInstanceId = workflowRetryInstanceId(input.runId, claim.attempts);
|
|
1538
1736
|
const retryStartedAt = Date.now();
|
|
1539
1737
|
let retryInstance: WorkflowInstance | null = null;
|
|
1738
|
+
const retryParams = { ...claim.params, runAttempt: claim.attempts };
|
|
1540
1739
|
try {
|
|
1541
1740
|
retryInstance = await createDynamicWorkflowInstance({
|
|
1542
1741
|
env: input.env,
|
|
1543
1742
|
id: retryInstanceId,
|
|
1544
|
-
params:
|
|
1743
|
+
params: workflowCreateParams(retryParams, {
|
|
1744
|
+
params: retryParams,
|
|
1745
|
+
paramsRef: claim.paramsRef,
|
|
1746
|
+
}),
|
|
1747
|
+
metadataParams: retryParams,
|
|
1545
1748
|
});
|
|
1546
1749
|
await recordWorkflowInstanceId({
|
|
1547
1750
|
env: input.env,
|
|
@@ -1591,10 +1794,14 @@ async function mintChildWorkflowExecutorToken(input: {
|
|
|
1591
1794
|
maxCreditsPerRun?: number | null;
|
|
1592
1795
|
}): Promise<string> {
|
|
1593
1796
|
const response = await input.env.HARNESS.runtimeApiCall({
|
|
1797
|
+
contract: PLAY_RUNTIME_CONTRACT,
|
|
1594
1798
|
executorToken: input.parentExecutorToken,
|
|
1595
1799
|
baseUrl: input.baseUrl,
|
|
1596
|
-
path: '/api/v2/
|
|
1597
|
-
headers: {
|
|
1800
|
+
path: '/api/v2/internal/play-runtime/child-executor-token',
|
|
1801
|
+
headers: {
|
|
1802
|
+
'x-deepline-request-id': crypto.randomUUID(),
|
|
1803
|
+
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
1804
|
+
},
|
|
1598
1805
|
timeoutMs: 15_000,
|
|
1599
1806
|
body: {
|
|
1600
1807
|
parentRunId: input.parentRunId,
|
|
@@ -1703,10 +1910,14 @@ async function createChildRuntimeDbSession(input: {
|
|
|
1703
1910
|
}): Promise<CreateDbSessionResponse> {
|
|
1704
1911
|
const decryptionKey = await generateDbSessionPostgresUrlDecryptionKey();
|
|
1705
1912
|
const response = await input.env.HARNESS.runtimeApiCall({
|
|
1913
|
+
contract: PLAY_RUNTIME_CONTRACT,
|
|
1706
1914
|
executorToken: input.childExecutorToken,
|
|
1707
1915
|
baseUrl: input.baseUrl,
|
|
1708
|
-
path: '/api/v2/
|
|
1709
|
-
headers: {
|
|
1916
|
+
path: '/api/v2/internal/play-runtime',
|
|
1917
|
+
headers: {
|
|
1918
|
+
'x-deepline-request-id': crypto.randomUUID(),
|
|
1919
|
+
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
1920
|
+
},
|
|
1710
1921
|
timeoutMs: 15_000,
|
|
1711
1922
|
body: {
|
|
1712
1923
|
action: 'create_db_session',
|
|
@@ -1812,10 +2023,14 @@ async function registerChildRunWithRuntime(input: {
|
|
|
1812
2023
|
executionProfile: string;
|
|
1813
2024
|
}): Promise<void> {
|
|
1814
2025
|
const response = await input.env.HARNESS.runtimeApiCall({
|
|
2026
|
+
contract: PLAY_RUNTIME_CONTRACT,
|
|
1815
2027
|
executorToken: input.childExecutorToken,
|
|
1816
2028
|
baseUrl: input.baseUrl,
|
|
1817
|
-
path: '/api/v2/
|
|
1818
|
-
headers: {
|
|
2029
|
+
path: '/api/v2/internal/play-runtime',
|
|
2030
|
+
headers: {
|
|
2031
|
+
'x-deepline-request-id': crypto.randomUUID(),
|
|
2032
|
+
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
2033
|
+
},
|
|
1819
2034
|
timeoutMs: 15_000,
|
|
1820
2035
|
body: {
|
|
1821
2036
|
action: 'start_inline_child_run',
|
|
@@ -1858,10 +2073,14 @@ async function markRegisteredChildRunFailed(input: {
|
|
|
1858
2073
|
const message =
|
|
1859
2074
|
input.error instanceof Error ? input.error.message : String(input.error);
|
|
1860
2075
|
const response = await input.env.HARNESS.runtimeApiCall({
|
|
2076
|
+
contract: PLAY_RUNTIME_CONTRACT,
|
|
1861
2077
|
executorToken: input.childExecutorToken,
|
|
1862
2078
|
baseUrl: input.baseUrl,
|
|
1863
|
-
path: '/api/v2/
|
|
1864
|
-
headers: {
|
|
2079
|
+
path: '/api/v2/internal/play-runtime',
|
|
2080
|
+
headers: {
|
|
2081
|
+
'x-deepline-request-id': crypto.randomUUID(),
|
|
2082
|
+
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
2083
|
+
},
|
|
1865
2084
|
timeoutMs: 15_000,
|
|
1866
2085
|
body: {
|
|
1867
2086
|
action: 'append_run_events',
|
|
@@ -1885,6 +2104,55 @@ async function markRegisteredChildRunFailed(input: {
|
|
|
1885
2104
|
}
|
|
1886
2105
|
}
|
|
1887
2106
|
|
|
2107
|
+
async function appendRegisteredChildRunTerminal(input: {
|
|
2108
|
+
env: CoordinatorEnv;
|
|
2109
|
+
baseUrl: string;
|
|
2110
|
+
childExecutorToken: string;
|
|
2111
|
+
childRunId: string;
|
|
2112
|
+
status: 'completed' | 'failed';
|
|
2113
|
+
result?: unknown;
|
|
2114
|
+
error?: string | null;
|
|
2115
|
+
}): Promise<void> {
|
|
2116
|
+
const event =
|
|
2117
|
+
input.status === 'completed'
|
|
2118
|
+
? ({
|
|
2119
|
+
type: 'run.completed',
|
|
2120
|
+
runId: input.childRunId,
|
|
2121
|
+
source: 'worker',
|
|
2122
|
+
occurredAt: Date.now(),
|
|
2123
|
+
...(input.result !== undefined ? { result: input.result } : {}),
|
|
2124
|
+
} satisfies PlayRunLedgerEvent)
|
|
2125
|
+
: ({
|
|
2126
|
+
type: 'run.failed',
|
|
2127
|
+
runId: input.childRunId,
|
|
2128
|
+
source: 'worker',
|
|
2129
|
+
occurredAt: Date.now(),
|
|
2130
|
+
error: input.error ?? 'Inline child Worker failed.',
|
|
2131
|
+
} satisfies PlayRunLedgerEvent);
|
|
2132
|
+
const response = await input.env.HARNESS.runtimeApiCall({
|
|
2133
|
+
contract: PLAY_RUNTIME_CONTRACT,
|
|
2134
|
+
executorToken: input.childExecutorToken,
|
|
2135
|
+
baseUrl: input.baseUrl,
|
|
2136
|
+
path: '/api/v2/internal/play-runtime',
|
|
2137
|
+
headers: {
|
|
2138
|
+
'x-deepline-request-id': crypto.randomUUID(),
|
|
2139
|
+
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
2140
|
+
},
|
|
2141
|
+
timeoutMs: 15_000,
|
|
2142
|
+
body: {
|
|
2143
|
+
action: 'append_run_events',
|
|
2144
|
+
playId: input.childRunId,
|
|
2145
|
+
events: [event],
|
|
2146
|
+
},
|
|
2147
|
+
});
|
|
2148
|
+
if (response.status < 200 || response.status >= 300) {
|
|
2149
|
+
const text = response.body ?? '';
|
|
2150
|
+
throw new Error(
|
|
2151
|
+
`Inline child terminal append failed ${response.status}: ${text.slice(0, 800)}`,
|
|
2152
|
+
);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
|
|
1888
2156
|
type CoordinatorRuntimeApiTiming = {
|
|
1889
2157
|
phase: string;
|
|
1890
2158
|
ms: number;
|
|
@@ -1924,12 +2192,14 @@ async function callRuntimeApiFromCoordinator(input: {
|
|
|
1924
2192
|
|
|
1925
2193
|
const fetchStartedAt = Date.now();
|
|
1926
2194
|
const response = await input.env.HARNESS.runtimeApiCall({
|
|
2195
|
+
contract: PLAY_RUNTIME_CONTRACT,
|
|
1927
2196
|
executorToken: input.executorToken,
|
|
1928
2197
|
baseUrl: input.baseUrl,
|
|
1929
|
-
path: '/api/v2/
|
|
2198
|
+
path: '/api/v2/internal/play-runtime',
|
|
1930
2199
|
body,
|
|
1931
2200
|
headers: {
|
|
1932
2201
|
'x-deepline-request-id': crypto.randomUUID(),
|
|
2202
|
+
[PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
|
|
1933
2203
|
},
|
|
1934
2204
|
});
|
|
1935
2205
|
recordTiming('coordinator.runtime_api.fetch', fetchStartedAt);
|
|
@@ -2022,19 +2292,38 @@ async function prepareInlineChildRunWithRuntime(input: {
|
|
|
2022
2292
|
};
|
|
2023
2293
|
}
|
|
2024
2294
|
|
|
2025
|
-
function buildChildRunId(
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2295
|
+
function buildChildRunId(input: {
|
|
2296
|
+
playName: string;
|
|
2297
|
+
parentRunId: string;
|
|
2298
|
+
body: Record<string, unknown>;
|
|
2299
|
+
}): string {
|
|
2300
|
+
const { playName, parentRunId, body } = input;
|
|
2301
|
+
const governance = isRecord(body.internalRunPlay)
|
|
2302
|
+
? body.internalRunPlay
|
|
2303
|
+
: null;
|
|
2304
|
+
// Adapt the coordinator's on-the-wire child-submit body to the shared
|
|
2305
|
+
// derivation. Output stays byte-identical to the previous inline
|
|
2306
|
+
// implementation (same fields → same digest → same run id), so child-submit
|
|
2307
|
+
// idempotency is preserved. See shared_libs/play-runtime/child-run-id.ts.
|
|
2308
|
+
return buildSharedChildRunId({
|
|
2309
|
+
childPlayName: playName,
|
|
2310
|
+
parentRunId,
|
|
2311
|
+
explicitIdempotencyKey:
|
|
2312
|
+
typeof body.childIdempotencyKey === 'string'
|
|
2313
|
+
? body.childIdempotencyKey
|
|
2314
|
+
: null,
|
|
2315
|
+
parentPlayName:
|
|
2316
|
+
typeof governance?.parentPlayName === 'string'
|
|
2317
|
+
? governance.parentPlayName
|
|
2318
|
+
: null,
|
|
2319
|
+
key: typeof governance?.key === 'string' ? governance.key : null,
|
|
2320
|
+
input: isRecord(body.input) ? body.input : {},
|
|
2321
|
+
graphHash: isRecord(body.manifest)
|
|
2322
|
+
? typeof body.manifest.graphHash === 'string'
|
|
2323
|
+
? body.manifest.graphHash
|
|
2324
|
+
: null
|
|
2325
|
+
: null,
|
|
2326
|
+
});
|
|
2038
2327
|
}
|
|
2039
2328
|
|
|
2040
2329
|
function normalizeRuntimeBaseUrl(value: unknown): string | null {
|
|
@@ -2219,6 +2508,7 @@ function buildChildWorkflowParams(input: {
|
|
|
2219
2508
|
executorToken: childToken,
|
|
2220
2509
|
baseUrl,
|
|
2221
2510
|
integrationMode: normalizeIntegrationMode(body.integrationMode),
|
|
2511
|
+
force: body.force === true || body.forceToolRefresh === true,
|
|
2222
2512
|
forceToolRefresh: body.forceToolRefresh === true,
|
|
2223
2513
|
orgId,
|
|
2224
2514
|
userEmail: typeof body.userEmail === 'string' ? body.userEmail : '',
|
|
@@ -2253,6 +2543,7 @@ function runRequestFromPlayWorkflowParams(
|
|
|
2253
2543
|
graphHash: params.graphHash,
|
|
2254
2544
|
userEmail: params.userEmail,
|
|
2255
2545
|
runtimeInput: params.input,
|
|
2546
|
+
runAttempt: normalizeWorkflowRunAttempt(params.runAttempt),
|
|
2256
2547
|
inlineCsv: params.inlineCsv ?? null,
|
|
2257
2548
|
inputFiles:
|
|
2258
2549
|
inputStorageKey && inputFileName
|
|
@@ -2280,8 +2571,11 @@ function runRequestFromPlayWorkflowParams(
|
|
|
2280
2571
|
executionPlan: params.executionPlan ?? null,
|
|
2281
2572
|
childPlayManifests: params.childPlayManifests ?? null,
|
|
2282
2573
|
playCallGovernance: params.playCallGovernance ?? null,
|
|
2283
|
-
force: params.forceToolRefresh === true,
|
|
2574
|
+
force: params.force === true || params.forceToolRefresh === true,
|
|
2575
|
+
forceToolRefresh: params.forceToolRefresh === true,
|
|
2284
2576
|
preloadedDbSessions: params.preloadedDbSessions ?? null,
|
|
2577
|
+
runtimeTestFaultHeader: params.runtimeTestFaultHeader ?? null,
|
|
2578
|
+
testPolicyOverrides: params.testPolicyOverrides ?? null,
|
|
2285
2579
|
inlineChildRunRegistered:
|
|
2286
2580
|
params.runtimeBackend === 'cf_workflows_dynamic_worker_inline_child',
|
|
2287
2581
|
coordinatorUrl: params.coordinatorUrl ?? null,
|
|
@@ -2376,7 +2670,11 @@ async function executeChildInline(input: {
|
|
|
2376
2670
|
}
|
|
2377
2671
|
const { manifest, governance, childPlayName, orgId, parentExecutorToken } =
|
|
2378
2672
|
validated;
|
|
2379
|
-
const childRunId = buildChildRunId(
|
|
2673
|
+
const childRunId = buildChildRunId({
|
|
2674
|
+
playName: childPlayName,
|
|
2675
|
+
parentRunId: input.parentRunId,
|
|
2676
|
+
body: input.body,
|
|
2677
|
+
});
|
|
2380
2678
|
const timings: CoordinatorTiming[] = [];
|
|
2381
2679
|
const trace = (
|
|
2382
2680
|
phase: string,
|
|
@@ -2433,10 +2731,11 @@ async function executeChildInline(input: {
|
|
|
2433
2731
|
input.body.parentPlayName.trim()
|
|
2434
2732
|
? input.body.parentPlayName.trim()
|
|
2435
2733
|
: governance.parentPlayName;
|
|
2734
|
+
const baseUrl = resolveRuntimeBaseUrl(input.env, input.body);
|
|
2436
2735
|
const { childToken, preloadedDbSessions, prepareTimings, transportTimings } =
|
|
2437
2736
|
await prepareInlineChildRunWithRuntime({
|
|
2438
2737
|
env: input.env,
|
|
2439
|
-
baseUrl
|
|
2738
|
+
baseUrl,
|
|
2440
2739
|
parentExecutorToken,
|
|
2441
2740
|
parentRunId: input.parentRunId,
|
|
2442
2741
|
parentPlayName,
|
|
@@ -2607,6 +2906,20 @@ async function executeChildInline(input: {
|
|
|
2607
2906
|
error: error.message,
|
|
2608
2907
|
},
|
|
2609
2908
|
});
|
|
2909
|
+
const terminalAppendStartedAt = Date.now();
|
|
2910
|
+
await appendRegisteredChildRunTerminal({
|
|
2911
|
+
env: input.env,
|
|
2912
|
+
baseUrl,
|
|
2913
|
+
childExecutorToken: childToken,
|
|
2914
|
+
childRunId,
|
|
2915
|
+
status: 'failed',
|
|
2916
|
+
error: error.message,
|
|
2917
|
+
});
|
|
2918
|
+
trace(
|
|
2919
|
+
'coordinator.inline_child_terminal_ledger_append',
|
|
2920
|
+
terminalAppendStartedAt,
|
|
2921
|
+
{ status: 'failed' },
|
|
2922
|
+
);
|
|
2610
2923
|
return {
|
|
2611
2924
|
workflowId: childRunId,
|
|
2612
2925
|
runId: childRunId,
|
|
@@ -2629,6 +2942,20 @@ async function executeChildInline(input: {
|
|
|
2629
2942
|
mode: 'inline_dynamic_worker',
|
|
2630
2943
|
},
|
|
2631
2944
|
});
|
|
2945
|
+
const terminalAppendStartedAt = Date.now();
|
|
2946
|
+
await appendRegisteredChildRunTerminal({
|
|
2947
|
+
env: input.env,
|
|
2948
|
+
baseUrl,
|
|
2949
|
+
childExecutorToken: childToken,
|
|
2950
|
+
childRunId,
|
|
2951
|
+
status: 'completed',
|
|
2952
|
+
result: parsed.result,
|
|
2953
|
+
});
|
|
2954
|
+
trace(
|
|
2955
|
+
'coordinator.inline_child_terminal_ledger_append',
|
|
2956
|
+
terminalAppendStartedAt,
|
|
2957
|
+
{ status: 'completed' },
|
|
2958
|
+
);
|
|
2632
2959
|
return {
|
|
2633
2960
|
workflowId: childRunId,
|
|
2634
2961
|
runId: childRunId,
|
|
@@ -2645,6 +2972,35 @@ async function executeChildInline(input: {
|
|
|
2645
2972
|
}
|
|
2646
2973
|
}
|
|
2647
2974
|
|
|
2975
|
+
function isRetryableChildWorkflowSubmitError(error: unknown): boolean {
|
|
2976
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2977
|
+
return /timed out|timeout|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|internal error/i.test(
|
|
2978
|
+
message,
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
function isRetryableChildWorkflowSubmitResponse(
|
|
2983
|
+
status: number,
|
|
2984
|
+
body: string,
|
|
2985
|
+
): boolean {
|
|
2986
|
+
if (
|
|
2987
|
+
status === 408 ||
|
|
2988
|
+
status === 429 ||
|
|
2989
|
+
status === 502 ||
|
|
2990
|
+
status === 503 ||
|
|
2991
|
+
status === 504 ||
|
|
2992
|
+
status === 530
|
|
2993
|
+
) {
|
|
2994
|
+
return true;
|
|
2995
|
+
}
|
|
2996
|
+
return (
|
|
2997
|
+
status === 500 &&
|
|
2998
|
+
/COORDINATOR_WORKFLOW_ROUTE_FAILED|timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|UND_ERR_CONNECT_TIMEOUT|internal error/i.test(
|
|
2999
|
+
body,
|
|
3000
|
+
)
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
|
|
2648
3004
|
async function submitChildWorkflowThroughCoordinator(input: {
|
|
2649
3005
|
env: CoordinatorEnv;
|
|
2650
3006
|
parentRunId: string;
|
|
@@ -2700,7 +3056,11 @@ async function submitChildWorkflowThroughCoordinator(input: {
|
|
|
2700
3056
|
}
|
|
2701
3057
|
const { manifest, governance, childPlayName, orgId, parentExecutorToken } =
|
|
2702
3058
|
validated;
|
|
2703
|
-
const childRunId = buildChildRunId(
|
|
3059
|
+
const childRunId = buildChildRunId({
|
|
3060
|
+
playName: childPlayName,
|
|
3061
|
+
parentRunId: input.parentRunId,
|
|
3062
|
+
body: input.body,
|
|
3063
|
+
});
|
|
2704
3064
|
const baseUrl = resolveRuntimeBaseUrl(input.env, input.body);
|
|
2705
3065
|
|
|
2706
3066
|
const tokenStartedAt = Date.now();
|
|
@@ -2783,38 +3143,96 @@ async function submitChildWorkflowThroughCoordinator(input: {
|
|
|
2783
3143
|
);
|
|
2784
3144
|
|
|
2785
3145
|
const workflowSubmitStartedAt = Date.now();
|
|
2786
|
-
let response: Response;
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
3146
|
+
let response: Response | null = null;
|
|
3147
|
+
let responseText = '';
|
|
3148
|
+
let lastSubmitError: unknown = null;
|
|
3149
|
+
for (
|
|
3150
|
+
let attempt = 0;
|
|
3151
|
+
attempt <= CHILD_WORKFLOW_SUBMIT_RETRY_DELAYS_MS.length;
|
|
3152
|
+
attempt += 1
|
|
3153
|
+
) {
|
|
3154
|
+
try {
|
|
3155
|
+
response = await handleWorkflowRoute({
|
|
3156
|
+
runId: childRunId,
|
|
3157
|
+
action: 'submit',
|
|
3158
|
+
request: new Request(
|
|
3159
|
+
`https://deepline.coordinator.internal/workflow/${encodeURIComponent(
|
|
3160
|
+
childRunId,
|
|
3161
|
+
)}/submit`,
|
|
3162
|
+
{
|
|
3163
|
+
method: 'POST',
|
|
3164
|
+
headers: { 'content-type': 'application/json' },
|
|
3165
|
+
body: JSON.stringify(params),
|
|
3166
|
+
},
|
|
3167
|
+
),
|
|
3168
|
+
env: input.env,
|
|
3169
|
+
});
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
lastSubmitError = error;
|
|
3172
|
+
trace(
|
|
3173
|
+
'coordinator.child_submit_workflow_attempt',
|
|
3174
|
+
workflowSubmitStartedAt,
|
|
3175
|
+
manifest.graphHash,
|
|
2795
3176
|
{
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3177
|
+
childRunId,
|
|
3178
|
+
attempt: attempt + 1,
|
|
3179
|
+
status: 'threw',
|
|
3180
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2799
3181
|
},
|
|
2800
|
-
)
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3182
|
+
);
|
|
3183
|
+
if (
|
|
3184
|
+
attempt >= CHILD_WORKFLOW_SUBMIT_RETRY_DELAYS_MS.length ||
|
|
3185
|
+
!isRetryableChildWorkflowSubmitError(error)
|
|
3186
|
+
) {
|
|
3187
|
+
await markRegisteredChildRunFailed({
|
|
3188
|
+
env: input.env,
|
|
3189
|
+
baseUrl,
|
|
3190
|
+
childExecutorToken: childToken,
|
|
3191
|
+
childRunId,
|
|
3192
|
+
error,
|
|
3193
|
+
}).catch((markError) => {
|
|
3194
|
+
console.error(
|
|
3195
|
+
'[coordinator] child workflow submit failure mark failed',
|
|
3196
|
+
{
|
|
3197
|
+
childRunId,
|
|
3198
|
+
error:
|
|
3199
|
+
markError instanceof Error
|
|
3200
|
+
? markError.message
|
|
3201
|
+
: String(markError),
|
|
3202
|
+
},
|
|
3203
|
+
);
|
|
3204
|
+
});
|
|
3205
|
+
throw error;
|
|
3206
|
+
}
|
|
3207
|
+
await sleep(CHILD_WORKFLOW_SUBMIT_RETRY_DELAYS_MS[attempt] ?? 0);
|
|
3208
|
+
continue;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
responseText = await response.text().catch(() => '');
|
|
3212
|
+
trace(
|
|
3213
|
+
'coordinator.child_submit_workflow_attempt',
|
|
3214
|
+
workflowSubmitStartedAt,
|
|
3215
|
+
manifest.graphHash,
|
|
3216
|
+
{
|
|
2812
3217
|
childRunId,
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
|
|
3218
|
+
attempt: attempt + 1,
|
|
3219
|
+
status: response.status,
|
|
3220
|
+
},
|
|
3221
|
+
);
|
|
3222
|
+
if (
|
|
3223
|
+
response.ok ||
|
|
3224
|
+
attempt >= CHILD_WORKFLOW_SUBMIT_RETRY_DELAYS_MS.length ||
|
|
3225
|
+
!isRetryableChildWorkflowSubmitResponse(response.status, responseText)
|
|
3226
|
+
) {
|
|
3227
|
+
break;
|
|
3228
|
+
}
|
|
3229
|
+
lastSubmitError = `workflow submit returned ${response.status}: ${responseText.slice(0, 800)}`;
|
|
3230
|
+
await sleep(CHILD_WORKFLOW_SUBMIT_RETRY_DELAYS_MS[attempt] ?? 0);
|
|
3231
|
+
}
|
|
3232
|
+
if (!response) {
|
|
3233
|
+
throw lastSubmitError instanceof Error
|
|
3234
|
+
? lastSubmitError
|
|
3235
|
+
: new Error(String(lastSubmitError));
|
|
2818
3236
|
}
|
|
2819
3237
|
trace(
|
|
2820
3238
|
'coordinator.child_submit_workflow',
|
|
@@ -2822,7 +3240,6 @@ async function submitChildWorkflowThroughCoordinator(input: {
|
|
|
2822
3240
|
manifest.graphHash,
|
|
2823
3241
|
{ childRunId, status: response.status },
|
|
2824
3242
|
);
|
|
2825
|
-
const responseText = await response.text().catch(() => '');
|
|
2826
3243
|
if (!response.ok) {
|
|
2827
3244
|
await markRegisteredChildRunFailed({
|
|
2828
3245
|
env: input.env,
|
|
@@ -3156,6 +3573,11 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
|
|
|
3156
3573
|
});
|
|
3157
3574
|
}
|
|
3158
3575
|
let dispatchedEvent = event;
|
|
3576
|
+
dispatchedEvent = await hydrateWorkflowLaunchParams({
|
|
3577
|
+
env: this.env,
|
|
3578
|
+
event: dispatchedEvent,
|
|
3579
|
+
trace,
|
|
3580
|
+
});
|
|
3159
3581
|
try {
|
|
3160
3582
|
dispatchedEvent = await hydrateWorkflowDbSessions({
|
|
3161
3583
|
env: this.env,
|
|
@@ -3202,8 +3624,8 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
|
|
|
3202
3624
|
await writeCoordinatorTerminalState(this.env, {
|
|
3203
3625
|
runId: entryTrace.runId,
|
|
3204
3626
|
status: 'failed',
|
|
3205
|
-
error:
|
|
3206
|
-
.message,
|
|
3627
|
+
error:
|
|
3628
|
+
normalizeCoordinatorWorkflowRuntimeFailure(hydrateError).message,
|
|
3207
3629
|
}).catch(() => undefined);
|
|
3208
3630
|
}
|
|
3209
3631
|
}
|
|
@@ -3300,12 +3722,42 @@ export class DynamicWorkflow extends WorkflowEntrypoint<
|
|
|
3300
3722
|
}
|
|
3301
3723
|
).run(innerEvent, innerStep);
|
|
3302
3724
|
const output = isRecord(result) ? result : null;
|
|
3725
|
+
const terminalResult = output?.result ?? result;
|
|
3303
3726
|
const terminalStartedAt = Date.now();
|
|
3727
|
+
const childTerminalStartedAt = Date.now();
|
|
3728
|
+
try {
|
|
3729
|
+
const childTerminalPersisted =
|
|
3730
|
+
await appendWorkflowChildCompletedRunEvent({
|
|
3731
|
+
env,
|
|
3732
|
+
event: innerEvent,
|
|
3733
|
+
result: terminalResult,
|
|
3734
|
+
});
|
|
3735
|
+
if (childTerminalPersisted) {
|
|
3736
|
+
trace({
|
|
3737
|
+
runId: runIdForTrace,
|
|
3738
|
+
phase:
|
|
3739
|
+
'coordinator.child_terminal_ledger_append_completed',
|
|
3740
|
+
ms: Date.now() - childTerminalStartedAt,
|
|
3741
|
+
graphHash,
|
|
3742
|
+
});
|
|
3743
|
+
}
|
|
3744
|
+
} catch (childTerminalError) {
|
|
3745
|
+
console.warn(
|
|
3746
|
+
'[coordinator] child terminal ledger append failed; preserving coordinator terminal cache',
|
|
3747
|
+
{
|
|
3748
|
+
runId: runIdForTrace,
|
|
3749
|
+
message:
|
|
3750
|
+
childTerminalError instanceof Error
|
|
3751
|
+
? childTerminalError.message
|
|
3752
|
+
: String(childTerminalError),
|
|
3753
|
+
},
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3304
3756
|
try {
|
|
3305
3757
|
await writeCoordinatorTerminalState(env, {
|
|
3306
3758
|
runId: runIdForTrace,
|
|
3307
3759
|
status: 'completed',
|
|
3308
|
-
result:
|
|
3760
|
+
result: terminalResult,
|
|
3309
3761
|
totalRows: output?.totalRows ?? output?.outputRows ?? null,
|
|
3310
3762
|
durationMs: output?.durationMs ?? null,
|
|
3311
3763
|
playName:
|
|
@@ -3586,6 +4038,69 @@ async function coordinatorRouteFetch(
|
|
|
3586
4038
|
});
|
|
3587
4039
|
}
|
|
3588
4040
|
}
|
|
4041
|
+
if (url.pathname === '/rate-acquire') {
|
|
4042
|
+
const authError = authorizeCoordinatorControlRequest({ request, env });
|
|
4043
|
+
if (authError) return authError;
|
|
4044
|
+
try {
|
|
4045
|
+
const body = (await request.json().catch(() => null)) as {
|
|
4046
|
+
bucketId?: unknown;
|
|
4047
|
+
rules?: unknown;
|
|
4048
|
+
requested?: unknown;
|
|
4049
|
+
} | null;
|
|
4050
|
+
const bucketId = typeof body?.bucketId === 'string' ? body.bucketId : '';
|
|
4051
|
+
if (!bucketId.trim()) {
|
|
4052
|
+
return Response.json(
|
|
4053
|
+
{ error: 'bucketId is required.' },
|
|
4054
|
+
{ status: 400 },
|
|
4055
|
+
);
|
|
4056
|
+
}
|
|
4057
|
+
const result = await callRateBucketControl<{
|
|
4058
|
+
granted: number;
|
|
4059
|
+
waitMs: number;
|
|
4060
|
+
}>(env, bucketId, '/rate-acquire', body);
|
|
4061
|
+
return Response.json(result);
|
|
4062
|
+
} catch (error) {
|
|
4063
|
+
return coordinatorRouteErrorResponse({
|
|
4064
|
+
logTag: '[coordinator.rate_acquire.error]',
|
|
4065
|
+
code: 'COORDINATOR_RATE_ROUTE_FAILED',
|
|
4066
|
+
phase: 'coordinator.rate.acquire',
|
|
4067
|
+
runId: null,
|
|
4068
|
+
error,
|
|
4069
|
+
});
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
if (url.pathname === '/rate-penalize') {
|
|
4073
|
+
const authError = authorizeCoordinatorControlRequest({ request, env });
|
|
4074
|
+
if (authError) return authError;
|
|
4075
|
+
try {
|
|
4076
|
+
const body = (await request.json().catch(() => null)) as {
|
|
4077
|
+
bucketId?: unknown;
|
|
4078
|
+
cooldownMs?: unknown;
|
|
4079
|
+
} | null;
|
|
4080
|
+
const bucketId = typeof body?.bucketId === 'string' ? body.bucketId : '';
|
|
4081
|
+
if (!bucketId.trim()) {
|
|
4082
|
+
return Response.json(
|
|
4083
|
+
{ error: 'bucketId is required.' },
|
|
4084
|
+
{ status: 400 },
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
await callRateBucketControl<{ ok?: unknown }>(
|
|
4088
|
+
env,
|
|
4089
|
+
bucketId,
|
|
4090
|
+
'/rate-penalize',
|
|
4091
|
+
body,
|
|
4092
|
+
);
|
|
4093
|
+
return Response.json({ ok: true });
|
|
4094
|
+
} catch (error) {
|
|
4095
|
+
return coordinatorRouteErrorResponse({
|
|
4096
|
+
logTag: '[coordinator.rate_penalize.error]',
|
|
4097
|
+
code: 'COORDINATOR_RATE_ROUTE_FAILED',
|
|
4098
|
+
phase: 'coordinator.rate.penalize',
|
|
4099
|
+
runId: null,
|
|
4100
|
+
error,
|
|
4101
|
+
});
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
3589
4104
|
// Workflow routes: /workflow/{runId}/{action}
|
|
3590
4105
|
const wfMatch = url.pathname.match(/^\/workflow\/([^/]+)(?:\/(.+))?$/);
|
|
3591
4106
|
if (wfMatch) {
|
|
@@ -3676,7 +4191,7 @@ async function flushTailRunLogs(
|
|
|
3676
4191
|
return;
|
|
3677
4192
|
}
|
|
3678
4193
|
await fetch(
|
|
3679
|
-
`${env.DEEPLINE_API_BASE_URL}/api/v2/
|
|
4194
|
+
`${env.DEEPLINE_API_BASE_URL}/api/v2/internal/play-runtime/tail-log`,
|
|
3680
4195
|
{
|
|
3681
4196
|
method: 'POST',
|
|
3682
4197
|
headers: {
|
|
@@ -3779,7 +4294,10 @@ function coordinatorWorkflowRouteErrorResponse(input: {
|
|
|
3779
4294
|
action: input.action,
|
|
3780
4295
|
error: message,
|
|
3781
4296
|
});
|
|
3782
|
-
if (
|
|
4297
|
+
if (
|
|
4298
|
+
input.action === 'submit' &&
|
|
4299
|
+
isTenantStorageDegradationError(input.error)
|
|
4300
|
+
) {
|
|
3783
4301
|
// De-redact: a tenant-storage failure gets the loud, actionable
|
|
3784
4302
|
// WORKSPACE_STORAGE_NOT_READY message + repair instruction instead of the
|
|
3785
4303
|
// generic "internal error". Same contract as the runtime route.
|
|
@@ -3996,12 +4514,15 @@ async function handleWorkflowRoute(input: {
|
|
|
3996
4514
|
// logs. Log the tenant-storage classification loudly so the real
|
|
3997
4515
|
// cause (and the WORKSPACE_STORAGE_NOT_READY repair path) is visible.
|
|
3998
4516
|
if (storageNotReady) {
|
|
3999
|
-
console.error(
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4517
|
+
console.error(
|
|
4518
|
+
'[coordinator.harness_prewarm_postgres.storage_not_ready]',
|
|
4519
|
+
{
|
|
4520
|
+
runId: submittedRunId,
|
|
4521
|
+
playName: params.playName,
|
|
4522
|
+
code: WORKSPACE_STORAGE_NOT_READY_CODE,
|
|
4523
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4524
|
+
},
|
|
4525
|
+
);
|
|
4005
4526
|
}
|
|
4006
4527
|
});
|
|
4007
4528
|
input.ctx?.waitUntil(prewarmPromise);
|
|
@@ -4010,9 +4531,12 @@ async function handleWorkflowRoute(input: {
|
|
|
4010
4531
|
params,
|
|
4011
4532
|
});
|
|
4012
4533
|
const workflowParams = dbSessionExternalization.params;
|
|
4534
|
+
workflowParams.submittedAt = Date.now();
|
|
4535
|
+
let launchState: Awaited<ReturnType<typeof persistWorkflowLaunchState>> =
|
|
4536
|
+
{};
|
|
4013
4537
|
try {
|
|
4014
4538
|
const retryStateStartedAt = Date.now();
|
|
4015
|
-
|
|
4539
|
+
launchState = await persistWorkflowLaunchState({
|
|
4016
4540
|
env,
|
|
4017
4541
|
runId: submittedRunId,
|
|
4018
4542
|
params: workflowParams,
|
|
@@ -4065,7 +4589,7 @@ async function handleWorkflowRoute(input: {
|
|
|
4065
4589
|
error,
|
|
4066
4590
|
});
|
|
4067
4591
|
}
|
|
4068
|
-
|
|
4592
|
+
const createParams = workflowCreateParams(workflowParams, launchState);
|
|
4069
4593
|
let instance: WorkflowInstance | null = null;
|
|
4070
4594
|
try {
|
|
4071
4595
|
const submitClaimStartedAt = Date.now();
|
|
@@ -4167,7 +4691,8 @@ async function handleWorkflowRoute(input: {
|
|
|
4167
4691
|
createDynamicWorkflowInstance({
|
|
4168
4692
|
env,
|
|
4169
4693
|
id: defaultInstanceId,
|
|
4170
|
-
params:
|
|
4694
|
+
params: createParams,
|
|
4695
|
+
metadataParams: workflowParams,
|
|
4171
4696
|
}),
|
|
4172
4697
|
getExisting: () => env.PLAY_WORKFLOW.get(defaultInstanceId),
|
|
4173
4698
|
onCreateFailure: async () => {
|
|
@@ -4737,7 +5262,8 @@ function stableHash(value: string): string {
|
|
|
4737
5262
|
return (hash >>> 0).toString(36);
|
|
4738
5263
|
}
|
|
4739
5264
|
|
|
4740
|
-
const DYNAMIC_PLAY_WORKER_HARNESS_VERSION =
|
|
5265
|
+
const DYNAMIC_PLAY_WORKER_HARNESS_VERSION =
|
|
5266
|
+
DYNAMIC_PLAY_WORKER_ARTIFACT_VERSION;
|
|
4741
5267
|
const DYNAMIC_WORKER_BUNDLED_CODE_CACHE_MAX_ENTRIES = 64;
|
|
4742
5268
|
const dynamicWorkerBundledCodeCache = new Map<string, string>();
|
|
4743
5269
|
|
|
@@ -5057,7 +5583,7 @@ export class TenantWorkflow extends WorkflowEntrypoint {
|
|
|
5057
5583
|
const runId = payload && typeof payload.runId === "string" ? payload.runId : "warmup";
|
|
5058
5584
|
const startedAt = Date.now();
|
|
5059
5585
|
if (this.env.RUNTIME_API) {
|
|
5060
|
-
await this.env.RUNTIME_API.fetch(new Request("https://deepline.runtime.internal/api/v2/
|
|
5586
|
+
await this.env.RUNTIME_API.fetch(new Request("https://deepline.runtime.internal/api/v2/internal/play-runtime", {
|
|
5061
5587
|
method: "POST",
|
|
5062
5588
|
headers: { "content-type": "application/json" },
|
|
5063
5589
|
body: "{}"
|