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
@@ -20,11 +20,15 @@ export type WorkflowStepLike = {
20
20
  };
21
21
 
22
22
  export type ChildTerminalCoordinator = {
23
- readChildTerminalState(
23
+ readChildTerminalState?(
24
24
  parentRunId: string,
25
25
  eventKey: string,
26
26
  timeoutMs?: number,
27
27
  ): Promise<{ data?: unknown } | null>;
28
+ readRunTerminalState(
29
+ runId: string,
30
+ timeoutMs?: number,
31
+ ): Promise<ChildRunTerminalSnapshotState | null>;
28
32
  };
29
33
 
30
34
  export type ChildPlayTerminalWaitResult = {
@@ -34,6 +38,52 @@ export type ChildPlayTerminalWaitResult = {
34
38
  waitMs: number;
35
39
  };
36
40
 
41
+ export type ChildRunTerminalSnapshotState = {
42
+ data?: unknown;
43
+ reason?: string;
44
+ status?: string;
45
+ runId?: string;
46
+ playName?: string;
47
+ parentRunId?: string | null;
48
+ };
49
+
50
+ export class ChildPlayTerminalVerificationError extends Error {
51
+ readonly parentRunId: string;
52
+ readonly eventKey: string;
53
+ readonly cause: unknown;
54
+
55
+ constructor(input: {
56
+ parentRunId: string;
57
+ eventKey: string;
58
+ cause: unknown;
59
+ }) {
60
+ const causeMessage =
61
+ input.cause instanceof Error
62
+ ? input.cause.message
63
+ : String(input.cause ?? '');
64
+ super(
65
+ `Child play terminal verification failed for parent run ${input.parentRunId} and event ${input.eventKey}.${
66
+ causeMessage ? ` Cause: ${causeMessage}` : ''
67
+ }`,
68
+ );
69
+ this.name = 'ChildPlayTerminalVerificationError';
70
+ this.parentRunId = input.parentRunId;
71
+ this.eventKey = input.eventKey;
72
+ this.cause = input.cause;
73
+ }
74
+ }
75
+
76
+ const CHILD_TERMINAL_DURABLE_VERIFY_GRACE_MS = 5_000;
77
+ const CHILD_TERMINAL_DURABLE_VERIFY_POLL_MS = 250;
78
+ const CHILD_TERMINAL_DURABLE_VERIFY_READ_TIMEOUT_MS = 3_000;
79
+ const CHILD_TERMINAL_DURABLE_VERIFY_MIN_READ_TIMEOUT_MS = 2_000;
80
+ const CHILD_TERMINAL_PERMANENT_RUNTIME_API_STATUS_RE =
81
+ /runtime API (400|401|403|404):/i;
82
+ const CHILD_TERMINAL_RETRYABLE_RUNTIME_API_STATUS_RE =
83
+ /runtime API (408|429|500|502|503|504|530):/i;
84
+ const CHILD_TERMINAL_RETRYABLE_ERROR_RE =
85
+ /timed out|timeout|fetch failed|ECONNRESET|ECONNREFUSED|UND_ERR_CONNECT_TIMEOUT/i;
86
+
37
87
  export interface AwaitChildTerminalInput {
38
88
  parentRunId: string;
39
89
  workflowStep: WorkflowStepLike | undefined;
@@ -41,7 +91,7 @@ export interface AwaitChildTerminalInput {
41
91
  playName: string;
42
92
  key: string;
43
93
  timeoutMs: number;
44
- /** Coordinator binding for the terminal-state poll. Null disables the poll. */
94
+ /** Coordinator binding for the terminal-state poll and durable run verification. */
45
95
  coordinator: ChildTerminalCoordinator | null;
46
96
  now: () => number;
47
97
  /** SHA-256 hex digest helper (same canonical hash entry.ts uses). */
@@ -84,6 +134,27 @@ function extractChildPlayOutput(status: Record<string, unknown>): unknown {
84
134
  return result ?? null;
85
135
  }
86
136
 
137
+ function terminalPayloadFromState(
138
+ state: { data?: unknown } | null | undefined,
139
+ ): Record<string, unknown> | null {
140
+ return isRecord(state?.data) ? state.data : null;
141
+ }
142
+
143
+ function isRetryableChildTerminalSnapshotError(error: unknown): boolean {
144
+ const message = error instanceof Error ? error.message : String(error);
145
+ if (CHILD_TERMINAL_PERMANENT_RUNTIME_API_STATUS_RE.test(message)) {
146
+ return false;
147
+ }
148
+ return (
149
+ CHILD_TERMINAL_RETRYABLE_RUNTIME_API_STATUS_RE.test(message) ||
150
+ CHILD_TERMINAL_RETRYABLE_ERROR_RE.test(message)
151
+ );
152
+ }
153
+
154
+ function sleep(ms: number): Promise<void> {
155
+ return new Promise((resolve) => setTimeout(resolve, ms));
156
+ }
157
+
87
158
  async function childPlayEventKey(input: {
88
159
  key: string;
89
160
  workflowId: string;
@@ -120,9 +191,19 @@ async function pollParentChildTerminalState(input: {
120
191
  input.timeoutMs - (input.now() - startedAt),
121
192
  );
122
193
  const waitMs = Math.min(remainingMs, 30_000);
123
- const state = await coordinator
124
- .readChildTerminalState(parentRunId, input.eventKey, waitMs)
125
- .catch(() => null);
194
+ let state: { data?: unknown } | null;
195
+ try {
196
+ // prettier-ignore
197
+ state = await coordinator.readChildTerminalState(parentRunId, input.eventKey, waitMs);
198
+ } catch {
199
+ // The parent-child cache is a safety net for missed workflow events.
200
+ // Transient cache read failures must not beat the event wait plus durable
201
+ // child run verification path in the race.
202
+ if (remainingMs > 0) {
203
+ await sleep(Math.min(remainingMs, CHILD_TERMINAL_DURABLE_VERIFY_POLL_MS));
204
+ }
205
+ continue;
206
+ }
126
207
  if (isRecord(state?.data)) {
127
208
  return {
128
209
  source: 'parent_child_terminal_cache',
@@ -134,6 +215,78 @@ async function pollParentChildTerminalState(input: {
134
215
  return await new Promise(() => undefined);
135
216
  }
136
217
 
218
+ async function readVerifiedChildTerminalPayload(input: {
219
+ coordinator: ChildTerminalCoordinator;
220
+ workflowId: string;
221
+ timeoutMs: number;
222
+ }): Promise<{
223
+ payload: Record<string, unknown> | null;
224
+ lastState: ChildRunTerminalSnapshotState | null;
225
+ lastError: unknown;
226
+ }> {
227
+ const graceMs = Math.max(
228
+ 1,
229
+ Math.min(input.timeoutMs, CHILD_TERMINAL_DURABLE_VERIFY_GRACE_MS),
230
+ );
231
+ const pollMs = Math.max(
232
+ 1,
233
+ Math.min(
234
+ CHILD_TERMINAL_DURABLE_VERIFY_POLL_MS,
235
+ Math.max(1, Math.floor(graceMs / 2)),
236
+ ),
237
+ );
238
+ const deadlineMs = Date.now() + graceMs;
239
+ let lastState: ChildRunTerminalSnapshotState | null = null;
240
+ let lastError: unknown = null;
241
+ const minReadTimeoutMs =
242
+ graceMs >= CHILD_TERMINAL_DURABLE_VERIFY_MIN_READ_TIMEOUT_MS
243
+ ? CHILD_TERMINAL_DURABLE_VERIFY_MIN_READ_TIMEOUT_MS
244
+ : 1;
245
+ while (Date.now() <= deadlineMs) {
246
+ const remainingMs = Math.max(0, deadlineMs - Date.now());
247
+ if (remainingMs < minReadTimeoutMs) break;
248
+ const readTimeoutMs = Math.max(
249
+ minReadTimeoutMs,
250
+ Math.min(remainingMs, CHILD_TERMINAL_DURABLE_VERIFY_READ_TIMEOUT_MS),
251
+ );
252
+ try {
253
+ const state = await input.coordinator.readRunTerminalState(
254
+ input.workflowId,
255
+ readTimeoutMs,
256
+ );
257
+ lastState = state ?? null;
258
+ const payload = terminalPayloadFromState(state);
259
+ if (payload) return { payload, lastState, lastError };
260
+ } catch (error) {
261
+ lastError = error;
262
+ if (!isRetryableChildTerminalSnapshotError(error)) {
263
+ throw error;
264
+ }
265
+ }
266
+ const sleepMs = Math.min(
267
+ pollMs,
268
+ Math.max(0, deadlineMs - Date.now() - minReadTimeoutMs),
269
+ );
270
+ if (sleepMs <= 0) break;
271
+ await sleep(sleepMs);
272
+ }
273
+ return { payload: null, lastState, lastError };
274
+ }
275
+
276
+ function describeSnapshotState(
277
+ state: ChildRunTerminalSnapshotState | null,
278
+ ): string {
279
+ if (!state) return 'no snapshot response';
280
+ const parts = [
281
+ state.reason ? `reason=${state.reason}` : null,
282
+ state.status ? `status=${state.status}` : null,
283
+ state.runId ? `runId=${state.runId}` : null,
284
+ state.playName ? `playName=${state.playName}` : null,
285
+ state.parentRunId ? `parentRunId=${state.parentRunId}` : null,
286
+ ].filter((part): part is string => Boolean(part));
287
+ return parts.length > 0 ? parts.join(' ') : 'empty snapshot response';
288
+ }
289
+
137
290
  /**
138
291
  * Block until the child run reaches a terminal state, via the workflow event
139
292
  * wait raced against the coordinator terminal-state poll. Throws on a non-
@@ -153,6 +306,15 @@ export async function awaitChildTerminal(
153
306
  workflowId: input.workflowId,
154
307
  hashJson: input.hashJson,
155
308
  });
309
+ if (!input.coordinator?.readRunTerminalState) {
310
+ throw new ChildPlayTerminalVerificationError({
311
+ parentRunId: input.parentRunId,
312
+ eventKey,
313
+ cause: new Error(
314
+ `Child run ${input.workflowId} terminal snapshot reader was not available.`,
315
+ ),
316
+ });
317
+ }
156
318
  const eventPromise = input.workflowStep
157
319
  .waitForEvent(`child_play_terminal:${eventKey}`, {
158
320
  type: integrationEventType(eventKey),
@@ -173,7 +335,42 @@ export async function awaitChildTerminal(
173
335
  now: input.now,
174
336
  }),
175
337
  ]);
176
- const payload = terminal.payload;
338
+ let verified:
339
+ | {
340
+ payload: Record<string, unknown> | null;
341
+ lastState: ChildRunTerminalSnapshotState | null;
342
+ lastError: unknown;
343
+ }
344
+ | null = null;
345
+ try {
346
+ verified = await readVerifiedChildTerminalPayload({
347
+ coordinator: input.coordinator,
348
+ workflowId: input.workflowId,
349
+ timeoutMs: input.timeoutMs,
350
+ });
351
+ } catch (error) {
352
+ throw new ChildPlayTerminalVerificationError({
353
+ parentRunId: input.parentRunId,
354
+ eventKey,
355
+ cause: error,
356
+ });
357
+ }
358
+ if (!verified?.payload) {
359
+ const lastErrorMessage =
360
+ verified?.lastError instanceof Error
361
+ ? ` lastError=${verified.lastError.message}`
362
+ : verified?.lastError
363
+ ? ` lastError=${String(verified.lastError)}`
364
+ : '';
365
+ throw new ChildPlayTerminalVerificationError({
366
+ parentRunId: input.parentRunId,
367
+ eventKey,
368
+ cause: new Error(
369
+ `Child run ${input.workflowId} terminal snapshot was not available after durable verification grace; ${describeSnapshotState(verified?.lastState ?? null)}.${lastErrorMessage}`,
370
+ ),
371
+ });
372
+ }
373
+ const payload = verified.payload;
177
374
  const status = String(payload.status ?? '').toLowerCase();
178
375
  if (status === 'completed') {
179
376
  return {
@@ -142,6 +142,12 @@ export async function submitChildPlayThroughCoordinator(
142
142
  input.req.runId,
143
143
  )}/submit-child`;
144
144
  const serializedBody = JSON.stringify(body);
145
+ const requestId = input.makeRequestId();
146
+ const idempotencyKey =
147
+ typeof body.childIdempotencyKey === 'string' &&
148
+ body.childIdempotencyKey.trim()
149
+ ? body.childIdempotencyKey.trim()
150
+ : requestId;
145
151
  let lastError: unknown = null;
146
152
 
147
153
  for (let attempt = 0; attempt <= retryDelaysMs.length; attempt += 1) {
@@ -150,7 +156,8 @@ export async function submitChildPlayThroughCoordinator(
150
156
  response = await fetchFn(url, {
151
157
  method: 'POST',
152
158
  headers: {
153
- 'x-deepline-request-id': input.makeRequestId(),
159
+ 'x-deepline-request-id': requestId,
160
+ 'x-deepline-idempotency-key': idempotencyKey,
154
161
  ...input.coordinatorRequestHeaders({
155
162
  runId: input.req.runId,
156
163
  contentType: 'application/json',