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
@@ -0,0 +1,437 @@
1
+ export type WorkReceiptStateStatus =
2
+ | 'pending'
3
+ | 'running'
4
+ | 'completed'
5
+ | 'failed'
6
+ | 'skipped';
7
+
8
+ export type WorkReceiptStateInput = {
9
+ status: WorkReceiptStateStatus;
10
+ runId?: string | null;
11
+ failureKind?: 'terminal' | 'repairable' | null;
12
+ leaseId?: string | null;
13
+ leaseOwnerRunId?: string | null;
14
+ leaseOwnerAttempt?: number | null;
15
+ leaseExpiresAt?: string | null;
16
+ };
17
+
18
+ export type WorkReceiptLeaseState =
19
+ | { kind: 'none' }
20
+ | {
21
+ kind: 'active';
22
+ leaseId: string;
23
+ ownerRunId: string;
24
+ ownerAttempt: number;
25
+ expiresAt: string;
26
+ expiresAtMs: number;
27
+ }
28
+ | {
29
+ kind: 'expired';
30
+ leaseId: string | null;
31
+ ownerRunId: string;
32
+ ownerAttempt: number;
33
+ expiresAt: string;
34
+ expiresAtMs: number;
35
+ }
36
+ | {
37
+ kind: 'invalid';
38
+ reason:
39
+ | 'missing_owner'
40
+ | 'missing_expiry'
41
+ | 'missing_lease_id'
42
+ | 'invalid_expiry';
43
+ leaseId: string | null;
44
+ ownerRunId: string | null;
45
+ expiresAt: string | null;
46
+ };
47
+
48
+ export type WorkReceiptInvalidLeaseReason = Extract<
49
+ WorkReceiptLeaseState,
50
+ { kind: 'invalid' }
51
+ >['reason'];
52
+
53
+ export type WorkReceiptState =
54
+ | {
55
+ kind: 'pending';
56
+ terminal: false;
57
+ reusable: false;
58
+ lease: WorkReceiptLeaseState;
59
+ }
60
+ | {
61
+ kind: 'running';
62
+ terminal: false;
63
+ reusable: false;
64
+ lease: WorkReceiptLeaseState;
65
+ }
66
+ | {
67
+ kind: 'completed';
68
+ terminal: true;
69
+ reusable: true;
70
+ lease: WorkReceiptLeaseState;
71
+ }
72
+ | {
73
+ kind: 'skipped';
74
+ terminal: true;
75
+ reusable: true;
76
+ lease: WorkReceiptLeaseState;
77
+ }
78
+ | {
79
+ kind: 'failed';
80
+ terminal: true;
81
+ reusable: false;
82
+ lease: WorkReceiptLeaseState;
83
+ };
84
+
85
+ export type WorkReceiptClaimDecision =
86
+ | { kind: 'claim' }
87
+ | { kind: 'reuse_terminal' }
88
+ | {
89
+ kind: 'blocked_active_lease';
90
+ status: WorkReceiptStateStatus;
91
+ ownerRunId: string | null;
92
+ leaseId: string | null;
93
+ }
94
+ | {
95
+ kind: 'blocked_running';
96
+ ownerRunId: string | null;
97
+ leaseId: string | null;
98
+ }
99
+ | {
100
+ kind: 'blocked_invalid_lease';
101
+ reason: WorkReceiptInvalidLeaseReason;
102
+ status: WorkReceiptStateStatus;
103
+ }
104
+ | { kind: 'blocked_failed' };
105
+
106
+ export type WorkReceiptOwnerOperationDecision =
107
+ | { kind: 'allow' }
108
+ | {
109
+ kind: 'reject';
110
+ reason:
111
+ | 'missing_receipt'
112
+ | 'not_running'
113
+ | 'owner_mismatch'
114
+ | 'attempt_mismatch'
115
+ | 'lease_mismatch'
116
+ | 'missing_request_lease';
117
+ };
118
+
119
+ export type WorkReceiptOwnerOperationInput = {
120
+ receipt: WorkReceiptStateInput | null;
121
+ ownerRunId?: string | null;
122
+ ownerRunAttempt?: number | null;
123
+ leaseId?: string | null;
124
+ };
125
+
126
+ function normalize(value: string | null | undefined): string | null {
127
+ const trimmed = value?.trim();
128
+ return trimmed ? trimmed : null;
129
+ }
130
+
131
+ function normalizeAttempt(value: number | null | undefined): number {
132
+ return typeof value === 'number' && Number.isFinite(value)
133
+ ? Math.max(0, Math.floor(value))
134
+ : 0;
135
+ }
136
+
137
+ export function classifyWorkReceiptLeaseState(
138
+ receipt: WorkReceiptStateInput,
139
+ nowMs: number,
140
+ ): WorkReceiptLeaseState {
141
+ const ownerRunId =
142
+ normalize(receipt.leaseOwnerRunId) ?? normalize(receipt.runId);
143
+ const leaseId = normalize(receipt.leaseId);
144
+ const expiresAt = normalize(receipt.leaseExpiresAt);
145
+ if (!leaseId && !expiresAt) {
146
+ return { kind: 'none' };
147
+ }
148
+ if (!ownerRunId) {
149
+ return {
150
+ kind: 'invalid',
151
+ reason: 'missing_owner',
152
+ leaseId,
153
+ ownerRunId,
154
+ expiresAt,
155
+ };
156
+ }
157
+ if (!expiresAt) {
158
+ return {
159
+ kind: 'invalid',
160
+ reason: 'missing_expiry',
161
+ leaseId,
162
+ ownerRunId,
163
+ expiresAt,
164
+ };
165
+ }
166
+ const expiresAtMs = Date.parse(expiresAt);
167
+ if (!Number.isFinite(expiresAtMs)) {
168
+ return {
169
+ kind: 'invalid',
170
+ reason: 'invalid_expiry',
171
+ leaseId,
172
+ ownerRunId,
173
+ expiresAt,
174
+ };
175
+ }
176
+ if (expiresAtMs <= nowMs) {
177
+ return {
178
+ kind: 'expired',
179
+ leaseId,
180
+ ownerRunId,
181
+ ownerAttempt: normalizeAttempt(receipt.leaseOwnerAttempt),
182
+ expiresAt,
183
+ expiresAtMs,
184
+ };
185
+ }
186
+ if (!leaseId) {
187
+ return {
188
+ kind: 'invalid',
189
+ reason: 'missing_lease_id',
190
+ leaseId,
191
+ ownerRunId,
192
+ expiresAt,
193
+ };
194
+ }
195
+ return {
196
+ kind: 'active',
197
+ leaseId,
198
+ ownerRunId,
199
+ ownerAttempt: normalizeAttempt(receipt.leaseOwnerAttempt),
200
+ expiresAt,
201
+ expiresAtMs,
202
+ };
203
+ }
204
+
205
+ export function classifyWorkReceiptState(
206
+ receipt: WorkReceiptStateInput,
207
+ nowMs: number,
208
+ ): WorkReceiptState {
209
+ const lease = classifyWorkReceiptLeaseState(receipt, nowMs);
210
+ switch (receipt.status) {
211
+ case 'pending':
212
+ return { kind: 'pending', terminal: false, reusable: false, lease };
213
+ case 'running':
214
+ return { kind: 'running', terminal: false, reusable: false, lease };
215
+ case 'completed':
216
+ return { kind: 'completed', terminal: true, reusable: true, lease };
217
+ case 'skipped':
218
+ return { kind: 'skipped', terminal: true, reusable: true, lease };
219
+ case 'failed':
220
+ return { kind: 'failed', terminal: true, reusable: false, lease };
221
+ default:
222
+ return assertNever(receipt.status);
223
+ }
224
+ }
225
+
226
+ export function decideWorkReceiptClaim(input: {
227
+ receipt: WorkReceiptStateInput | null;
228
+ claimantRunId?: string | null;
229
+ claimantRunAttempt?: number | null;
230
+ reclaimRunning?: boolean;
231
+ forceRefresh?: boolean;
232
+ forceFailedRefresh?: boolean;
233
+ nowMs: number;
234
+ }): WorkReceiptClaimDecision {
235
+ if (!input.receipt) return { kind: 'claim' };
236
+ const state = classifyWorkReceiptState(input.receipt, input.nowMs);
237
+ if (state.reusable && input.forceRefresh !== true) {
238
+ return { kind: 'reuse_terminal' };
239
+ }
240
+ if (state.kind === 'failed' && input.forceRefresh !== true) {
241
+ if (input.forceFailedRefresh === true) {
242
+ return { kind: 'claim' };
243
+ }
244
+ const claimantRunId = normalize(input.claimantRunId);
245
+ const ownerRunId = normalize(input.receipt.runId);
246
+ const claimantRunAttempt = normalizeAttempt(input.claimantRunAttempt);
247
+ const ownerAttempt = normalizeAttempt(input.receipt.leaseOwnerAttempt);
248
+ if (
249
+ input.receipt.failureKind === 'repairable' &&
250
+ claimantRunId !== null &&
251
+ ownerRunId !== null &&
252
+ (claimantRunId !== ownerRunId || ownerAttempt < claimantRunAttempt)
253
+ ) {
254
+ return { kind: 'claim' };
255
+ }
256
+ return { kind: 'blocked_failed' };
257
+ }
258
+ if (state.kind === 'pending' || state.kind === 'running') {
259
+ if (state.lease.kind === 'active') {
260
+ if (
261
+ (state.kind !== 'running' ||
262
+ input.reclaimRunning === true ||
263
+ input.forceRefresh === true) &&
264
+ state.lease.ownerRunId !== null &&
265
+ state.lease.ownerRunId === normalize(input.claimantRunId) &&
266
+ state.lease.ownerAttempt < normalizeAttempt(input.claimantRunAttempt)
267
+ ) {
268
+ return { kind: 'claim' };
269
+ }
270
+ return {
271
+ kind: 'blocked_active_lease',
272
+ status: state.kind,
273
+ ownerRunId: state.lease.ownerRunId,
274
+ leaseId: state.lease.leaseId,
275
+ };
276
+ }
277
+ if (state.lease.kind === 'invalid') {
278
+ return {
279
+ kind: 'blocked_invalid_lease',
280
+ reason: state.lease.reason,
281
+ status: state.kind,
282
+ };
283
+ }
284
+ if (input.forceRefresh === true && input.reclaimRunning === true) {
285
+ return { kind: 'claim' };
286
+ }
287
+ if (state.kind === 'pending') {
288
+ return { kind: 'claim' };
289
+ }
290
+ if (
291
+ input.reclaimRunning === true &&
292
+ (state.lease.kind === 'expired' || state.lease.kind === 'none')
293
+ ) {
294
+ return { kind: 'claim' };
295
+ }
296
+ return {
297
+ kind: 'blocked_running',
298
+ ownerRunId:
299
+ state.lease.kind === 'expired'
300
+ ? state.lease.ownerRunId
301
+ : normalize(input.receipt.runId),
302
+ leaseId:
303
+ state.lease.kind === 'expired'
304
+ ? state.lease.leaseId
305
+ : normalize(input.receipt.leaseId),
306
+ };
307
+ }
308
+ if (
309
+ input.forceRefresh === true &&
310
+ (state.kind === 'completed' || state.kind === 'skipped')
311
+ ) {
312
+ if (state.lease.kind === 'active') {
313
+ if (
314
+ state.lease.ownerRunId !== null &&
315
+ state.lease.ownerRunId === normalize(input.claimantRunId) &&
316
+ state.lease.ownerAttempt < normalizeAttempt(input.claimantRunAttempt)
317
+ ) {
318
+ return { kind: 'claim' };
319
+ }
320
+ return {
321
+ kind: 'blocked_active_lease',
322
+ status: state.kind,
323
+ ownerRunId: state.lease.ownerRunId,
324
+ leaseId: state.lease.leaseId,
325
+ };
326
+ }
327
+ if (state.lease.kind === 'invalid') {
328
+ return {
329
+ kind: 'blocked_invalid_lease',
330
+ reason: state.lease.reason,
331
+ status: state.kind,
332
+ };
333
+ }
334
+ }
335
+ if (input.forceRefresh === true) {
336
+ return { kind: 'claim' };
337
+ }
338
+ return { kind: 'claim' };
339
+ }
340
+
341
+ function ownerRunIdForReceipt(receipt: WorkReceiptStateInput): string | null {
342
+ return normalize(receipt.leaseOwnerRunId) ?? normalize(receipt.runId);
343
+ }
344
+
345
+ function baseRunningOwnerDecision(
346
+ input: WorkReceiptOwnerOperationInput,
347
+ ): WorkReceiptOwnerOperationDecision {
348
+ if (!input.receipt) {
349
+ return { kind: 'reject', reason: 'missing_receipt' };
350
+ }
351
+ if (input.receipt.status !== 'running') {
352
+ return { kind: 'reject', reason: 'not_running' };
353
+ }
354
+ const ownerRunId = normalize(input.ownerRunId);
355
+ if (!ownerRunId || ownerRunIdForReceipt(input.receipt) !== ownerRunId) {
356
+ return { kind: 'reject', reason: 'owner_mismatch' };
357
+ }
358
+ if (
359
+ normalizeAttempt(input.receipt.leaseOwnerAttempt) !==
360
+ normalizeAttempt(input.ownerRunAttempt)
361
+ ) {
362
+ return { kind: 'reject', reason: 'attempt_mismatch' };
363
+ }
364
+ return { kind: 'allow' };
365
+ }
366
+
367
+ /**
368
+ * Law for complete/fail/skip. A terminal write may only be made by the
369
+ * running receipt owner for the same attempt. When the receipt has a lease id,
370
+ * the writer must also present that exact lease id; legacy unleased running
371
+ * receipts remain writable by their run+attempt owner during the migration.
372
+ */
373
+ export function decideWorkReceiptTerminalWrite(
374
+ input: WorkReceiptOwnerOperationInput,
375
+ ): WorkReceiptOwnerOperationDecision {
376
+ const ownerDecision = baseRunningOwnerDecision(input);
377
+ if (ownerDecision.kind === 'reject') return ownerDecision;
378
+
379
+ const receiptLeaseId = normalize(input.receipt?.leaseId);
380
+ if (!receiptLeaseId) return { kind: 'allow' };
381
+ if (receiptLeaseId !== normalize(input.leaseId)) {
382
+ return { kind: 'reject', reason: 'lease_mismatch' };
383
+ }
384
+ return { kind: 'allow' };
385
+ }
386
+
387
+ /**
388
+ * Law for a single suspended receipt release. It uses the same ownership fence
389
+ * as terminal writes: only the running owner can release, and a leased receipt
390
+ * requires the exact lease id.
391
+ */
392
+ export function decideWorkReceiptExactRelease(
393
+ input: WorkReceiptOwnerOperationInput,
394
+ ): WorkReceiptOwnerOperationDecision {
395
+ return decideWorkReceiptTerminalWrite(input);
396
+ }
397
+
398
+ /**
399
+ * Law for run-fatal teardown. The owner may release all of its still-running
400
+ * receipts for the same attempt without threading every per-receipt lease id.
401
+ * It must never match another run or another attempt.
402
+ */
403
+ export function decideWorkReceiptOwnerRelease(
404
+ input: WorkReceiptOwnerOperationInput,
405
+ ): WorkReceiptOwnerOperationDecision {
406
+ return baseRunningOwnerDecision(input);
407
+ }
408
+
409
+ /**
410
+ * Law for heartbeat. Heartbeats are stricter than terminal writes: they renew a
411
+ * specific live lease, so both receipt and request must carry the same lease id.
412
+ */
413
+ export function decideWorkReceiptHeartbeat(
414
+ input: WorkReceiptOwnerOperationInput,
415
+ ): WorkReceiptOwnerOperationDecision {
416
+ const ownerDecision = baseRunningOwnerDecision(input);
417
+ if (ownerDecision.kind === 'reject') return ownerDecision;
418
+
419
+ const requestLeaseId = normalize(input.leaseId);
420
+ if (!requestLeaseId) {
421
+ return { kind: 'reject', reason: 'missing_request_lease' };
422
+ }
423
+ if (normalize(input.receipt?.leaseId) !== requestLeaseId) {
424
+ return { kind: 'reject', reason: 'lease_mismatch' };
425
+ }
426
+ return { kind: 'allow' };
427
+ }
428
+
429
+ export function isReusableWorkReceiptState(
430
+ receipt: WorkReceiptStateInput,
431
+ ): boolean {
432
+ return receipt.status === 'completed' || receipt.status === 'skipped';
433
+ }
434
+
435
+ function assertNever(value: never): never {
436
+ throw new Error(`Unhandled work receipt state: ${String(value)}`);
437
+ }
@@ -1,3 +1,29 @@
1
+ import { isReusableWorkReceiptState } from './work-receipt-state-machine';
2
+
3
+ export type WorkReceiptFailureKind = 'terminal' | 'repairable';
4
+
5
+ export const WORK_RECEIPT_FAILURE_KIND_CODE: Record<
6
+ WorkReceiptFailureKind,
7
+ number
8
+ > = {
9
+ terminal: 0,
10
+ repairable: 1,
11
+ };
12
+
13
+ export function workReceiptFailureKindFromCode(
14
+ value: unknown,
15
+ ): WorkReceiptFailureKind {
16
+ return Number(value) === WORK_RECEIPT_FAILURE_KIND_CODE.repairable
17
+ ? 'repairable'
18
+ : 'terminal';
19
+ }
20
+
21
+ export function workReceiptFailureKindCode(
22
+ value: WorkReceiptFailureKind | null | undefined,
23
+ ): number {
24
+ return WORK_RECEIPT_FAILURE_KIND_CODE[value ?? 'terminal'];
25
+ }
26
+
1
27
  export type WorkReceiptStatus =
2
28
  | 'pending'
3
29
  | 'running'
@@ -10,7 +36,12 @@ export type WorkReceipt = {
10
36
  status: WorkReceiptStatus;
11
37
  output?: unknown;
12
38
  error?: string | null;
39
+ failureKind?: WorkReceiptFailureKind | null;
13
40
  runId?: string | null;
41
+ leaseId?: string | null;
42
+ leaseOwnerRunId?: string | null;
43
+ leaseOwnerAttempt?: number | null;
44
+ leaseExpiresAt?: string | null;
14
45
  updatedAt?: string | null;
15
46
  };
16
47
 
@@ -18,26 +49,57 @@ export type WorkReceiptCommand = {
18
49
  playName: string;
19
50
  runId: string;
20
51
  key: string;
52
+ leaseId?: string | null;
53
+ runAttempt?: number | null;
54
+ leaseTtlMs?: number | null;
55
+ leaseAware?: boolean;
21
56
  reclaimRunning?: boolean;
22
57
  forceRefresh?: boolean;
58
+ forceFailedRefresh?: boolean;
23
59
  };
24
60
 
25
61
  export type WorkReceiptBatchClaimCommand = {
26
62
  playName: string;
27
63
  runId: string;
28
64
  keys: string[];
65
+ leaseAware?: boolean;
29
66
  reclaimRunning?: boolean;
30
67
  forceRefresh?: boolean;
68
+ forceFailedRefresh?: boolean;
69
+ runAttempt?: number | null;
70
+ leaseTtlMs?: number | null;
31
71
  };
32
72
 
33
73
  export type WorkReceiptBatchCompleteCommand = {
34
74
  playName: string;
35
- receipts: Array<{ runId: string; key: string; output: unknown }>;
75
+ receipts: Array<{
76
+ runId: string;
77
+ key: string;
78
+ output: unknown;
79
+ leaseId?: string | null;
80
+ runAttempt?: number | null;
81
+ }>;
36
82
  };
37
83
 
38
84
  export type WorkReceiptBatchFailCommand = {
39
85
  playName: string;
40
- receipts: Array<{ runId: string; key: string; error: string }>;
86
+ receipts: Array<{
87
+ runId: string;
88
+ key: string;
89
+ error: string;
90
+ failureKind?: WorkReceiptFailureKind | null;
91
+ leaseId?: string | null;
92
+ runAttempt?: number | null;
93
+ }>;
94
+ };
95
+
96
+ export type WorkReceiptBatchHeartbeatCommand = {
97
+ playName: string;
98
+ runId: string;
99
+ runAttempt?: number | null;
100
+ leaseId: string;
101
+ keys: string[];
102
+ leaseTtlMs?: number | null;
41
103
  };
42
104
 
43
105
  export type WorkReceiptGetCommand = {
@@ -58,6 +120,10 @@ export type WorkReceiptClaim =
58
120
  disposition: 'running';
59
121
  receipt: WorkReceipt;
60
122
  }
123
+ | {
124
+ disposition: 'blocked_active_lease';
125
+ receipt: WorkReceipt;
126
+ }
61
127
  | {
62
128
  disposition: 'failed';
63
129
  receipt: WorkReceipt;
@@ -74,11 +140,22 @@ export type WorkReceiptStore = {
74
140
  ): Promise<WorkReceipt | null>;
75
141
  completeReceipts?(
76
142
  input: WorkReceiptBatchCompleteCommand,
77
- ): Promise<WorkReceipt[]>;
143
+ ): Promise<Array<WorkReceipt | null>>;
78
144
  failReceipt(
79
- input: WorkReceiptCommand & { error: string },
145
+ input: WorkReceiptCommand & {
146
+ error: string;
147
+ failureKind?: WorkReceiptFailureKind | null;
148
+ },
149
+ ): Promise<WorkReceipt | null>;
150
+ failReceipts?(
151
+ input: WorkReceiptBatchFailCommand,
152
+ ): Promise<Array<WorkReceipt | null>>;
153
+ releaseReceipt?(
154
+ input: WorkReceiptCommand,
80
155
  ): Promise<WorkReceipt | null>;
81
- failReceipts?(input: WorkReceiptBatchFailCommand): Promise<WorkReceipt[]>;
156
+ heartbeatReceipts?(
157
+ input: WorkReceiptBatchHeartbeatCommand,
158
+ ): Promise<Array<WorkReceipt | null>>;
82
159
  };
83
160
 
84
161
  const SCOPED_WORK_RECEIPT_KEY_VERSION = 'ctxv2';
@@ -122,5 +199,5 @@ export function scopedWorkReceiptKeyPrefix(input: {
122
199
  }
123
200
 
124
201
  export function isReusableWorkReceipt(receipt: WorkReceipt): boolean {
125
- return receipt.status === 'completed' || receipt.status === 'skipped';
202
+ return isReusableWorkReceiptState(receipt);
126
203
  }
@@ -42,6 +42,12 @@ const TRANSPORT_OWNED_REQUEST_HEADERS = new Set([
42
42
  'x-forwarded-host',
43
43
  'x-forwarded-proto',
44
44
  ]);
45
+ const CROSS_ORIGIN_REDIRECT_SAFE_HEADERS = new Set([
46
+ 'accept',
47
+ 'accept-language',
48
+ 'content-language',
49
+ 'content-type',
50
+ ]);
45
51
 
46
52
  function cloneHeaders(headers: RequestInit['headers']): Headers {
47
53
  return new Headers(headers);
@@ -57,6 +63,18 @@ function removeTransportOwnedHeaders(headers: Headers): void {
57
63
  }
58
64
  }
59
65
 
66
+ function removeUnsafeCrossOriginRedirectHeaders(headers: Headers): void {
67
+ const namesToDelete: string[] = [];
68
+ for (const [name] of headers) {
69
+ if (!CROSS_ORIGIN_REDIRECT_SAFE_HEADERS.has(name.toLowerCase())) {
70
+ namesToDelete.push(name);
71
+ }
72
+ }
73
+ for (const name of namesToDelete) {
74
+ headers.delete(name);
75
+ }
76
+ }
77
+
60
78
  function withContentLength(
61
79
  body: Buffer | string | undefined,
62
80
  headers: Headers,
@@ -222,14 +240,11 @@ function createRequest(
222
240
  response.on('error', reject);
223
241
  response.on('end', () => {
224
242
  resolve(
225
- new Response(
226
- noBodyResponse ? null : Buffer.concat(chunks),
227
- {
228
- status,
229
- statusText: response.statusMessage,
230
- headers: response.headers as HeadersInit,
231
- },
232
- ),
243
+ new Response(noBodyResponse ? null : Buffer.concat(chunks), {
244
+ status,
245
+ statusText: response.statusMessage,
246
+ headers: response.headers as HeadersInit,
247
+ }),
233
248
  );
234
249
  });
235
250
  },
@@ -248,7 +263,6 @@ function initForRedirect(
248
263
  from: URL,
249
264
  to: URL,
250
265
  status: number,
251
- sensitiveHeaders: Iterable<string>,
252
266
  ): RequestInit {
253
267
  const headers = cloneHeaders(init.headers);
254
268
  let method = String(init.method ?? 'GET').toUpperCase();
@@ -265,12 +279,7 @@ function initForRedirect(
265
279
  }
266
280
 
267
281
  if (from.origin !== to.origin) {
268
- deleteHeader(headers, 'authorization');
269
- deleteHeader(headers, 'cookie');
270
- deleteHeader(headers, 'proxy-authorization');
271
- for (const header of sensitiveHeaders) {
272
- deleteHeader(headers, header);
273
- }
282
+ removeUnsafeCrossOriginRedirectHeaders(headers);
274
283
  }
275
284
 
276
285
  return {
@@ -290,7 +299,6 @@ export async function safeOutboundFetch(
290
299
  const redirectMode = init.redirect ?? 'follow';
291
300
  const maxRedirects = options.maxRedirects ?? 10;
292
301
  const maxResponseBytes = options.maxResponseBytes;
293
- const sensitiveHeaders = options.sensitiveHeaders ?? [];
294
302
  const validateUrl = options.validateUrl;
295
303
  let currentUrl = assertPublicHttpUrl(input);
296
304
  let currentInit: RequestInit = { ...init, redirect: 'manual' };
@@ -348,7 +356,6 @@ export async function safeOutboundFetch(
348
356
  currentUrl,
349
357
  nextUrl,
350
358
  response.status,
351
- sensitiveHeaders,
352
359
  );
353
360
  currentUrl = nextUrl;
354
361
  }