deepline 0.1.182 → 0.1.184

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 (69) 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 +639 -109
  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 +1752 -1899
  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 +133 -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 +1612 -287
  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-paths.ts +21 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2038 -262
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
  52. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
  58. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
  59. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +28 -11
  60. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +38 -6
  61. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
  62. package/dist/cli/index.js +76 -17
  63. package/dist/cli/index.mjs +76 -17
  64. package/dist/index.d.mts +2 -0
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.js +63 -9
  67. package/dist/index.mjs +63 -9
  68. package/dist/plays/bundle-play-file.mjs +20 -6
  69. 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
  }
@@ -475,7 +475,9 @@ export function extractDefinedPlayDescriptionForExport(
475
475
  sourceCode: string,
476
476
  exportName: string,
477
477
  ): string | null {
478
- return extractDefinedPlayMetadata(sourceCode, exportName)?.description ?? null;
478
+ return (
479
+ extractDefinedPlayMetadata(sourceCode, exportName)?.description ?? null
480
+ );
479
481
  }
480
482
 
481
483
  type AstNode = {
@@ -548,7 +550,9 @@ function astArray(value: unknown): AstNode[] {
548
550
  return Array.isArray(value) ? value.filter(isAstNode) : [];
549
551
  }
550
552
 
551
- function memberExpressionPath(node: AstNode | null | undefined): string[] | null {
553
+ function memberExpressionPath(
554
+ node: AstNode | null | undefined,
555
+ ): string[] | null {
552
556
  const expression = unwrapStaticExpression(node);
553
557
  if (!expression) return null;
554
558
  if (expression.type === 'Identifier' && typeof expression.name === 'string') {
@@ -657,7 +661,9 @@ function buildPlayMetadataContext(ast: AstNode): PlayMetadataExtractionContext {
657
661
  isAstNode(statement.declaration) &&
658
662
  statement.declaration.type === 'VariableDeclaration'
659
663
  ) {
660
- for (const declaration of astArray(statement.declaration.declarations)) {
664
+ for (const declaration of astArray(
665
+ statement.declaration.declarations,
666
+ )) {
661
667
  const name = getIdentifierName(declaration.id);
662
668
  if (!name) continue;
663
669
  const initializer = isAstNode(declaration.init)
@@ -708,7 +714,9 @@ function buildPlayMetadataContext(ast: AstNode): PlayMetadataExtractionContext {
708
714
  return { declarations, namedExports, commonJsExports, defaultExport };
709
715
  }
710
716
 
711
- function unwrapStaticExpression(node: AstNode | null | undefined): AstNode | null {
717
+ function unwrapStaticExpression(
718
+ node: AstNode | null | undefined,
719
+ ): AstNode | null {
712
720
  let current = node ?? null;
713
721
  while (
714
722
  current &&
@@ -837,9 +845,7 @@ function stringPropertyFromObjectExpression(
837
845
 
838
846
  if (property.type !== 'Property') continue;
839
847
  if (propertyNameFromKey(property) !== propertyName) continue;
840
- const propertyValue = property.shorthand
841
- ? property.key
842
- : property.value;
848
+ const propertyValue = property.shorthand ? property.key : property.value;
843
849
  value = staticStringFromExpression(
844
850
  isAstNode(propertyValue) ? propertyValue : null,
845
851
  context,
@@ -883,11 +889,16 @@ function playMetadataFromDefinePlayCall(
883
889
 
884
890
  const args = astArray(expression.arguments);
885
891
  const firstArg = args[0] ?? null;
886
- const objectName = stringPropertyFromObjectExpression(firstArg, 'id', context);
892
+ const objectName = stringPropertyFromObjectExpression(
893
+ firstArg,
894
+ 'id',
895
+ context,
896
+ );
887
897
  const name = objectName ?? staticStringFromExpression(firstArg, context);
888
898
 
889
899
  let description =
890
- stringPropertyFromObjectExpression(firstArg, 'description', context) ?? null;
900
+ stringPropertyFromObjectExpression(firstArg, 'description', context) ??
901
+ null;
891
902
  for (let index = args.length - 1; !description && index >= 2; index -= 1) {
892
903
  description = stringPropertyFromObjectExpression(
893
904
  args[index],
@@ -1608,7 +1619,10 @@ async function analyzeSourceGraph(
1608
1619
  }
1609
1620
  visited.add(absolutePath);
1610
1621
 
1611
- const sourceCode = await readFile(absolutePath, 'utf-8');
1622
+ const sourceCode = await readFile(
1623
+ /* turbopackIgnore: true */ absolutePath,
1624
+ 'utf-8',
1625
+ );
1612
1626
  localFiles.set(absolutePath, sourceCode);
1613
1627
 
1614
1628
  if (extname(absolutePath).toLowerCase() === '.json') {
@@ -1645,7 +1659,10 @@ async function analyzeSourceGraph(
1645
1659
  column,
1646
1660
  });
1647
1661
  if (resolved !== absoluteEntryFile && isPlaySourceFile(resolved)) {
1648
- const importedSource = await readFile(resolved, 'utf-8');
1662
+ const importedSource = await readFile(
1663
+ /* turbopackIgnore: true */ resolved,
1664
+ 'utf-8',
1665
+ );
1649
1666
  const importedPlayName = extractDefinedPlayName(importedSource);
1650
1667
  if (!importedPlayName) {
1651
1668
  throw new Error(