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
@@ -8,12 +8,16 @@ import type {
8
8
  PlayDatasetInput,
9
9
  PlayDatasetRow,
10
10
  } from '../plays/dataset';
11
- import type { PlayQueueHint } from './governor/rate-state-backend';
11
+ import type {
12
+ PlayQueueHint,
13
+ RateStateBackend,
14
+ } from './governor/rate-state-backend';
12
15
  import type { GovernanceSnapshot } from './governor/governor';
13
16
  import type { AnyBatchOperationStrategy } from './batching-types';
14
17
  import type { ToolResultMetadataInput } from './tool-result';
15
18
  import type { PreviousCell } from './cell-staleness';
16
19
  import type { MapRowOutcome } from './durability-store';
20
+ import type { WorkReceiptFailureKind } from './work-receipts';
17
21
 
18
22
  export interface RowState {
19
23
  results: Map<string, unknown>;
@@ -24,7 +28,10 @@ export interface ToolCallRequest {
24
28
  cacheKey: string;
25
29
  cacheable?: boolean;
26
30
  receiptKey?: string | null;
31
+ executionAuthScopeDigest?: string | null;
32
+ receiptLeaseId?: string | null;
27
33
  force?: boolean;
34
+ forceFailedRefresh?: boolean;
28
35
  rowId: number;
29
36
  fieldName?: string;
30
37
  toolId: string;
@@ -46,7 +53,12 @@ export interface RuntimeStepReceipt {
46
53
  status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
47
54
  output?: unknown;
48
55
  error?: string;
56
+ failureKind?: WorkReceiptFailureKind | null;
49
57
  runId?: string | null;
58
+ leaseId?: string | null;
59
+ leaseOwnerRunId?: string | null;
60
+ leaseOwnerAttempt?: number | null;
61
+ leaseExpiresAt?: string | null;
50
62
  claimState?: 'claimed' | 'existing';
51
63
  }
52
64
 
@@ -57,25 +69,42 @@ export interface GetRuntimeStepReceiptInput {
57
69
  export interface ClaimRuntimeStepReceiptInput {
58
70
  key: string;
59
71
  runId: string;
72
+ runAttempt?: number | null;
73
+ leaseAware?: boolean;
60
74
  reclaimRunning?: boolean;
61
75
  forceRefresh?: boolean;
76
+ forceFailedRefresh?: boolean;
62
77
  }
63
78
 
64
79
  export interface CompleteRuntimeStepReceiptInput {
65
80
  key: string;
66
81
  runId: string;
82
+ runAttempt?: number | null;
83
+ leaseId?: string | null;
67
84
  output: unknown | null;
68
85
  }
69
86
 
87
+ export interface ReleaseRuntimeStepReceiptInput {
88
+ key: string;
89
+ runId: string;
90
+ runAttempt?: number | null;
91
+ leaseId?: string | null;
92
+ }
93
+
70
94
  export interface FailRuntimeStepReceiptInput {
71
95
  key: string;
72
96
  runId: string;
97
+ runAttempt?: number | null;
98
+ leaseId?: string | null;
73
99
  error: string;
100
+ failureKind?: WorkReceiptFailureKind | null;
74
101
  }
75
102
 
76
103
  export interface SkipRuntimeStepReceiptInput {
77
104
  key: string;
78
105
  runId: string;
106
+ runAttempt?: number | null;
107
+ leaseId?: string | null;
79
108
  output?: unknown | null;
80
109
  }
81
110
 
@@ -86,14 +115,19 @@ export interface GetRuntimeStepReceiptsInput {
86
115
  export interface ClaimRuntimeStepReceiptsInput {
87
116
  keys: string[];
88
117
  runId: string;
118
+ runAttempt?: number | null;
119
+ leaseAware?: boolean;
89
120
  reclaimRunning?: boolean;
90
121
  forceRefresh?: boolean;
122
+ forceFailedRefresh?: boolean;
91
123
  }
92
124
 
93
125
  export interface CompleteRuntimeStepReceiptsInput {
94
126
  receipts: Array<{
95
127
  key: string;
96
128
  runId: string;
129
+ runAttempt?: number | null;
130
+ leaseId?: string | null;
97
131
  output: unknown | null;
98
132
  }>;
99
133
  }
@@ -102,10 +136,20 @@ export interface FailRuntimeStepReceiptsInput {
102
136
  receipts: Array<{
103
137
  key: string;
104
138
  runId: string;
139
+ runAttempt?: number | null;
140
+ leaseId?: string | null;
105
141
  error: string;
142
+ failureKind?: WorkReceiptFailureKind | null;
106
143
  }>;
107
144
  }
108
145
 
146
+ export interface HeartbeatRuntimeStepReceiptsInput {
147
+ keys: string[];
148
+ runId: string;
149
+ runAttempt?: number | null;
150
+ leaseId: string;
151
+ }
152
+
109
153
  export interface CsvOptions {
110
154
  description?: string;
111
155
  columns?: Record<string, string | readonly string[]>;
@@ -271,6 +315,10 @@ export interface BatchRequest {
271
315
  export interface MapStartResult {
272
316
  /** Rows that need processing for this run. Previous values may be included for context. */
273
317
  pendingRows: Record<string, unknown>[];
318
+ /** Rows already terminal for this run and safe to count as completed. */
319
+ completedRows?: Record<string, unknown>[];
320
+ /** Rows leased by another active attempt and not executable by this attempt. */
321
+ blockedRows?: Record<string, unknown>[];
274
322
  /** Resolved table namespace. */
275
323
  tableNamespace: string;
276
324
  }
@@ -403,6 +451,7 @@ export interface ContextOptions {
403
451
  graphHash?: string | null;
404
452
  artifactHash?: string | null;
405
453
  convexUrl?: string;
454
+ runtimeSchedulerSchema?: string | null;
406
455
  staticPipeline?: PlayStaticPipeline | null;
407
456
  workflowId?: string;
408
457
  sessionId?: string;
@@ -411,9 +460,15 @@ export interface ContextOptions {
411
460
  checkpoint?: PlayCheckpoint;
412
461
  /** Enables durable boundary replay such as ctx.sleep() resumptions. */
413
462
  durableBoundaries?: boolean;
414
- /** Run-level cache policy. forceToolRefresh bypasses ctx.tools.execute receipts. */
463
+ /**
464
+ * Run-level cache policy.
465
+ * forceToolRefresh bypasses all ctx.tools.execute receipts.
466
+ * forceFailedToolRefresh reclaims only failed receipts, preserving completed
467
+ * provider-call idempotency while allowing forced repair runs to make progress.
468
+ */
415
469
  cachePolicy?: {
416
470
  forceToolRefresh?: boolean;
471
+ forceFailedToolRefresh?: boolean;
417
472
  };
418
473
  /** Called after each durable batch completes — for Temporal heartbeating. */
419
474
  onBatchComplete?: (checkpoint: PlayCheckpoint) => void;
@@ -439,6 +494,7 @@ export interface ContextOptions {
439
494
  playName?: string;
440
495
  playId?: string;
441
496
  runId?: string;
497
+ executorToken?: string;
442
498
  staticPipeline?: PlayStaticPipeline | null;
443
499
  forceRefresh?: boolean;
444
500
  },
@@ -453,6 +509,7 @@ export interface ContextOptions {
453
509
  playName?: string;
454
510
  playId?: string;
455
511
  runId?: string;
512
+ executorToken?: string;
456
513
  tableNamespace: string;
457
514
  rows: MapRowOutcome[];
458
515
  outputFields: string[];
@@ -460,12 +517,25 @@ export interface ContextOptions {
460
517
  }) => Promise<void>;
461
518
  playId?: string;
462
519
  runId?: string;
520
+ runAttempt?: number | null;
521
+ /**
522
+ * Optional shared provider-rate state for fleet substrates. Local/test
523
+ * contexts omit this and use the in-process backend.
524
+ */
525
+ rateState?: RateStateBackend;
526
+ /**
527
+ * Loud guard for production Node substrates: when true, constructing a
528
+ * context without a shared rate-state backend is a configuration error.
529
+ */
530
+ requireSharedRateState?: boolean;
463
531
  resolvePlay?: (playRef: string) => Promise<ResolvedPlayExecution | null>;
464
532
  getToolQueueHints?: (toolId: string) => Promise<readonly PlayQueueHint[]>;
465
533
  getToolRetryPolicy?: (toolId: string) => Promise<{
466
534
  retrySafeTransientHttp?: boolean;
467
535
  } | null>;
468
536
  getToolActionCacheVersion?: (toolId: string) => Promise<string> | string;
537
+ getToolAuthScopeDigest?: (toolId: string) => Promise<string> | string;
538
+ invalidateToolAuthScopeDigest?: (toolId: string) => void;
469
539
  getToolTargetGetters?: (
470
540
  toolId: string,
471
541
  output: string,
@@ -502,25 +572,41 @@ export interface ContextOptions {
502
572
  ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
503
573
  getRuntimeStepReceipts?: (
504
574
  input: GetRuntimeStepReceiptsInput,
505
- ) => Promise<RuntimeStepReceipt[]> | RuntimeStepReceipt[];
575
+ ) =>
576
+ | Promise<Array<RuntimeStepReceipt | null>>
577
+ | Array<RuntimeStepReceipt | null>;
506
578
  claimRuntimeStepReceipt?: (
507
579
  input: ClaimRuntimeStepReceiptInput,
508
580
  ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
509
581
  claimRuntimeStepReceipts?: (
510
582
  input: ClaimRuntimeStepReceiptsInput,
511
- ) => Promise<RuntimeStepReceipt[]> | RuntimeStepReceipt[];
583
+ ) =>
584
+ | Promise<Array<RuntimeStepReceipt | null>>
585
+ | Array<RuntimeStepReceipt | null>;
512
586
  completeRuntimeStepReceipt?: (
513
587
  input: CompleteRuntimeStepReceiptInput,
514
588
  ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
589
+ releaseRuntimeStepReceipt?: (
590
+ input: ReleaseRuntimeStepReceiptInput,
591
+ ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
515
592
  completeRuntimeStepReceipts?: (
516
593
  input: CompleteRuntimeStepReceiptsInput,
517
- ) => Promise<RuntimeStepReceipt[]> | RuntimeStepReceipt[];
594
+ ) =>
595
+ | Promise<Array<RuntimeStepReceipt | null>>
596
+ | Array<RuntimeStepReceipt | null>;
518
597
  failRuntimeStepReceipt?: (
519
598
  input: FailRuntimeStepReceiptInput,
520
599
  ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
521
600
  failRuntimeStepReceipts?: (
522
601
  input: FailRuntimeStepReceiptsInput,
523
- ) => Promise<RuntimeStepReceipt[]> | RuntimeStepReceipt[];
602
+ ) =>
603
+ | Promise<Array<RuntimeStepReceipt | null>>
604
+ | Array<RuntimeStepReceipt | null>;
605
+ heartbeatRuntimeStepReceipts?: (
606
+ input: HeartbeatRuntimeStepReceiptsInput,
607
+ ) =>
608
+ | Promise<Array<RuntimeStepReceipt | null>>
609
+ | Array<RuntimeStepReceipt | null>;
524
610
  skipRuntimeStepReceipt?: (
525
611
  input: SkipRuntimeStepReceiptInput,
526
612
  ) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
@@ -580,6 +666,15 @@ export interface PlayCheckpoint {
580
666
  output?: unknown;
581
667
  completedAt?: number;
582
668
  }
669
+ | {
670
+ kind: 'child_play';
671
+ childRunId: string;
672
+ childPlayName: string;
673
+ status: 'completed' | 'failed' | 'cancelled';
674
+ output?: unknown;
675
+ error?: string;
676
+ completedAt?: number;
677
+ }
583
678
  >;
584
679
  /** Per-map execution frames keyed by invocation id. */
585
680
  mapFrames?: Record<string, MapExecutionFrame>;
@@ -783,6 +878,10 @@ export interface PlayRowUpdate {
783
878
  key: string;
784
879
  rowId: number;
785
880
  tableNamespace?: string | null;
881
+ attemptId?: string | null;
882
+ attemptOwnerRunId?: string | null;
883
+ attemptSeq?: number | null;
884
+ attemptExpiresAt?: string | null;
786
885
  status?: 'running' | 'completed' | 'failed';
787
886
  stage?: string | null;
788
887
  provider?: string | null;
@@ -81,6 +81,7 @@ const testRateLimitBatchStrategy: BatchOperationStrategy<
81
81
  batchOperation: 'test_batch_rate_limit',
82
82
  kind: 'identifier_batch',
83
83
  maxBatchSize: 200,
84
+ bucketKeyPayloadFields: ['stage'],
84
85
  canBatchWith(left, right) {
85
86
  return String(left.stage || '') === String(right.stage || '');
86
87
  },
@@ -3,6 +3,7 @@ import type {
3
3
  CompleteRuntimeStepReceiptInput,
4
4
  FailRuntimeStepReceiptInput,
5
5
  GetRuntimeStepReceiptInput,
6
+ ReleaseRuntimeStepReceiptInput,
6
7
  RuntimeStepReceipt,
7
8
  SkipRuntimeStepReceiptInput,
8
9
  } from './ctx-types';
@@ -50,6 +51,9 @@ export interface PlayDurabilityStore {
50
51
  completeRuntimeStepReceipt(
51
52
  input: CompleteRuntimeStepReceiptInput,
52
53
  ): Promise<RuntimeStepReceipt | null>;
54
+ releaseRuntimeStepReceipt(
55
+ input: ReleaseRuntimeStepReceiptInput,
56
+ ): Promise<RuntimeStepReceipt | null>;
53
57
  failRuntimeStepReceipt(
54
58
  input: FailRuntimeStepReceiptInput,
55
59
  ): Promise<RuntimeStepReceipt | null>;
@@ -47,37 +47,36 @@ export const durableCallStaleBucket = durableCacheStaleBucket;
47
47
 
48
48
  export function buildDurableToolCallCacheKey(input: {
49
49
  orgId?: string | null;
50
- playId: string;
51
50
  toolId: string;
52
51
  requestInput: Record<string, unknown>;
53
52
  authScopeDigest?: string | null;
54
53
  providerActionVersion?: string | null;
55
54
  cachePolicyVersion?: string | null;
56
55
  staleAfterSeconds?: number | null;
56
+ playLocalScope?: string | null;
57
57
  }): string {
58
58
  const orgId = input.orgId?.trim() || 'org';
59
- const playId = input.playId.trim();
60
- if (!playId) {
61
- throw new Error('Missing durable tool play id.');
62
- }
63
59
  const toolId = input.toolId.trim();
64
60
  if (!toolId) {
65
- throw new Error('Missing durable tool id.');
61
+ throw new Error('Durable tool call cache key requires a non-empty toolId.');
66
62
  }
67
- const p = input.providerActionVersion?.trim();
68
- if (!p) {
69
- throw new Error('Missing durable tool action version.');
63
+ const providerActionVersion = input.providerActionVersion?.trim();
64
+ if (!providerActionVersion) {
65
+ throw new Error(
66
+ 'Durable tool call cache key requires a provider action version.',
67
+ );
70
68
  }
69
+ const playLocalScope = input.playLocalScope?.trim() || 'play';
71
70
  const digest = sha256Hex(
72
71
  stableStringify({
73
72
  kind: 'tool' satisfies DurableCallKind,
74
73
  orgId,
75
- playId,
74
+ playLocalScope,
76
75
  toolId,
77
76
  normalizedToolId: normalizeTableNamespace(toolId),
78
77
  requestInput: input.requestInput,
79
78
  authScopeDigest: input.authScopeDigest ?? null,
80
- providerActionVersion: p,
79
+ providerActionVersion,
81
80
  cachePolicyVersion:
82
81
  input.cachePolicyVersion ?? DURABLE_CALL_CACHE_POLICY_VERSION,
83
82
  staleBucket: durableCacheStaleBucket({
@@ -101,9 +100,7 @@ export function buildDurableCtxCallCacheKey(input: {
101
100
  const playId = input.playId?.trim() || 'play';
102
101
  const id = input.id.trim();
103
102
  if (!id) {
104
- throw new Error(
105
- `Durable ${input.kind} call cache key requires a non-empty id.`,
106
- );
103
+ throw new Error(`Durable ${input.kind} key needs id.`);
107
104
  }
108
105
  const normalizedId = normalizeTableNamespace(id);
109
106
  const digest = sha256Hex(
@@ -124,16 +121,119 @@ export function buildDurableCtxCallCacheKey(input: {
124
121
  return `ctx:${orgId}:call:${input.kind}:${normalizedId}:${digest}`;
125
122
  }
126
123
 
124
+ export function buildDurableRunPlaySemanticKey(input: {
125
+ childPlayName: string;
126
+ input: Record<string, unknown>;
127
+ childRevisionFingerprint?: string | null;
128
+ rowScope?: Record<string, unknown> | null;
129
+ }): string {
130
+ return sha256Hex(
131
+ stableStringify({
132
+ childPlayName: input.childPlayName.trim(),
133
+ childRevisionFingerprint: input.childRevisionFingerprint ?? null,
134
+ input: input.input,
135
+ rowScope: input.rowScope ?? null,
136
+ }),
137
+ );
138
+ }
139
+
127
140
  export function buildDurableToolCallAuthScopeDigest(input: {
128
141
  orgId?: string | null;
129
- userEmail?: string | null;
130
142
  toolId: string;
143
+ executionAuthScopeDigest?: string | null;
131
144
  }): string {
132
145
  return sha256Hex(
133
146
  stableStringify({
134
147
  orgId: input.orgId?.trim() || 'org',
135
- actor: input.userEmail?.trim().toLowerCase() || 'workspace',
136
148
  toolId: input.toolId.trim(),
149
+ executionAuthScopeDigest: input.executionAuthScopeDigest?.trim() || null,
137
150
  }),
138
151
  );
139
152
  }
153
+
154
+ export function buildDurableToolReceiptPrefix(input: {
155
+ orgId?: string | null;
156
+ toolId: string;
157
+ }): string {
158
+ const orgId = input.orgId?.trim() || 'org';
159
+ const toolId = input.toolId.trim();
160
+ if (!toolId) {
161
+ throw new Error('Durable tool receipt prefix requires a non-empty toolId.');
162
+ }
163
+ return `ctx:${orgId}:call:tool:${normalizeTableNamespace(toolId)}:`;
164
+ }
165
+
166
+ export function buildDurableToolProviderIdempotencyKey(input: {
167
+ receiptKey: string;
168
+ force?: boolean;
169
+ receiptLeaseId?: string | null;
170
+ fallbackAttemptId?: string | null;
171
+ }): string {
172
+ const receiptKey = input.receiptKey.trim();
173
+ if (!receiptKey) {
174
+ throw new Error('Need receipt key.');
175
+ }
176
+ if (input.force !== true) {
177
+ return receiptKey;
178
+ }
179
+ const attemptId =
180
+ input.receiptLeaseId?.trim() || input.fallbackAttemptId?.trim() || null;
181
+ if (!attemptId) {
182
+ throw new Error('Forced idempotency needs lease id.');
183
+ }
184
+ return `${receiptKey}:force:${attemptId}`;
185
+ }
186
+
187
+ export function buildDurableToolAggregateReceiptKey(input: {
188
+ receiptKeys: string[];
189
+ prefix?: 'batch' | null;
190
+ aggregateReceiptPrefix?: string | null;
191
+ }): string {
192
+ const receiptKeys = input.receiptKeys.map((key) => key.trim()).sort();
193
+ const digest = sha256Hex(stableStringify(receiptKeys));
194
+ if (input.prefix !== 'batch') {
195
+ return digest;
196
+ }
197
+ const scopedPrefix =
198
+ input.aggregateReceiptPrefix?.trim() ||
199
+ commonDurableToolReceiptPrefix(receiptKeys);
200
+ if (!scopedPrefix) {
201
+ throw new Error('Batch receipt key needs same-tool members.');
202
+ }
203
+ return `${scopedPrefix}batch:${digest}`;
204
+ }
205
+
206
+ function commonDurableToolReceiptPrefix(
207
+ receiptKeys: readonly string[],
208
+ ): string {
209
+ let commonPrefix: string | null = null;
210
+ for (const receiptKey of receiptKeys) {
211
+ const match = /^(ctx:[^:]+:call:tool:[^:]+:)/.exec(receiptKey);
212
+ if (!match) {
213
+ return '';
214
+ }
215
+ const prefix = match[1] ?? '';
216
+ if (!commonPrefix) {
217
+ commonPrefix = prefix;
218
+ continue;
219
+ }
220
+ if (commonPrefix !== prefix) {
221
+ return '';
222
+ }
223
+ }
224
+ return commonPrefix ?? '';
225
+ }
226
+
227
+ export function buildDurableToolAggregateProviderIdempotencyKey(input: {
228
+ aggregateReceiptKey: string;
229
+ receiptKeys: string[];
230
+ providerIdempotencyKeys: string[];
231
+ }): string {
232
+ const receiptKeys = input.receiptKeys.map((key) => key.trim());
233
+ const providerIdempotencyKeys = input.providerIdempotencyKeys.map((key) =>
234
+ key.trim(),
235
+ );
236
+ return `${input.aggregateReceiptKey}:ordered:${sha256Hex(
237
+ stableStringify({ receiptKeys, providerIdempotencyKeys }),
238
+ )}`;
239
+ }