deepline 0.1.227 → 0.1.229

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 (46) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +76 -1842
  2. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +0 -113
  3. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +144 -1000
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/ledger-event-batches.ts +81 -0
  5. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +1 -50
  6. package/dist/bundling-sources/sdk/src/client.ts +24 -2
  7. package/dist/bundling-sources/sdk/src/play.ts +2 -2
  8. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  9. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +382 -31
  10. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +84 -79
  11. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +201 -705
  12. package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +3 -10
  13. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +2 -2
  14. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -47
  15. package/dist/bundling-sources/shared_libs/play-runtime/governor/block-reserving-budget-state-backend.ts +0 -2
  16. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +12 -200
  17. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +2 -17
  18. package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +3 -9
  19. package/dist/bundling-sources/shared_libs/play-runtime/play-call-execution.ts +6 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +28 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/run-execution-scope.ts +16 -64
  22. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +26 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +5 -2
  24. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-paths.ts +0 -4
  25. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +81 -18
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +0 -36
  27. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +3 -21
  28. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +9 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/transport-error-diagnostics.ts +63 -0
  30. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +2 -0
  31. package/dist/cli/index.js +78 -11
  32. package/dist/cli/index.mjs +78 -11
  33. package/dist/{compiler-manifest-BhgZ23A4.d.ts → compiler-manifest-CYcwzSOJ.d.mts} +2 -0
  34. package/dist/{compiler-manifest-BhgZ23A4.d.mts → compiler-manifest-CYcwzSOJ.d.ts} +2 -0
  35. package/dist/index.d.mts +3 -3
  36. package/dist/index.d.ts +3 -3
  37. package/dist/index.js +14 -5
  38. package/dist/index.mjs +14 -5
  39. package/dist/plays/bundle-play-file.d.mts +2 -2
  40. package/dist/plays/bundle-play-file.d.ts +2 -2
  41. package/package.json +1 -1
  42. package/dist/bundling-sources/apps/play-runner-workers/src/child-manifest-resolver.ts +0 -108
  43. package/dist/bundling-sources/apps/play-runner-workers/src/child-play-await.ts +0 -374
  44. package/dist/bundling-sources/apps/play-runner-workers/src/child-play-submit.ts +0 -203
  45. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-placement.ts +0 -14
  46. package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +0 -121
@@ -3,10 +3,7 @@ import {
3
3
  type RuntimeExecutionCapability,
4
4
  } from './execution-capabilities';
5
5
 
6
- export type RunExecutionPlacementKind =
7
- | 'root'
8
- | 'inline_child'
9
- | 'scheduled_child';
6
+ export type RunExecutionPlacementKind = 'root' | 'inline_child';
10
7
 
11
8
  export type RunExecutionAuthorityInput = {
12
9
  readonly orgId: string;
@@ -59,21 +56,12 @@ export type RootRunExecutionScopeInput = {
59
56
  readonly authority: RunExecutionAuthorityInput;
60
57
  };
61
58
 
62
- export type ChildRunExecutionScopeInput =
63
- | {
64
- readonly placement: 'inline';
65
- readonly runId: string;
66
- readonly playId: string;
67
- readonly receiptNamespace: string;
68
- }
69
- | {
70
- readonly placement: 'scheduled';
71
- readonly runId: string;
72
- readonly playId: string;
73
- readonly attempt: number;
74
- readonly receiptNamespace: string;
75
- readonly authority?: Partial<Omit<RunExecutionAuthorityInput, 'orgId'>>;
76
- };
59
+ export type ChildRunExecutionScopeInput = {
60
+ readonly placement: 'inline';
61
+ readonly runId: string;
62
+ readonly playId: string;
63
+ readonly receiptNamespace: string;
64
+ };
77
65
 
78
66
  function requireIdentity(value: string, field: string): string {
79
67
  if (
@@ -203,54 +191,18 @@ export function deriveChildRunExecutionScope(
203
191
  rootPlayId: parent.logical.rootPlayId,
204
192
  };
205
193
 
206
- if (input.placement === 'inline') {
207
- return freezeScope({
208
- logical,
209
- receipt: {
210
- ownerRunId: parent.receipt.ownerRunId,
211
- ownerAttempt: parent.receipt.ownerAttempt,
212
- namespace,
213
- },
214
- placement: {
215
- kind: 'inline_child',
216
- executorRunId: parent.placement.executorRunId,
217
- executorPlayId: parent.placement.executorPlayId,
218
- },
219
- authority: parent.authority,
220
- });
221
- }
222
-
223
- const attempt = requireAttempt(input.attempt, 'attempt');
224
194
  return freezeScope({
225
195
  logical,
226
- receipt: { ownerRunId: runId, ownerAttempt: attempt, namespace },
196
+ receipt: {
197
+ ownerRunId: parent.receipt.ownerRunId,
198
+ ownerAttempt: parent.receipt.ownerAttempt,
199
+ namespace,
200
+ },
227
201
  placement: {
228
- kind: 'scheduled_child',
229
- executorRunId: runId,
230
- executorPlayId: playId,
202
+ kind: 'inline_child',
203
+ executorRunId: parent.placement.executorRunId,
204
+ executorPlayId: parent.placement.executorPlayId,
231
205
  },
232
- authority: authorityMetadata({
233
- runId,
234
- playId,
235
- orgId: parent.authority.orgId,
236
- authority: {
237
- workflowId: input.authority?.workflowId,
238
- billingRunId:
239
- input.authority?.billingRunId === undefined
240
- ? parent.authority.billingRunId
241
- : input.authority.billingRunId,
242
- maxCreditsPerRun:
243
- input.authority?.maxCreditsPerRun === undefined
244
- ? parent.authority.maxCreditsPerRun
245
- : input.authority.maxCreditsPerRun,
246
- integrationMode:
247
- input.authority?.integrationMode === undefined
248
- ? parent.authority.integrationMode
249
- : input.authority.integrationMode,
250
- synthetic: input.authority?.synthetic ?? parent.authority.synthetic,
251
- capabilities:
252
- input.authority?.capabilities ?? parent.authority.capabilities,
253
- },
254
- }),
206
+ authority: parent.authority,
255
207
  });
256
208
  }
@@ -166,6 +166,12 @@ export type PlayRunLedgerEvent =
166
166
  playName?: string | null;
167
167
  runtimeBackend?: string | null;
168
168
  })
169
+ | (PlayRunLedgerBaseEvent & {
170
+ type: 'run.waiting';
171
+ })
172
+ | (PlayRunLedgerBaseEvent & {
173
+ type: 'run.resumed';
174
+ })
169
175
  | (PlayRunLedgerBaseEvent & {
170
176
  type: 'run.completed';
171
177
  result?: unknown;
@@ -1025,6 +1031,16 @@ export function reducePlayRunLedgerEvent(
1025
1031
  if (event.runId !== snapshot.runId) {
1026
1032
  return snapshot;
1027
1033
  }
1034
+ // The scheduler's outbox can replay an older wait transition after its run
1035
+ // reached a terminal state. That transition is no longer meaningful and
1036
+ // must be byte-for-byte inert: even advancing `updatedAt` would re-publish
1037
+ // a stale terminal snapshot to every observer.
1038
+ if (
1039
+ (event.type === 'run.waiting' || event.type === 'run.resumed') &&
1040
+ shouldIgnoreNonTerminalAfterStickyTerminal(snapshot)
1041
+ ) {
1042
+ return snapshot;
1043
+ }
1028
1044
  const occurredAt = Math.max(0, event.occurredAt);
1029
1045
  const base: PlayRunLedgerSnapshot = {
1030
1046
  ...snapshot,
@@ -1062,6 +1078,16 @@ export function reducePlayRunLedgerEvent(
1062
1078
  : 'running',
1063
1079
  startedAt: base.startedAt ?? occurredAt,
1064
1080
  });
1081
+ case 'run.waiting':
1082
+ return withTiming({
1083
+ ...base,
1084
+ status: 'waiting',
1085
+ });
1086
+ case 'run.resumed':
1087
+ return withTiming({
1088
+ ...base,
1089
+ status: 'running',
1090
+ });
1065
1091
  case 'run.completed':
1066
1092
  return (
1067
1093
  conflictingTerminalSnapshot(base, event.type, occurredAt, null, {
@@ -1,6 +1,7 @@
1
1
  export type PlayRunLifecycleStatus =
2
2
  | 'queued'
3
3
  | 'running'
4
+ | 'waiting'
4
5
  | 'completed'
5
6
  | 'failed'
6
7
  | 'cancelled'
@@ -35,8 +36,9 @@ export function normalizePlayRunLifecycleStatus(
35
36
  return 'running';
36
37
  case 'running':
37
38
  case 'started':
38
- case 'waiting':
39
39
  return 'running';
40
+ case 'waiting':
41
+ return 'waiting';
40
42
  case 'completed':
41
43
  case 'complete':
42
44
  case 'succeeded':
@@ -62,7 +64,8 @@ export function isTerminalPlayRunLifecycleStatus(status: unknown): boolean {
62
64
  }
63
65
 
64
66
  export function isActivePlayRunLifecycleStatus(status: unknown): boolean {
65
- return normalizePlayRunLifecycleStatus(status) === 'running';
67
+ const normalized = normalizePlayRunLifecycleStatus(status);
68
+ return normalized === 'running' || normalized === 'waiting';
66
69
  }
67
70
 
68
71
  export function isRecoverableDatasetRowStatus(status: unknown): boolean {
@@ -2,10 +2,6 @@ export const PLAY_RUNTIME_API_COMPAT_PATH = '/api/v2/plays/internal/runtime';
2
2
  export const PLAY_RUNTIME_API_CURRENT_PATH = '/api/v2/internal/play-runtime';
3
3
  export const PLAY_RUNTIME_ATTEMPT_EXECUTOR_TOKEN_PATH =
4
4
  '/api/v2/internal/play-runtime/executor-token';
5
- export const PLAY_RUNTIME_CHILD_EXECUTOR_TOKEN_COMPAT_PATH =
6
- '/api/v2/plays/internal/child-executor-token';
7
- export const PLAY_RUNTIME_CHILD_EXECUTOR_TOKEN_CURRENT_PATH =
8
- '/api/v2/internal/play-runtime/child-executor-token';
9
5
  export const PLAY_RUNTIME_TAIL_LOG_COMPAT_PATH =
10
6
  '/api/v2/plays/internal/tail-log';
11
7
  export const PLAY_RUNTIME_TAIL_LOG_CURRENT_PATH =
@@ -371,7 +371,22 @@ export type ResolvedRuntimePlay = {
371
371
  sourceCode?: string | null;
372
372
  artifact?: PlayBundleArtifact | null;
373
373
  codeFormat?: 'function' | 'cjs_module' | 'esm_module';
374
+ /**
375
+ * The runtime must receive the child contract with its artifact. Inline
376
+ * composition rejects unresolved or dataset-backed children before running
377
+ * their code, so dropping this field at the API boundary makes valid scalar
378
+ * children indistinguishable from unknown ones.
379
+ */
380
+ staticPipeline?: PlayStaticPipeline | null;
374
381
  contractSnapshot?: Record<string, unknown> | null;
382
+ /** Preview-only contract propagation evidence, emitted by the server and
383
+ * logged by the runner while diagnosing a cross-runtime child resolution
384
+ * failure. It intentionally contains no source, inputs, or credentials. */
385
+ resolutionDiagnostics?: {
386
+ serverPlayPipeline: boolean;
387
+ manifestPipeline: boolean;
388
+ responsePipeline: boolean;
389
+ } | null;
375
390
  };
376
391
 
377
392
  export type PrepareRuntimeSheetResult = {
@@ -522,6 +537,14 @@ function resolveRuntimeApiHeaders(
522
537
  'content-type': 'application/json',
523
538
  authorization: `Bearer ${token}`,
524
539
  [PLAY_RUNTIME_CONTRACT_HEADER]: String(PLAY_RUNTIME_CONTRACT),
540
+ // A run has many callback HTTP attempts. Preserve the logical run
541
+ // correlation in headers while Vercel keeps its own per-attempt request id.
542
+ ...(context.runId?.trim()
543
+ ? {
544
+ 'x-deepline-run-id': context.runId.trim(),
545
+ 'x-deepline-correlation-id': `run:${context.runId.trim()}`,
546
+ }
547
+ : {}),
525
548
  ...vercelHeaders,
526
549
  ...(context.runtimeTestFaultHeader?.trim()
527
550
  ? {
@@ -677,7 +700,7 @@ function runtimeApiMaxAttempts(
677
700
  }
678
701
 
679
702
  export function isTransientRuntimeApiErrorMessage(message: string): boolean {
680
- return /timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|requested endpoint could not be found, or you don't have access|RUNTIME_SCHEDULER_SATURATED|Runtime scheduler DB pool is saturated|Runtime scheduler DB circuit breaker is open/i.test(
703
+ return /timeout exceeded when trying to connect|timed out|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT|requested endpoint could not be found, or you don't have access|RUNTIME_SCHEDULER_(?:SATURATED|UNAVAILABLE)|Runtime scheduler DB pool is saturated|Runtime scheduler DB circuit breaker is open/i.test(
681
704
  message,
682
705
  );
683
706
  }
@@ -1779,11 +1802,22 @@ function isMissingRelationError(error: unknown): boolean {
1779
1802
  }
1780
1803
 
1781
1804
  function isPostgresPermissionDeniedError(error: unknown): boolean {
1782
- return (
1783
- error !== null &&
1784
- typeof error === 'object' &&
1785
- 'code' in error &&
1786
- String(error.code) === '42501'
1805
+ if (error === null || typeof error !== 'object') {
1806
+ return false;
1807
+ }
1808
+ if ('code' in error && String(error.code) === '42501') {
1809
+ return true;
1810
+ }
1811
+ const message =
1812
+ 'message' in error && typeof error.message === 'string'
1813
+ ? error.message
1814
+ : '';
1815
+ // Postgres errors raised by a direct session retain SQLSTATE 42501. The
1816
+ // same failure crossing the runtime API boundary is intentionally rendered
1817
+ // as a safe Error message, so preserve the exact permission-denied shape as
1818
+ // the repair signal without treating arbitrary 500s as storage drift.
1819
+ return /permission denied for (?:database|schema|relation|table|sequence)/i.test(
1820
+ message,
1787
1821
  );
1788
1822
  }
1789
1823
 
@@ -2932,15 +2966,28 @@ export async function resolveRuntimeReferencedPlay(
2932
2966
  playRef: string,
2933
2967
  options?: { artifactKind?: PlayArtifactKind },
2934
2968
  ): Promise<ResolvedRuntimePlay | null> {
2935
- const response = await postRuntimeApi<{ play: ResolvedRuntimePlay | null }>(
2936
- context,
2937
- {
2938
- action: 'resolve_play',
2939
- playRef,
2940
- ...(options?.artifactKind ? { artifactKind: options.artifactKind } : {}),
2941
- },
2942
- );
2943
- return response.play;
2969
+ const response = await postRuntimeApi<{
2970
+ play: ResolvedRuntimePlay | null;
2971
+ manifest?: { staticPipeline?: PlayStaticPipeline | null } | null;
2972
+ }>(context, {
2973
+ action: 'resolve_play',
2974
+ playRef,
2975
+ ...(options?.artifactKind ? { artifactKind: options.artifactKind } : {}),
2976
+ });
2977
+ if (!response.play) {
2978
+ return null;
2979
+ }
2980
+
2981
+ // The server builds the manifest and child artifact together. Its manifest
2982
+ // carries the canonical empty scalar contract when a published scalar's
2983
+ // revision snapshot has no pipeline. Keep that contract on the object the
2984
+ // runner passes to ctx.runPlay; otherwise a valid scalar leaf arrives as an
2985
+ // indistinguishable "missing static contract" at runtime.
2986
+ return {
2987
+ ...response.play,
2988
+ staticPipeline:
2989
+ response.play.staticPipeline ?? response.manifest?.staticPipeline ?? null,
2990
+ };
2944
2991
  }
2945
2992
 
2946
2993
  export async function ensureRuntimeSheet(
@@ -2951,12 +2998,28 @@ export async function ensureRuntimeSheet(
2951
2998
  sheetContract: PlaySheetContract;
2952
2999
  },
2953
3000
  ): Promise<void> {
2954
- await postRuntimeApi<{ ok: true }>(context, {
2955
- action: 'ensure_sheet',
3001
+ const request = {
3002
+ action: 'ensure_sheet' as const,
2956
3003
  ...input,
2957
3004
  runId: context.runId ?? null,
2958
3005
  userEmail: normalizeRuntimeUserEmail(context.userEmail),
2959
- });
3006
+ };
3007
+ try {
3008
+ await postRuntimeApi<{ ok: true }>(context, request);
3009
+ } catch (error) {
3010
+ if (!isPostgresPermissionDeniedError(error)) {
3011
+ throw error;
3012
+ }
3013
+ // Fresh tenant databases can become visible through the pooled endpoint
3014
+ // before their runtime-role grants are usable. Non-preloaded file runs hit
3015
+ // ensure_sheet before any direct Postgres operation, so the existing
3016
+ // operation-level self-heal cannot observe this boundary. Repair the
3017
+ // canonical storage contract once, then retry the exact ensure request.
3018
+ await repairRuntimeStorageGrants(context, {
3019
+ playName: input.playName,
3020
+ });
3021
+ await postRuntimeApi<{ ok: true }>(context, request);
3022
+ }
2960
3023
  }
2961
3024
 
2962
3025
  async function prepareRuntimeSheetDatasetRows(
@@ -17,7 +17,6 @@ import type {
17
17
  PlayRowUpdate,
18
18
  } from './ctx-types';
19
19
  import type { ExecutionPlan } from './execution-plan';
20
- import type { PlayRuntimeManifestMap } from '../plays/compiler-manifest';
21
20
  import type { PreloadedRuntimeDbSession } from './db-session';
22
21
  import type { RuntimeAuthorityDescriptor } from './execution-capabilities';
23
22
  import type { PlayRunnerRuntimeTiming } from './protocol';
@@ -42,39 +41,6 @@ export type PlaySchedulerBackendId =
42
41
 
43
42
  export type { PlayRunInputPayload };
44
43
 
45
- export type PlayCallGovernanceSnapshot = {
46
- rootRunId: string;
47
- parentRunId: string;
48
- parentPlayName: string;
49
- key: string;
50
- ancestryPlayIds: string[];
51
- callDepth: number;
52
- /**
53
- * Cumulative lineage-global budget counters consumed by ancestors at the
54
- * moment this child was launched. The child seeds its own counters from these
55
- * so the corresponding budgets (`maxPlayCallCount`, `maxToolCallCount`,
56
- * `maxRetryCount`, `maxDescendants`, `maxWaterfallStepExecutions`) accumulate
57
- * down the dispatch lineage instead of resetting to 0 in each worker isolate —
58
- * matching the cjs forkChild path, which threads all of them. The Governor
59
- * documents these budgets as global across the nested-play tree, not
60
- * per-worker (see policy.ts / rate-state-backend.ts); threading them here is
61
- * what makes that true on `esm_workers`.
62
- *
63
- * `descendantCount` is load-bearing for fan-out: forkChild charges
64
- * `descendant` on every child launch, so without threading it a deep/wide tree
65
- * would reset descendant accounting at each isolate and never converge on the
66
- * lineage-global cap.
67
- *
68
- * All optional and fail-safe: if absent (older callers / dropped in transit)
69
- * the child falls back to 0, i.e. prior behavior.
70
- */
71
- playCallCount?: number;
72
- toolCallCount?: number;
73
- retryCount?: number;
74
- descendantCount?: number;
75
- waterfallStepExecutions?: number;
76
- };
77
-
78
44
  export type PlaySchedulerSubmitInput = {
79
45
  runId: string;
80
46
  playId: string;
@@ -122,8 +88,6 @@ export type PlaySchedulerSubmitInput = {
122
88
  }> | null;
123
89
  contractSnapshot?: unknown;
124
90
  executionPlan?: ExecutionPlan | null;
125
- childPlayManifests?: PlayRuntimeManifestMap | null;
126
- playCallGovernance?: PlayCallGovernanceSnapshot | null;
127
91
  preloadedDbSessions?: PreloadedRuntimeDbSession[] | null;
128
92
  /**
129
93
  * Secret that decrypts `preloadedDbSessions` postgres URLs. Equals the run's
@@ -18,20 +18,6 @@ export type PlayExecutionSuspension =
18
18
  timeoutMs: number;
19
19
  }>;
20
20
  }
21
- | {
22
- kind: 'child_play';
23
- boundaryId: string;
24
- childRunId: string;
25
- childPlayName: string;
26
- }
27
- | {
28
- kind: 'child_play_batch';
29
- boundaries: Array<{
30
- boundaryId: string;
31
- childRunId: string;
32
- childPlayName: string;
33
- }>;
34
- }
35
21
  | {
36
22
  /**
37
23
  * Push-execution park (B2-final): the Daytona runner command was started
@@ -72,13 +58,9 @@ export class PlayExecutionSuspendedError extends Error {
72
58
  ? `Play execution suspended for durable sleep (${suspension.delayMs}ms).`
73
59
  : suspension.kind === 'integration_event_batch'
74
60
  ? `Play execution suspended waiting for ${suspension.boundaries.length} integration events.`
75
- : suspension.kind === 'child_play_batch'
76
- ? `Play execution suspended waiting for ${suspension.boundaries.length} child plays.`
77
- : suspension.kind === 'child_play'
78
- ? `Play execution suspended waiting for child play ${JSON.stringify(suspension.childPlayName)}.`
79
- : suspension.kind === 'detached_runner'
80
- ? `Play execution parked on detached runner ${suspension.cmdId} (sandbox ${suspension.sandboxId}).`
81
- : `Play execution suspended waiting for integration event ${JSON.stringify(suspension.eventKey)}.`,
61
+ : suspension.kind === 'detached_runner'
62
+ ? `Play execution parked on detached runner ${suspension.cmdId} (sandbox ${suspension.sandboxId}).`
63
+ : `Play execution suspended waiting for integration event ${JSON.stringify(suspension.eventKey)}.`,
82
64
  );
83
65
  this.name = 'PlayExecutionSuspendedError';
84
66
  this.suspension = suspension;
@@ -5,7 +5,14 @@ export const PLAY_RUNTIME_TEST_FAULT_HEADER = 'x-deepline-test-fault';
5
5
  export type RuntimeTestFaultName =
6
6
  | 'receipt_complete_write_fail'
7
7
  | 'receipt_fail_write_fail'
8
- | 'worker_receipt_complete_write_fail';
8
+ | 'worker_receipt_complete_write_fail'
9
+ /**
10
+ * Holds an already checked-out receipt-gateway scheduler client for the
11
+ * supplied bounded millisecond value. Preview/CI only: this creates a
12
+ * deterministic real-Pg-pool pressure window without changing an action's
13
+ * SQL or outcome.
14
+ */
15
+ | 'receipt_gateway_hold_ms';
9
16
 
10
17
  export type RuntimeTestFaultRegistry = {
11
18
  consume(name: RuntimeTestFaultName): boolean;
@@ -63,6 +70,7 @@ const SUPPORTED_RUNTIME_TEST_FAULTS = new Set<RuntimeTestFaultName>([
63
70
  'receipt_complete_write_fail',
64
71
  'receipt_fail_write_fail',
65
72
  'worker_receipt_complete_write_fail',
73
+ 'receipt_gateway_hold_ms',
66
74
  ]);
67
75
 
68
76
  const RUNTIME_TEST_POLICY_MS_FIELDS = new Set([
@@ -0,0 +1,63 @@
1
+ import { redactLedgerString } from './ledger-safe-payload';
2
+
3
+ type ErrorLike = Record<string, unknown>;
4
+
5
+ export type TransportErrorDiagnostic = {
6
+ name: string | null;
7
+ message: string | null;
8
+ code: string | null;
9
+ errno: string | null;
10
+ syscall: string | null;
11
+ cause: TransportErrorDiagnostic | null;
12
+ };
13
+
14
+ function asErrorLike(value: unknown): ErrorLike | null {
15
+ return value !== null && typeof value === 'object'
16
+ ? (value as ErrorLike)
17
+ : null;
18
+ }
19
+
20
+ function safeString(value: unknown): string | null {
21
+ if (typeof value !== 'string' && typeof value !== 'number') return null;
22
+ return redactLedgerString(String(value))?.slice(0, 500) ?? null;
23
+ }
24
+
25
+ export function transportGatewayOriginForDiagnostic(url: string): string {
26
+ try {
27
+ return new URL(url).origin;
28
+ } catch {
29
+ return '<invalid-gateway-origin>';
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Make a small, secret-redacted transport diagnostic that can safely cross the
35
+ * runner -> gateway -> durable run-log boundary. Keep stacks in process logs:
36
+ * serializing them into run state leaks implementation details and is rarely
37
+ * useful without the original error's code/errno/cause chain.
38
+ */
39
+ export function describeTransportError(
40
+ error: unknown,
41
+ depth = 0,
42
+ ): TransportErrorDiagnostic {
43
+ const value = asErrorLike(error);
44
+ const errorValue = error instanceof Error ? error : null;
45
+ const name =
46
+ safeString(errorValue?.name ?? value?.name) ?? (value ? 'Error' : null);
47
+ const message = safeString(errorValue?.message ?? value?.message ?? error);
48
+ const cause = value?.cause;
49
+
50
+ return {
51
+ name,
52
+ message,
53
+ code: safeString(value?.code),
54
+ errno: safeString(value?.errno),
55
+ syscall: safeString(value?.syscall),
56
+ // A short cause chain is enough for fetch/undici errors and prevents a
57
+ // pathological custom error graph from making durable traces unbounded.
58
+ cause:
59
+ cause !== undefined && depth < 2
60
+ ? describeTransportError(cause, depth + 1)
61
+ : null,
62
+ };
63
+ }
@@ -586,6 +586,8 @@ export type PlayStaticSubstep = PlayStaticSubstepMetadata &
586
586
  type: 'play_call';
587
587
  playId: string;
588
588
  execution?: 'inline' | 'child-workflow';
589
+ timeoutMs?: number;
590
+ hasExplicitTimeout?: boolean;
589
591
  field: string;
590
592
  inLoop?: boolean;
591
593
  pipeline?: PlayStaticPipeline | null;