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.
Files changed (70) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/child-play-await.ts +203 -6
  2. package/dist/bundling-sources/apps/play-runner-workers/src/child-play-submit.ts +8 -1
  3. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +633 -107
  4. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +3 -2
  5. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +1932 -1838
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +116 -0
  7. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +542 -78
  8. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +124 -12
  9. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +292 -11
  10. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +519 -0
  11. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +2381 -0
  12. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +18 -5
  13. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +130 -0
  14. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +39 -0
  15. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +2 -0
  16. package/dist/bundling-sources/sdk/src/client.ts +48 -5
  17. package/dist/bundling-sources/sdk/src/http.ts +23 -8
  18. package/dist/bundling-sources/sdk/src/play.ts +200 -26
  19. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +56 -3
  20. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  21. package/dist/bundling-sources/sdk/src/types.ts +2 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +132 -10
  23. package/dist/bundling-sources/shared_libs/play-runtime/auth-scope-resolver.ts +92 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -4
  25. package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +8 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +101 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1857 -314
  28. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +106 -6
  29. package/dist/bundling-sources/shared_libs/play-runtime/dedup-backend.ts +0 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +1 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +4 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +116 -16
  33. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +186 -20
  34. package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +2 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +133 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +245 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -5
  38. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +14 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +52 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +41 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +3 -2
  42. package/dist/bundling-sources/shared_libs/play-runtime/query-result-dataset.ts +120 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +124 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +6 -2
  45. package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +48 -2
  46. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +158 -15
  47. package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +45 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +9 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2037 -262
  50. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
  56. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
  58. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
  59. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +139 -17
  60. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
  62. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
  63. package/dist/cli/index.js +85 -23
  64. package/dist/cli/index.mjs +85 -23
  65. package/dist/index.d.mts +4 -1
  66. package/dist/index.d.ts +4 -1
  67. package/dist/index.js +274 -35
  68. package/dist/index.mjs +274 -35
  69. package/dist/plays/bundle-play-file.mjs +2 -2
  70. 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: params.dynamicWorkerCode ?? null,
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/plays/internal/runtime`;
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<void> {
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
- async function hydrateWorkflowRetryParams(input: {
1401
- env: CoordinatorEnv;
1402
- params: unknown;
1403
- paramsRef: unknown;
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 = input.paramsRef.storageKind;
1412
- const storageKey = input.paramsRef.storageKey;
1413
- const expectedBytes = input.paramsRef.bytes;
1414
- const expectedHash = input.paramsRef.hash;
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: claim.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/plays/internal/child-executor-token',
1597
- headers: { 'x-deepline-request-id': crypto.randomUUID() },
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/plays/internal/runtime',
1709
- headers: { 'x-deepline-request-id': crypto.randomUUID() },
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/plays/internal/runtime',
1818
- headers: { 'x-deepline-request-id': crypto.randomUUID() },
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/plays/internal/runtime',
1864
- headers: { 'x-deepline-request-id': crypto.randomUUID() },
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/plays/internal/runtime',
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(playName: string): string {
2026
- const slug =
2027
- playName
2028
- .toLowerCase()
2029
- .replace(/[^a-z0-9-]/g, '-')
2030
- .replace(/-+/g, '-')
2031
- .replace(/^-+|-+$/g, '')
2032
- .slice(0, 64) || 'play';
2033
- return `play/${slug}/run/${new Date()
2034
- .toISOString()
2035
- .replace(/[-:.]/g, '')
2036
- .replace('T', 't')
2037
- .replace('Z', '')}-${crypto.randomUUID().slice(0, 8)}`;
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(childPlayName);
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: resolveRuntimeBaseUrl(input.env, input.body),
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(childPlayName);
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
- try {
2788
- response = await handleWorkflowRoute({
2789
- runId: childRunId,
2790
- action: 'submit',
2791
- request: new Request(
2792
- `https://deepline.coordinator.internal/workflow/${encodeURIComponent(
2793
- childRunId,
2794
- )}/submit`,
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
- method: 'POST',
2797
- headers: { 'content-type': 'application/json' },
2798
- body: JSON.stringify(params),
3177
+ childRunId,
3178
+ attempt: attempt + 1,
3179
+ status: 'threw',
3180
+ error: error instanceof Error ? error.message : String(error),
2799
3181
  },
2800
- ),
2801
- env: input.env,
2802
- });
2803
- } catch (error) {
2804
- await markRegisteredChildRunFailed({
2805
- env: input.env,
2806
- baseUrl,
2807
- childExecutorToken: childToken,
2808
- childRunId,
2809
- error,
2810
- }).catch((markError) => {
2811
- console.error('[coordinator] child workflow submit failure mark failed', {
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
- error:
2814
- markError instanceof Error ? markError.message : String(markError),
2815
- });
2816
- });
2817
- throw error;
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: normalizeCoordinatorWorkflowRuntimeFailure(hydrateError)
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: output?.result ?? 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/plays/internal/tail-log`,
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 (input.action === 'submit' && isTenantStorageDegradationError(input.error)) {
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('[coordinator.harness_prewarm_postgres.storage_not_ready]', {
4000
- runId: submittedRunId,
4001
- playName: params.playName,
4002
- code: WORKSPACE_STORAGE_NOT_READY_CODE,
4003
- error: error instanceof Error ? error.message : String(error),
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
- const launchState = await persistWorkflowLaunchState({
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
- workflowParams.submittedAt = Date.now();
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: workflowParams,
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 = 'h16-coordinator-only-prewarm';
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/plays/internal/runtime", {
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: "{}"