deepline 0.1.212 → 0.1.214

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 (41) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +167 -329
  2. package/dist/bundling-sources/sdk/src/client.ts +2 -2
  3. package/dist/bundling-sources/sdk/src/index.ts +2 -0
  4. package/dist/bundling-sources/sdk/src/play.ts +8 -1
  5. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +11 -1
  6. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  7. package/dist/bundling-sources/sdk/src/types.ts +3 -3
  8. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +49 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-placement.ts +14 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +57 -23
  11. package/dist/bundling-sources/shared_libs/play-runtime/completed-receipt-cache.ts +166 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +209 -123
  13. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +2 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +30 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +160 -2
  16. package/dist/bundling-sources/shared_libs/play-runtime/gateway-auth-session.ts +93 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/play-call-execution.ts +1 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +1 -1
  19. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +16 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +10 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +80 -3
  22. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +94 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +54 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +477 -85
  25. package/dist/bundling-sources/shared_libs/play-runtime/runtime-postgres-admission.ts +162 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -2
  27. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +16 -17
  28. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
  29. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +2 -4
  30. package/dist/cli/index.js +50 -436
  31. package/dist/cli/index.mjs +50 -436
  32. package/dist/{compiler-manifest-j6z8qzpd.d.mts → compiler-manifest-BhgZ23A4.d.mts} +1 -0
  33. package/dist/{compiler-manifest-j6z8qzpd.d.ts → compiler-manifest-BhgZ23A4.d.ts} +1 -0
  34. package/dist/index.d.mts +11 -8
  35. package/dist/index.d.ts +11 -8
  36. package/dist/index.js +4 -4
  37. package/dist/index.mjs +4 -4
  38. package/dist/plays/bundle-play-file.d.mts +2 -2
  39. package/dist/plays/bundle-play-file.d.ts +2 -2
  40. package/dist/plays/bundle-play-file.mjs +1 -1
  41. package/package.json +1 -1
@@ -1563,8 +1563,8 @@ export class DeeplineClient {
1563
1563
  ? { waitForCompletionMs: request.waitForCompletionMs }
1564
1564
  : {}),
1565
1565
  // Profile selection is the API's job, not the CLI's. The server
1566
- // defaults to workers_edge; tests and runtime probes that want a
1567
- // different profile pass `request.profile` explicitly.
1566
+ // defaults to absurd; callers that need workers_edge pass
1567
+ // `request.profile` explicitly.
1568
1568
  ...(request.profile ? { profile: request.profile } : {}),
1569
1569
  ...(integrationMode ? { integrationMode } : {}),
1570
1570
  ...(testPolicyOverrides ? { testPolicyOverrides } : {}),
@@ -184,6 +184,8 @@ export type {
184
184
  PreviousCell,
185
185
  PlayDataset,
186
186
  PlayDatasetInput,
187
+ PlayCallExecution,
188
+ PlayCallOptions,
187
189
  PlayStepProgramStep,
188
190
  PrebuiltPlayRef,
189
191
  StepOptions,
@@ -114,6 +114,13 @@ import type {
114
114
  } from './types.js';
115
115
  import type { ToolExecution } from './client.js';
116
116
 
117
+ export type PlayCallExecution = 'inline' | 'child-workflow';
118
+
119
+ export interface PlayCallOptions {
120
+ description?: string;
121
+ execution?: PlayCallExecution;
122
+ }
123
+
117
124
  /**
118
125
  * Optional trigger bindings for a play.
119
126
  *
@@ -891,7 +898,7 @@ export interface DeeplinePlayRuntimeContext {
891
898
  key: string,
892
899
  playRef: string | PlayReferenceLike,
893
900
  input: Record<string, unknown>,
894
- options: { description: string },
901
+ options?: PlayCallOptions,
895
902
  ): Promise<TOutput>;
896
903
 
897
904
  /**
@@ -274,6 +274,7 @@ export async function harnessStartSheetDataset(input: {
274
274
  attemptExpiresAt?: string | null;
275
275
  attemptSeq?: number | null;
276
276
  attemptLeaseTtlMs?: number | null;
277
+ writeVersion?: number | null;
277
278
  inputOffset?: number;
278
279
  force?: boolean;
279
280
  userEmail?: string | null;
@@ -288,6 +289,7 @@ export async function harnessStartSheetDataset(input: {
288
289
  attemptOwnerRunId?: string;
289
290
  attemptExpiresAt?: string;
290
291
  attemptSeq?: number;
292
+ writeVersion?: number;
291
293
  timings?: Array<Record<string, unknown>>;
292
294
  }> {
293
295
  return requireBinding().startSheetDataset(input);
@@ -312,8 +314,16 @@ export async function harnessPersistCompletedSheetRows(input: {
312
314
  attemptOwnerRunId?: string | null;
313
315
  attemptExpiresAt?: string | null;
314
316
  attemptSeq?: number | null;
317
+ writeVersion?: number | null;
315
318
  forceFailedRows?: boolean;
316
319
  userEmail?: string | null;
317
- }): Promise<{ ok: true; rowsWritten: number; tableNamespace: string }> {
320
+ }): Promise<{
321
+ ok: true;
322
+ rowsWritten: number;
323
+ tableNamespace: string;
324
+ fencedKeys?: string[];
325
+ staleDropped?: number;
326
+ staleDroppedKeys?: string[];
327
+ }> {
318
328
  return requireBinding().persistCompletedMapRows(input);
319
329
  }
@@ -106,10 +106,10 @@ export const SDK_RELEASE = {
106
106
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
107
107
  // fields shipped in 0.1.153.
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
- version: '0.1.212',
109
+ version: '0.1.214',
110
110
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
111
111
  supportPolicy: {
112
- latest: '0.1.212',
112
+ latest: '0.1.214',
113
113
  minimumSupported: '0.1.53',
114
114
  deprecatedBelow: '0.1.53',
115
115
  commandMinimumSupported: [
@@ -1138,9 +1138,9 @@ export interface StartPlayRunRequest {
1138
1138
  /** Optionally let the start request wait briefly and return a terminal result. */
1139
1139
  waitForCompletionMs?: number;
1140
1140
  /**
1141
- * Per-run execution profile override. The server defaults to workers_edge;
1142
- * tests and runtime probes can pass a different profile here. Most callers
1143
- * should leave this unset.
1141
+ * Per-run execution profile override. The server defaults to absurd;
1142
+ * callers that need workers_edge can pass it here. Most callers should leave
1143
+ * this unset.
1144
1144
  */
1145
1145
  profile?: string;
1146
1146
  /** Optional per-run provider execution mode for eval/smoke runs. */
@@ -207,11 +207,27 @@ type RuntimeApiRequest =
207
207
  runId: string;
208
208
  key: string;
209
209
  }
210
+ | {
211
+ action: 'acquire_runtime_receipt_execution_lock';
212
+ playName: string;
213
+ runId: string;
214
+ key: string;
215
+ ownerExecutionId: string;
216
+ ttlMs: number;
217
+ }
218
+ | {
219
+ action: 'release_runtime_receipt_execution_lock';
220
+ playName: string;
221
+ runId: string;
222
+ key: string;
223
+ ownerExecutionId: string;
224
+ }
210
225
  | {
211
226
  action: 'claim_runtime_step_receipt';
212
227
  playName: string;
213
228
  runId: string;
214
229
  key: string;
230
+ leaseId?: string;
215
231
  runAttempt?: number | null;
216
232
  leaseAware?: boolean;
217
233
  reclaimRunning?: boolean;
@@ -265,6 +281,7 @@ type RuntimeApiRequest =
265
281
  playName: string;
266
282
  runId: string;
267
283
  keys: string[];
284
+ leaseIds?: string[];
268
285
  runAttempt?: number | null;
269
286
  leaseAware?: boolean;
270
287
  reclaimRunning?: boolean;
@@ -1332,6 +1349,38 @@ export async function getRuntimeStepReceiptViaAppRuntime(
1332
1349
  });
1333
1350
  }
1334
1351
 
1352
+ export async function acquireRuntimeReceiptExecutionLockViaAppRuntime(
1353
+ context: WorkerRuntimeApiContext,
1354
+ input: Omit<
1355
+ Extract<
1356
+ RuntimeApiRequest,
1357
+ { action: 'acquire_runtime_receipt_execution_lock' }
1358
+ >,
1359
+ 'action'
1360
+ >,
1361
+ ): Promise<{ ownerExecutionId: string; expiresAt: string } | null> {
1362
+ return await postAppRuntimeApi(context, {
1363
+ action: 'acquire_runtime_receipt_execution_lock',
1364
+ ...input,
1365
+ });
1366
+ }
1367
+
1368
+ export async function releaseRuntimeReceiptExecutionLockViaAppRuntime(
1369
+ context: WorkerRuntimeApiContext,
1370
+ input: Omit<
1371
+ Extract<
1372
+ RuntimeApiRequest,
1373
+ { action: 'release_runtime_receipt_execution_lock' }
1374
+ >,
1375
+ 'action'
1376
+ >,
1377
+ ): Promise<boolean> {
1378
+ return await postAppRuntimeApi(context, {
1379
+ action: 'release_runtime_receipt_execution_lock',
1380
+ ...input,
1381
+ });
1382
+ }
1383
+
1335
1384
  export async function getRuntimeStepReceiptsViaAppRuntime(
1336
1385
  context: WorkerRuntimeApiContext,
1337
1386
  input: Omit<
@@ -0,0 +1,14 @@
1
+ import type { PlayCallExecution } from './play-call-execution';
2
+
3
+ export function resolveChildExecutionPlacement(input: {
4
+ requiresWorkflow: boolean;
5
+ execution?: PlayCallExecution | null;
6
+ }): { strategy: 'inline' | 'scheduled' } {
7
+ return {
8
+ strategy:
9
+ input.execution === 'child-workflow' ||
10
+ (input.execution !== 'inline' && input.requiresWorkflow)
11
+ ? 'scheduled'
12
+ : 'inline',
13
+ };
14
+ }
@@ -2,6 +2,8 @@ import {
2
2
  flattenStaticPipeline,
3
3
  type PlayStaticPipeline,
4
4
  } from '../plays/static-pipeline';
5
+ import type { PlayCallExecution } from './play-call-execution';
6
+ import { resolveChildExecutionPlacement } from './child-execution-placement';
5
7
 
6
8
  export type ChildExecutionStrategy = 'inline' | 'scheduled';
7
9
 
@@ -12,7 +14,9 @@ export type ChildExecutionDecision = {
12
14
  | 'missing_static_contract'
13
15
  | 'dataset_child'
14
16
  | 'event_wait_child'
15
- | 'explicit_timeout';
17
+ | 'explicit_timeout'
18
+ | 'explicit_inline'
19
+ | 'explicit_child_workflow';
16
20
  };
17
21
 
18
22
  /**
@@ -23,31 +27,61 @@ export type ChildExecutionDecision = {
23
27
  export function resolveChildExecutionStrategy(input: {
24
28
  pipeline: PlayStaticPipeline | null | undefined;
25
29
  timeoutMs?: number | null;
30
+ execution?: PlayCallExecution | null;
31
+ childPlayName?: string | null;
26
32
  }): ChildExecutionDecision {
27
- if (input.timeoutMs != null) {
28
- return { strategy: 'scheduled', reason: 'explicit_timeout' };
33
+ if (input.execution === 'child-workflow') {
34
+ return { strategy: 'scheduled', reason: 'explicit_child_workflow' };
29
35
  }
30
- if (!input.pipeline) {
31
- return { strategy: 'scheduled', reason: 'missing_static_contract' };
36
+ const steps = input.pipeline ? flattenStaticPipeline(input.pipeline) : [];
37
+ const authoredDecision: ChildExecutionDecision =
38
+ input.timeoutMs != null
39
+ ? { strategy: 'scheduled', reason: 'explicit_timeout' }
40
+ : !input.pipeline
41
+ ? { strategy: 'scheduled', reason: 'missing_static_contract' }
42
+ : input.pipeline.tableNamespace ||
43
+ input.pipeline.sheetContract ||
44
+ steps.some((step) => step.type === 'dataset' || step.type === 'csv')
45
+ ? { strategy: 'scheduled', reason: 'dataset_child' }
46
+ : steps.some(
47
+ (step) =>
48
+ step.type === 'tool' &&
49
+ (step.isEventWait || step.toolId === 'test_wait_for_event'),
50
+ )
51
+ ? { strategy: 'scheduled', reason: 'event_wait_child' }
52
+ : { strategy: 'inline', reason: 'scalar_child' };
53
+ if (input.execution === 'inline') {
54
+ if (
55
+ authoredDecision.strategy === 'scheduled' &&
56
+ authoredDecision.reason !== 'missing_static_contract'
57
+ ) {
58
+ throw new Error(
59
+ inlineExecutionRequiresScalarChildMessage(
60
+ input.childPlayName?.trim() || 'child',
61
+ authoredDecision.reason,
62
+ ),
63
+ );
64
+ }
65
+ return { strategy: 'inline', reason: 'explicit_inline' };
32
66
  }
33
- const substeps = flattenStaticPipeline(input.pipeline);
34
- if (
35
- input.pipeline.tableNamespace ||
36
- input.pipeline.sheetContract ||
37
- substeps.some((step) => step.type === 'dataset' || step.type === 'csv')
38
- ) {
39
- return { strategy: 'scheduled', reason: 'dataset_child' };
67
+ const placement = resolveChildExecutionPlacement({
68
+ requiresWorkflow: authoredDecision.strategy === 'scheduled',
69
+ execution: input.execution,
70
+ });
71
+ if (placement.strategy === 'inline') {
72
+ return { strategy: 'inline', reason: 'scalar_child' };
40
73
  }
41
- if (
42
- substeps.some(
43
- (step) =>
44
- step.type === 'tool' &&
45
- (step.isEventWait === true || step.toolId === 'test_wait_for_event'),
46
- )
47
- ) {
48
- return { strategy: 'scheduled', reason: 'event_wait_child' };
49
- }
50
- return { strategy: 'inline', reason: 'scalar_child' };
74
+ return authoredDecision;
75
+ }
76
+
77
+ export function inlineExecutionRequiresScalarChildMessage(
78
+ childPlayName: string,
79
+ reason: ChildExecutionDecision['reason'],
80
+ ): string {
81
+ return (
82
+ `ctx.runPlay("${childPlayName}") requested execution: "inline", but the child requires a child workflow ` +
83
+ `(${reason}). Remove the explicit execution option or use execution: "child-workflow".`
84
+ );
51
85
  }
52
86
 
53
87
  /**
@@ -63,7 +97,7 @@ export function inlineChildScheduledInRowMessage(
63
97
  return (
64
98
  `ctx.runPlay("${childPlayName}") runs per row in the "${rowContextField}" batch, ` +
65
99
  'but it is a scheduling-required child (it uses ctx.dataset()/ctx.csv(), waits on an ' +
66
- 'event, or sets an explicit timeout). Row-scoped children must be scalar. ' +
100
+ 'event, sets an explicit timeout, or explicitly requests a child workflow). Row-scoped children must be scalar. ' +
67
101
  'Give the batch ONE dataset child at play level instead of fanning a scheduled run out per row.'
68
102
  );
69
103
  }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * A completed receipt is an immutable cache fact. Execution coordination is a
3
+ * separate, short-lived concern and must never be represented by a "running"
4
+ * receipt.
5
+ */
6
+ export type CompletedReceipt<T> = {
7
+ key: string;
8
+ output: T;
9
+ createdAt: string;
10
+ expiresAt?: string | null;
11
+ };
12
+
13
+ export type CompletedReceiptCache<T> = {
14
+ get(key: string): Promise<CompletedReceipt<T> | null>;
15
+ /**
16
+ * Insert only when the key is absent. On conflict, return the already stored
17
+ * immutable fact. Implementations should use INSERT ... ON CONFLICT DO
18
+ * NOTHING followed by a point read.
19
+ */
20
+ putIfAbsent(receipt: CompletedReceipt<T>): Promise<CompletedReceipt<T>>;
21
+ };
22
+
23
+ export type ReceiptExecutionLock = {
24
+ receiptKey: string;
25
+ ownerExecutionId: string;
26
+ expiresAt: string;
27
+ };
28
+
29
+ export type ReceiptExecutionLockStore = {
30
+ tryAcquire(input: {
31
+ receiptKey: string;
32
+ ownerExecutionId: string;
33
+ expiresAt: string;
34
+ }): Promise<ReceiptExecutionLock | null>;
35
+ release(input: {
36
+ receiptKey: string;
37
+ ownerExecutionId: string;
38
+ }): Promise<void>;
39
+ };
40
+
41
+ export type ReceiptMissCoordination =
42
+ | { kind: 'provider_idempotency' }
43
+ | {
44
+ kind: 'execution_lock';
45
+ store: ReceiptExecutionLockStore;
46
+ ownerExecutionId: string;
47
+ lockTtlMs: number;
48
+ waitTimeoutMs: number;
49
+ pollIntervalMs?: number;
50
+ };
51
+
52
+ export class ReceiptExecutionLockTimeoutError extends Error {
53
+ constructor(receiptKey: string, waitTimeoutMs: number) {
54
+ super(
55
+ `Timed out waiting ${waitTimeoutMs}ms for receipt execution lock ${receiptKey}.`,
56
+ );
57
+ this.name = 'ReceiptExecutionLockTimeoutError';
58
+ }
59
+ }
60
+
61
+ const MAX_EXECUTION_LOCK_TTL_MS = 10 * 60_000;
62
+
63
+ function assertBoundedLockPolicy(
64
+ policy: Extract<ReceiptMissCoordination, { kind: 'execution_lock' }>,
65
+ ): void {
66
+ if (
67
+ !Number.isFinite(policy.lockTtlMs) ||
68
+ policy.lockTtlMs <= 0 ||
69
+ policy.lockTtlMs > MAX_EXECUTION_LOCK_TTL_MS
70
+ ) {
71
+ throw new Error(
72
+ `Receipt execution lock TTL must be between 1 and ${MAX_EXECUTION_LOCK_TTL_MS}ms.`,
73
+ );
74
+ }
75
+ if (!Number.isFinite(policy.waitTimeoutMs) || policy.waitTimeoutMs <= 0) {
76
+ throw new Error('Receipt execution lock wait timeout must be positive.');
77
+ }
78
+ }
79
+
80
+ function sleep(ms: number): Promise<void> {
81
+ return new Promise((resolve) => setTimeout(resolve, ms));
82
+ }
83
+
84
+ async function executeAndPublish<T>(input: {
85
+ receiptKey: string;
86
+ cache: CompletedReceiptCache<T>;
87
+ execute: () => Promise<T>;
88
+ now: () => number;
89
+ receiptTtlMs?: number | null;
90
+ }): Promise<T> {
91
+ const output = await input.execute();
92
+ const now = input.now();
93
+ const receipt = await input.cache.putIfAbsent({
94
+ key: input.receiptKey,
95
+ output,
96
+ createdAt: new Date(now).toISOString(),
97
+ ...(input.receiptTtlMs
98
+ ? { expiresAt: new Date(now + input.receiptTtlMs).toISOString() }
99
+ : {}),
100
+ });
101
+ return receipt.output;
102
+ }
103
+
104
+ /**
105
+ * Execute one semantic operation against an immutable completed-receipt cache.
106
+ *
107
+ * Providers with native idempotency do not need a Deepline execution lock:
108
+ * concurrent misses may dispatch with the same provider idempotency key, then
109
+ * converge on the first immutable cache insert. A separate temporary lock is
110
+ * reserved for actions explicitly classified as dangerous non-idempotent side
111
+ * effects. Ordinary enrichment misses may do bounded duplicate work and
112
+ * converge on the first published receipt.
113
+ */
114
+ export async function executeWithCompletedReceiptCache<T>(input: {
115
+ receiptKey: string;
116
+ cache: CompletedReceiptCache<T>;
117
+ missCoordination: ReceiptMissCoordination;
118
+ execute: () => Promise<T>;
119
+ now?: () => number;
120
+ receiptTtlMs?: number | null;
121
+ }): Promise<T> {
122
+ const cached = await input.cache.get(input.receiptKey);
123
+ if (cached) return cached.output;
124
+
125
+ const now = input.now ?? Date.now;
126
+ if (input.missCoordination.kind === 'provider_idempotency') {
127
+ return await executeAndPublish({ ...input, now });
128
+ }
129
+
130
+ const policy = input.missCoordination;
131
+ assertBoundedLockPolicy(policy);
132
+ const deadline = now() + policy.waitTimeoutMs;
133
+ const pollIntervalMs = Math.max(1, policy.pollIntervalMs ?? 100);
134
+
135
+ while (true) {
136
+ const lock = await policy.store.tryAcquire({
137
+ receiptKey: input.receiptKey,
138
+ ownerExecutionId: policy.ownerExecutionId,
139
+ expiresAt: new Date(now() + policy.lockTtlMs).toISOString(),
140
+ });
141
+ if (lock) {
142
+ try {
143
+ // The previous owner may have published between our cache read and
144
+ // lock acquisition. Never execute after that terminal fact exists.
145
+ const afterAcquire = await input.cache.get(input.receiptKey);
146
+ if (afterAcquire) return afterAcquire.output;
147
+ return await executeAndPublish({ ...input, now });
148
+ } finally {
149
+ await policy.store.release({
150
+ receiptKey: input.receiptKey,
151
+ ownerExecutionId: policy.ownerExecutionId,
152
+ });
153
+ }
154
+ }
155
+
156
+ const completed = await input.cache.get(input.receiptKey);
157
+ if (completed) return completed.output;
158
+ if (now() >= deadline) {
159
+ throw new ReceiptExecutionLockTimeoutError(
160
+ input.receiptKey,
161
+ policy.waitTimeoutMs,
162
+ );
163
+ }
164
+ await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - now())));
165
+ }
166
+ }