deepline 0.1.182 → 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 (66) 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 +1747 -1890
  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/receipts.ts +292 -11
  9. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +519 -0
  10. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +2381 -0
  11. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +18 -5
  12. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +130 -0
  13. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +39 -0
  14. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +2 -0
  15. package/dist/bundling-sources/sdk/src/client.ts +41 -0
  16. package/dist/bundling-sources/sdk/src/http.ts +23 -8
  17. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +56 -3
  18. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  19. package/dist/bundling-sources/sdk/src/types.ts +2 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +132 -10
  21. package/dist/bundling-sources/shared_libs/play-runtime/auth-scope-resolver.ts +92 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -4
  23. package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +8 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +101 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1605 -281
  26. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +105 -6
  27. package/dist/bundling-sources/shared_libs/play-runtime/dedup-backend.ts +0 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +1 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +4 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +116 -16
  31. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +186 -20
  32. package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +2 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +133 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +245 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -5
  36. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +14 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +52 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +41 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +3 -2
  40. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +124 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +6 -2
  42. package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +48 -2
  43. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +158 -15
  44. package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +45 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +9 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2037 -262
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
  51. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
  53. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
  58. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
  59. package/dist/cli/index.js +76 -17
  60. package/dist/cli/index.mjs +76 -17
  61. package/dist/index.d.mts +2 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +63 -9
  64. package/dist/index.mjs +63 -9
  65. package/dist/plays/bundle-play-file.mjs +2 -2
  66. package/package.json +1 -1
@@ -6,11 +6,13 @@ import {
6
6
  export type WorkerToolReceiptSelection = {
7
7
  claimableReceiptKey: string | null;
8
8
  forceDurableRefresh: boolean;
9
+ forceFailedDurableRefresh: boolean;
9
10
  };
10
11
 
11
12
  export type WorkerToolReceiptGroup<TRequest> = {
12
13
  claimableReceiptKey: string;
13
14
  forceDurableRefresh: boolean;
15
+ forceFailedDurableRefresh: boolean;
14
16
  requests: TRequest[];
15
17
  };
16
18
 
@@ -155,6 +157,7 @@ export function selectWorkerToolReceipt(input: {
155
157
  durableReceiptKey?: string | null;
156
158
  localRetryCacheKey: string;
157
159
  force: boolean;
160
+ forceFailed?: boolean;
158
161
  allowLocalRetryReceipts: boolean;
159
162
  }): WorkerToolReceiptSelection {
160
163
  const durableReceiptKey = input.durableReceiptKey?.trim() || null;
@@ -165,6 +168,10 @@ export function selectWorkerToolReceipt(input: {
165
168
  return {
166
169
  claimableReceiptKey: durableReceiptKey ?? localRetryReceiptKey,
167
170
  forceDurableRefresh: input.force === true && durableReceiptKey !== null,
171
+ forceFailedDurableRefresh:
172
+ input.force !== true &&
173
+ input.forceFailed === true &&
174
+ durableReceiptKey !== null,
168
175
  };
169
176
  }
170
177
 
@@ -176,17 +183,21 @@ export function planWorkerToolReceiptGroups<TRequest>(
176
183
  durableReceiptKey?: string | null;
177
184
  localRetryCacheKey: string;
178
185
  force: boolean;
186
+ forceFailed?: boolean;
179
187
  };
180
188
  },
181
189
  ): WorkerToolReceiptGroupPlan<TRequest> {
182
190
  const localRequests: TRequest[] = [];
183
191
  const groupsByKey = new Map<string, WorkerToolReceiptGroup<TRequest>>();
184
192
  for (const request of requests) {
185
- const { claimableReceiptKey, forceDurableRefresh } =
186
- selectWorkerToolReceipt({
187
- ...input.getReceiptInput(request),
188
- allowLocalRetryReceipts: input.allowLocalRetryReceipts,
189
- });
193
+ const {
194
+ claimableReceiptKey,
195
+ forceDurableRefresh,
196
+ forceFailedDurableRefresh,
197
+ } = selectWorkerToolReceipt({
198
+ ...input.getReceiptInput(request),
199
+ allowLocalRetryReceipts: input.allowLocalRetryReceipts,
200
+ });
190
201
  if (!claimableReceiptKey) {
191
202
  localRequests.push(request);
192
203
  continue;
@@ -194,10 +205,12 @@ export function planWorkerToolReceiptGroups<TRequest>(
194
205
  const group = groupsByKey.get(claimableReceiptKey) ?? {
195
206
  claimableReceiptKey,
196
207
  forceDurableRefresh: false,
208
+ forceFailedDurableRefresh: false,
197
209
  requests: [],
198
210
  };
199
211
  group.requests.push(request);
200
212
  group.forceDurableRefresh ||= forceDurableRefresh;
213
+ group.forceFailedDurableRefresh ||= forceFailedDurableRefresh;
201
214
  groupsByKey.set(claimableReceiptKey, group);
202
215
  }
203
216
  return {
@@ -0,0 +1,130 @@
1
+ import { WORKER_PLATFORM_YIELD_LIMITS } from './worker-platform-budget';
2
+
3
+ export type WorkerWorkBudgetEventKind =
4
+ | 'tool'
5
+ | 'provider'
6
+ | 'receipt'
7
+ | 'heartbeat'
8
+ | 'sheet'
9
+ | 'log'
10
+ | 'egress';
11
+
12
+ export type WorkerWorkBudgetSnapshot = {
13
+ startedAt: number;
14
+ updatedAt: number;
15
+ elapsedMs: number;
16
+ counts: Record<WorkerWorkBudgetEventKind, number>;
17
+ elapsedMsByKind: Record<WorkerWorkBudgetEventKind, number>;
18
+ };
19
+
20
+ export type WorkerWorkBudgetYieldReason =
21
+ | 'elapsed'
22
+ | 'subrequest'
23
+ | 'provider'
24
+ | 'tool'
25
+ | 'receipt'
26
+ | 'heartbeat'
27
+ | 'sheet'
28
+ | 'log'
29
+ | 'egress';
30
+
31
+ export type WorkerWorkBudgetYieldDecision = {
32
+ shouldYield: boolean;
33
+ reason: WorkerWorkBudgetYieldReason | null;
34
+ snapshot: WorkerWorkBudgetSnapshot;
35
+ };
36
+
37
+ export type WorkerWorkBudgetYieldLimits = Partial<
38
+ Record<WorkerWorkBudgetYieldReason, number>
39
+ >;
40
+
41
+ export type WorkerWorkBudgetMeter = {
42
+ count(kind: WorkerWorkBudgetEventKind, amount?: number): void;
43
+ snapshot(): WorkerWorkBudgetSnapshot;
44
+ resetWindow(): void;
45
+ };
46
+
47
+ const WORK_BUDGET_EVENT_KINDS: WorkerWorkBudgetEventKind[] = [
48
+ 'tool',
49
+ 'provider',
50
+ 'receipt',
51
+ 'heartbeat',
52
+ 'sheet',
53
+ 'log',
54
+ 'egress',
55
+ ];
56
+
57
+ function zeroCounters(): Record<WorkerWorkBudgetEventKind, number> {
58
+ return Object.fromEntries(
59
+ WORK_BUDGET_EVENT_KINDS.map((kind) => [kind, 0]),
60
+ ) as Record<WorkerWorkBudgetEventKind, number>;
61
+ }
62
+
63
+ export function createWorkerWorkBudgetMeter(input?: {
64
+ nowMs?: () => number;
65
+ }): WorkerWorkBudgetMeter {
66
+ const nowMs = input?.nowMs ?? Date.now;
67
+ let startedAt = nowMs();
68
+ let updatedAt = startedAt;
69
+ let counts = zeroCounters();
70
+ let elapsedMsByKind = zeroCounters();
71
+
72
+ return {
73
+ count(kind, amount = 1) {
74
+ counts[kind] += amount;
75
+ updatedAt = nowMs();
76
+ },
77
+ snapshot() {
78
+ const now = nowMs();
79
+ return {
80
+ startedAt,
81
+ updatedAt,
82
+ elapsedMs: Math.max(0, now - startedAt),
83
+ counts: { ...counts },
84
+ elapsedMsByKind: { ...elapsedMsByKind },
85
+ };
86
+ },
87
+ resetWindow() {
88
+ startedAt = nowMs();
89
+ updatedAt = startedAt;
90
+ counts = zeroCounters();
91
+ elapsedMsByKind = zeroCounters();
92
+ },
93
+ };
94
+ }
95
+
96
+ export const WORKER_WORK_BUDGET_YIELD_LIMITS = WORKER_PLATFORM_YIELD_LIMITS;
97
+
98
+ export function shouldYieldWorkerWorkBudget(input: {
99
+ meter: WorkerWorkBudgetMeter;
100
+ limits?: WorkerWorkBudgetYieldLimits;
101
+ projectedSubrequests?: number;
102
+ }): WorkerWorkBudgetYieldDecision {
103
+ const snapshot = input.meter.snapshot();
104
+ const limits = {
105
+ ...WORKER_WORK_BUDGET_YIELD_LIMITS,
106
+ ...(input.limits ?? {}),
107
+ };
108
+ if (snapshot.elapsedMs >= limits.elapsed) {
109
+ return { shouldYield: true, reason: 'elapsed', snapshot };
110
+ }
111
+ const subrequests =
112
+ snapshot.counts.provider +
113
+ snapshot.counts.receipt +
114
+ snapshot.counts.heartbeat +
115
+ snapshot.counts.sheet +
116
+ snapshot.counts.egress;
117
+ const projectedSubrequests = Math.max(
118
+ 0,
119
+ Math.floor(input.projectedSubrequests ?? 0),
120
+ );
121
+ if (subrequests + projectedSubrequests >= limits.subrequest) {
122
+ return { shouldYield: true, reason: 'subrequest', snapshot };
123
+ }
124
+ for (const kind of WORK_BUDGET_EVENT_KINDS) {
125
+ if (snapshot.counts[kind] >= limits[kind]) {
126
+ return { shouldYield: true, reason: kind, snapshot };
127
+ }
128
+ }
129
+ return { shouldYield: false, reason: null, snapshot };
130
+ }
@@ -0,0 +1,39 @@
1
+ import type { WorkerWorkBudgetYieldLimits } from './work-budget';
2
+
3
+ // Preview accounts can still enforce the free/internal-service subrequest
4
+ // ceiling even when wrangler config asks for more. This is a planner budget,
5
+ // not the advertised platform ceiling: unbatched tool chunks also spend
6
+ // subrequests on sheet writes, progress/ledger flushes, service-binding hops,
7
+ // and workflow replay around the visible provider calls. Individual log lines
8
+ // are buffered and batched into those flushes; they are not one subrequest each.
9
+ // Keep real headroom here;
10
+ // paid deployments may raise it only once the active account limit is proven by
11
+ // an E2E run, not by config alone.
12
+ export const WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT = 700;
13
+
14
+ // Fresh unbatched tool calls spend more than the visible provider RPC:
15
+ // durable receipt claim/complete, provider execution, route/service-binding
16
+ // hops, and row progress/sheet writes all share the Worker subrequest pool.
17
+ export const WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL = 8;
18
+
19
+ export const WORKER_PLATFORM_YIELD_LIMITS: Required<WorkerWorkBudgetYieldLimits> =
20
+ {
21
+ elapsed: 45_000,
22
+ subrequest: WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
23
+ provider: WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
24
+ tool: 1_500,
25
+ receipt: 2_000,
26
+ heartbeat: 1_000,
27
+ sheet: 800,
28
+ log: 1_500,
29
+ egress: 1_000,
30
+ };
31
+
32
+ export function estimateUnbatchedToolCallSubrequests(input: {
33
+ toolCalls: number;
34
+ }): number {
35
+ return (
36
+ Math.max(0, Math.floor(input.toolCalls)) *
37
+ WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL
38
+ );
39
+ }
@@ -27,6 +27,7 @@ export type WorkflowRetryParamsRef = {
27
27
 
28
28
  export type WorkflowRetryPlayParams = {
29
29
  runId: string;
30
+ runAttempt?: number;
30
31
  playId: string;
31
32
  playName: string;
32
33
  artifactStorageKey: string;
@@ -72,6 +73,7 @@ export type WorkflowRetryPlayParams = {
72
73
  totalRows?: number;
73
74
  coordinatorUrl?: string | null;
74
75
  coordinatorInternalToken?: string | null;
76
+ runtimeTestFaultHeader?: string | null;
75
77
  };
76
78
 
77
79
  export type WorkflowRetryStatePayload<TParams = WorkflowRetryPlayParams> =
@@ -109,6 +109,41 @@ function resolvePlayRunIntegrationMode(
109
109
  );
110
110
  }
111
111
 
112
+ function normalizeTestPolicyOverrides(
113
+ value: unknown,
114
+ source: string,
115
+ ): Record<string, unknown> {
116
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
117
+ return value as Record<string, unknown>;
118
+ }
119
+ throw new DeeplineError(
120
+ `${source} must be a JSON object.`,
121
+ undefined,
122
+ 'INVALID_TEST_POLICY_OVERRIDES',
123
+ );
124
+ }
125
+
126
+ function parseEnvTestPolicyOverrides(): Record<string, unknown> | undefined {
127
+ const raw =
128
+ typeof process !== 'undefined'
129
+ ? process.env?.DEEPLINE_TEST_POLICY_OVERRIDES
130
+ : undefined;
131
+ const trimmed = raw?.trim();
132
+ if (!trimmed) return undefined;
133
+ let parsed: unknown;
134
+ try {
135
+ parsed = JSON.parse(trimmed);
136
+ } catch (error) {
137
+ const detail = error instanceof Error ? ` ${error.message}` : '';
138
+ throw new DeeplineError(
139
+ `DEEPLINE_TEST_POLICY_OVERRIDES must be valid JSON.${detail}`,
140
+ undefined,
141
+ 'INVALID_TEST_POLICY_OVERRIDES',
142
+ );
143
+ }
144
+ return normalizeTestPolicyOverrides(parsed, 'DEEPLINE_TEST_POLICY_OVERRIDES');
145
+ }
146
+
112
147
  function sleep(ms: number): Promise<void> {
113
148
  return new Promise((resolve) => setTimeout(resolve, ms));
114
149
  }
@@ -1426,6 +1461,8 @@ export class DeeplineClient {
1426
1461
  */
1427
1462
  async startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart> {
1428
1463
  const integrationMode = resolvePlayRunIntegrationMode(request);
1464
+ const testPolicyOverrides =
1465
+ request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
1429
1466
  const forceToolRefresh = request.forceToolRefresh === true;
1430
1467
  const response = await this.http.post<Record<string, unknown>>(
1431
1468
  '/api/v2/plays/run',
@@ -1470,6 +1507,7 @@ export class DeeplineClient {
1470
1507
  // different profile pass `request.profile` explicitly.
1471
1508
  ...(request.profile ? { profile: request.profile } : {}),
1472
1509
  ...(integrationMode ? { integrationMode } : {}),
1510
+ ...(testPolicyOverrides ? { testPolicyOverrides } : {}),
1473
1511
  },
1474
1512
  );
1475
1513
  return normalizePlayRunStart(response);
@@ -1491,6 +1529,8 @@ export class DeeplineClient {
1491
1529
  options?: { signal?: AbortSignal },
1492
1530
  ): AsyncGenerator<PlayLiveEvent> {
1493
1531
  const integrationMode = resolvePlayRunIntegrationMode(request);
1532
+ const testPolicyOverrides =
1533
+ request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
1494
1534
  const forceToolRefresh = request.forceToolRefresh === true;
1495
1535
  const body = {
1496
1536
  ...(request.name ? { name: request.name } : {}),
@@ -1530,6 +1570,7 @@ export class DeeplineClient {
1530
1570
  : {}),
1531
1571
  ...(request.profile ? { profile: request.profile } : {}),
1532
1572
  ...(integrationMode ? { integrationMode } : {}),
1573
+ ...(testPolicyOverrides ? { testPolicyOverrides } : {}),
1533
1574
  };
1534
1575
  for await (const event of this.http.streamSse<PlayLiveEvent>(
1535
1576
  '/api/v2/plays/run?stream=true',
@@ -34,6 +34,7 @@ import {
34
34
  SYNTHETIC_RUN_HEADER,
35
35
  WORKER_CALLBACK_URL_OVERRIDE_HEADER,
36
36
  } from '../../shared_libs/play-runtime/coordinator-headers.js';
37
+ import { PLAY_RUNTIME_TEST_FAULT_HEADER } from '../../shared_libs/play-runtime/test-runtime-seams.js';
37
38
 
38
39
  const MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
39
40
  const COWORK_NETWORK_HINT =
@@ -240,16 +241,12 @@ export class HttpClient {
240
241
  typeof process !== 'undefined'
241
242
  ? process.env?.DEEPLINE_COORDINATOR_URL
242
243
  : undefined;
244
+ const coordinatorInternalToken =
245
+ typeof process !== 'undefined'
246
+ ? process.env?.DEEPLINE_INTERNAL_TOKEN
247
+ : undefined;
243
248
  if (coordinatorUrl?.trim()) {
244
249
  headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
245
- const coordinatorInternalToken =
246
- typeof process !== 'undefined'
247
- ? process.env?.DEEPLINE_INTERNAL_TOKEN
248
- : undefined;
249
- if (coordinatorInternalToken?.trim()) {
250
- headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
251
- coordinatorInternalToken.trim();
252
- }
253
250
  }
254
251
  const workerCallbackUrl =
255
252
  typeof process !== 'undefined'
@@ -276,6 +273,24 @@ export class HttpClient {
276
273
  if (syntheticRun && syntheticRun.trim() && syntheticRun.trim() !== '0') {
277
274
  headers[SYNTHETIC_RUN_HEADER] = '1';
278
275
  }
276
+ // Test-only runtime seam. The server validates both environment and value
277
+ // before honoring this header.
278
+ const runtimeTestFault =
279
+ typeof process !== 'undefined'
280
+ ? process.env?.DEEPLINE_TEST_RUNTIME_FAULT
281
+ : undefined;
282
+ if (runtimeTestFault?.trim()) {
283
+ headers[PLAY_RUNTIME_TEST_FAULT_HEADER] = runtimeTestFault.trim();
284
+ }
285
+ if (
286
+ coordinatorInternalToken?.trim() &&
287
+ (coordinatorUrl?.trim() ||
288
+ workerCallbackUrl?.trim() ||
289
+ runtimeTestFault?.trim())
290
+ ) {
291
+ headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
292
+ coordinatorInternalToken.trim();
293
+ }
279
294
  return headers;
280
295
  }
281
296
 
@@ -36,10 +36,18 @@ import type {
36
36
  CompleteRuntimeReceiptsInput,
37
37
  FailRuntimeReceiptInput,
38
38
  FailRuntimeReceiptsInput,
39
+ HeartbeatRuntimeReceiptsInput,
40
+ ReleaseRuntimeReceiptInput,
39
41
  RuntimeApiCallInput,
40
42
  RuntimeApiCallResult,
41
43
  RuntimeReceiptInput,
42
44
  RuntimeReceiptsInput,
45
+ SheetAttemptHeartbeatInput,
46
+ SheetAttemptHeartbeatResult,
47
+ SheetAttemptReleaseInput,
48
+ SheetAttemptReleaseResult,
49
+ WorkReceiptReleaseInput,
50
+ WorkReceiptReleaseResult,
43
51
  SheetDatasetRowKeysInput,
44
52
  SheetDatasetRowKeysResult,
45
53
  SheetDatasetRowsInput,
@@ -94,7 +102,7 @@ function requireBinding(): HarnessBinding {
94
102
  if (!cachedBinding) {
95
103
  throw new Error(
96
104
  '[harness-stub] env.HARNESS is not wired. The coordinator must pass HARNESS in the WorkerLoader factory env. ' +
97
- 'See apps/play-runner-workers/src/coordinator-entry.ts loadDynamicPlayWorkerSync.',
105
+ 'See apps/play-runner-workers/src/coordinator-entry.ts -> loadDynamicPlayWorkerSync.',
98
106
  );
99
107
  }
100
108
  return cachedBinding;
@@ -157,7 +165,7 @@ export async function harnessCompleteRuntimeReceipt(
157
165
 
158
166
  export async function harnessCompleteRuntimeReceipts(
159
167
  input: CompleteRuntimeReceiptsInput,
160
- ): Promise<WorkReceipt[]> {
168
+ ): Promise<Array<WorkReceipt | null>> {
161
169
  return requireBinding().completeRuntimeReceipts(input);
162
170
  }
163
171
 
@@ -167,12 +175,24 @@ export async function harnessFailRuntimeReceipt(
167
175
  return requireBinding().failRuntimeReceipt(input);
168
176
  }
169
177
 
178
+ export async function harnessReleaseRuntimeReceipt(
179
+ input: ReleaseRuntimeReceiptInput,
180
+ ): Promise<WorkReceipt | null> {
181
+ return requireBinding().releaseRuntimeReceipt(input);
182
+ }
183
+
170
184
  export async function harnessFailRuntimeReceipts(
171
185
  input: FailRuntimeReceiptsInput,
172
- ): Promise<WorkReceipt[]> {
186
+ ): Promise<Array<WorkReceipt | null>> {
173
187
  return requireBinding().failRuntimeReceipts(input);
174
188
  }
175
189
 
190
+ export async function harnessHeartbeatRuntimeReceipts(
191
+ input: HeartbeatRuntimeReceiptsInput,
192
+ ): Promise<Array<WorkReceipt | null>> {
193
+ return requireBinding().heartbeatRuntimeReceipts(input);
194
+ }
195
+
176
196
  /**
177
197
  * Read a bounded staged-file byte range through typed harness RPC. This is the
178
198
  * only staged-file data path for per-play Workers; there is intentionally no
@@ -197,6 +217,24 @@ export async function harnessReadSheetDatasetRowKeys(
197
217
  return requireBinding().readSheetDatasetRowKeys(input);
198
218
  }
199
219
 
220
+ export async function harnessHeartbeatSheetAttempt(
221
+ input: SheetAttemptHeartbeatInput,
222
+ ): Promise<SheetAttemptHeartbeatResult> {
223
+ return requireBinding().heartbeatSheetAttempt(input);
224
+ }
225
+
226
+ export async function harnessReleaseSheetAttempt(
227
+ input: SheetAttemptReleaseInput,
228
+ ): Promise<SheetAttemptReleaseResult> {
229
+ return requireBinding().releaseSheetAttempt(input);
230
+ }
231
+
232
+ export async function harnessReleaseRuntimeReceipts(
233
+ input: WorkReceiptReleaseInput,
234
+ ): Promise<WorkReceiptReleaseResult> {
235
+ return requireBinding().releaseRuntimeReceipts(input);
236
+ }
237
+
200
238
  /**
201
239
  * Warm Postgres sessions in the long-lived harness Worker. This preserves the
202
240
  * map fast path without bundling the Neon client into every dynamic play.
@@ -224,6 +262,11 @@ export async function harnessStartSheetDataset(input: {
224
262
  sheetContract: unknown;
225
263
  rows: Array<Record<string, unknown>>;
226
264
  runId: string;
265
+ attemptId?: string | null;
266
+ attemptOwnerRunId?: string | null;
267
+ attemptExpiresAt?: string | null;
268
+ attemptSeq?: number | null;
269
+ attemptLeaseTtlMs?: number | null;
227
270
  inputOffset?: number;
228
271
  force?: boolean;
229
272
  userEmail?: string | null;
@@ -232,7 +275,12 @@ export async function harnessStartSheetDataset(input: {
232
275
  skipped: number;
233
276
  pendingRows: Array<Record<string, unknown>>;
234
277
  completedRows: Array<Record<string, unknown>>;
278
+ blockedRows: Array<Record<string, unknown>>;
235
279
  tableNamespace: string;
280
+ attemptId?: string;
281
+ attemptOwnerRunId?: string;
282
+ attemptExpiresAt?: string;
283
+ attemptSeq?: number;
236
284
  timings?: Array<Record<string, unknown>>;
237
285
  }> {
238
286
  return requireBinding().startSheetDataset(input);
@@ -253,6 +301,11 @@ export async function harnessPersistCompletedSheetRows(input: {
253
301
  rows: Array<Record<string, unknown>>;
254
302
  outputFields: string[];
255
303
  runId: string;
304
+ attemptId?: string | null;
305
+ attemptOwnerRunId?: string | null;
306
+ attemptExpiresAt?: string | null;
307
+ attemptSeq?: number | null;
308
+ forceFailedRows?: boolean;
256
309
  userEmail?: string | null;
257
310
  }): Promise<{ ok: true; rowsWritten: number; tableNamespace: string }> {
258
311
  return requireBinding().persistCompletedMapRows(input);
@@ -105,10 +105,10 @@ export const SDK_RELEASE = {
105
105
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
106
106
  // fields shipped in 0.1.153.
107
107
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
108
- version: '0.1.182',
108
+ version: '0.1.183',
109
109
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
110
110
  supportPolicy: {
111
- latest: '0.1.182',
111
+ latest: '0.1.183',
112
112
  minimumSupported: '0.1.53',
113
113
  deprecatedBelow: '0.1.53',
114
114
  commandMinimumSupported: [
@@ -1010,6 +1010,8 @@ export interface StartPlayRunRequest {
1010
1010
  profile?: string;
1011
1011
  /** Optional per-run provider execution mode for eval/smoke runs. */
1012
1012
  integrationMode?: 'live' | 'eval_stub' | 'fixture';
1013
+ /** Internal/dev-only runtime policy overrides for black-box durability tests. */
1014
+ testPolicyOverrides?: Record<string, unknown>;
1013
1015
  }
1014
1016
 
1015
1017
  /**