deepline 0.3.127 → 0.3.129

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 (128) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +34 -9
  2. package/dist/bundling-sources/sdk/src/http.ts +22 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  17. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  27. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  30. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  31. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  32. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  35. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  42. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  52. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  67. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  83. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  103. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  104. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  109. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  110. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  111. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  112. package/dist/cli/index.js +254 -38
  113. package/dist/cli/index.mjs +254 -38
  114. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  115. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  116. package/dist/index.d.mts +4 -2
  117. package/dist/index.d.ts +4 -2
  118. package/dist/index.js +231 -35
  119. package/dist/index.mjs +231 -35
  120. package/dist/install-integrity.json +31 -2
  121. package/dist/plays/bundle-play-file.d.mts +2 -2
  122. package/dist/plays/bundle-play-file.d.ts +2 -2
  123. package/dist/plays/bundle-play-file.mjs +65 -1
  124. package/dist/release.d.mts +1 -1
  125. package/dist/release.d.ts +1 -1
  126. package/dist/release.js +1 -1
  127. package/dist/release.mjs +1 -1
  128. package/package.json +1 -1
@@ -0,0 +1,452 @@
1
+ /**
2
+ * Gateway-side commit coalescing for runtime step receipt completions.
3
+ *
4
+ * A runner settles receipts continuously, so a Machine sees many small
5
+ * `complete_runtime_step_receipts` requests arrive within a few milliseconds of
6
+ * each other. Each one costs a heavy-admission slot, a pooled client, and its
7
+ * own storage statement, which is why the commit lane saturated long before
8
+ * Postgres did (measured: p50 1 ms / p99 66 ms per statement while the lane was
9
+ * the fleet-wide chokepoint).
10
+ *
11
+ * This Module holds concurrent requests for a short window and commits them as
12
+ * ONE storage statement under ONE slot, then hands each request exactly its own
13
+ * receipts' results, in its original order. It owns nothing else: the caller
14
+ * supplies the commit, which owns admission, the client, and the statement.
15
+ *
16
+ * Coalescing is only sound inside one commit key. The completion statement is
17
+ * org-scoped (`target.org_id = $6`) and schema-scoped by table name, and its
18
+ * response projection depends on the request's legacy wire format, so requests
19
+ * from different orgs, schemas, or wire formats are never merged. Run identity
20
+ * IS per input row in the statement, so requests from different runs of the
21
+ * same org share a statement safely.
22
+ *
23
+ * Windows are additionally split by payload size class. Merging is only worth
24
+ * doing between statements of comparable cost: folding a 9 KB batch into a
25
+ * window that is building a multi-megabyte statement makes the cheap batch wait
26
+ * for the expensive one to be built AND committed, which is the head-of-line
27
+ * blocking the size classes exist to remove. So a window's class is fixed when
28
+ * it opens, a light window a request would push over the threshold is committed
29
+ * before that request joins anything, and light and heavy windows of the same
30
+ * commit key are built and committed independently.
31
+ */
32
+
33
+ import { BoundedBatchFunction } from '../bounded-batch-function';
34
+
35
+ /** The gateway admission adapter owns how these two classes acquire capacity. */
36
+ export type GatewayCommitSizeClass = 'light' | 'heavy';
37
+
38
+ /**
39
+ * Window for gathering concurrent completion requests. A completion is already
40
+ * asynchronous relative to the row that produced it, so a short hold costs the
41
+ * runner nothing while turning a burst into a single statement. Keep it well
42
+ * below the runner's own completion window so the gateway never becomes the
43
+ * dominant term.
44
+ */
45
+ export const RUNTIME_RECEIPT_GATEWAY_COMPLETION_COALESCE_MS = 20;
46
+
47
+ /**
48
+ * Receipts one coalesced statement may carry.
49
+ *
50
+ * Mirrors the runner's `RUNTIME_STEP_RECEIPT_REQUEST_CHUNK_SIZE` (play-runtime
51
+ * `context.ts`), which already bounds a single completion request: one
52
+ * statement therefore never carries more than one runner request's worth of
53
+ * receipts, whatever the window collected. The coalescer test pins the two
54
+ * values together, since only the runner's constant is importable here without
55
+ * pulling the whole runtime into the gateway process.
56
+ */
57
+ export const RUNTIME_RECEIPT_GATEWAY_COMPLETION_MAX_RECEIPTS = 1_000;
58
+
59
+ /** Identity that must match for two requests to share one statement. */
60
+ export type ReceiptCompletionCommitKey = {
61
+ orgId: string;
62
+ schema: string | null;
63
+ wireFormat: number;
64
+ };
65
+
66
+ export function receiptCompletionCommitKeyId(
67
+ key: ReceiptCompletionCommitKey,
68
+ ): string {
69
+ // Org ids and schema names never contain '|', so the id is unambiguous.
70
+ return `${key.orgId}|${key.schema ?? ''}|${key.wireFormat}`;
71
+ }
72
+
73
+ /**
74
+ * Identity of one open window: a commit key plus the size class of the
75
+ * statement it is building. Two windows of the same commit key but different
76
+ * size classes are legal and deliberate; each commits on its own.
77
+ */
78
+ export function receiptCompletionWindowId(
79
+ key: ReceiptCompletionCommitKey,
80
+ sizeClass: GatewayCommitSizeClass,
81
+ ): string {
82
+ return `${receiptCompletionCommitKeyId(key)}|${sizeClass}`;
83
+ }
84
+
85
+ /** One committed statement, for the per-statement telemetry record. */
86
+ export type ReceiptCompletionStatementRecord = {
87
+ key: ReceiptCompletionCommitKey;
88
+ /** Requests merged into this statement. */
89
+ requestCount: number;
90
+ /** Receipts across those requests. */
91
+ receiptCount: number;
92
+ /** Caller-estimated request bytes across those requests. */
93
+ bytes: number;
94
+ /**
95
+ * On-the-wire body bytes of the merged requests that carried these receipts.
96
+ *
97
+ * `bytes` is the DECODED size the statement is admitted and classified on;
98
+ * this is what the network actually moved. They are equal when the requests
99
+ * arrived uncompressed (the caller supplies no wire size and it defaults to
100
+ * `bytes`), and `wireBytes / bytes` is the compression ratio the transport
101
+ * achieved when they arrived gzipped. Admission never reads it: a compressed
102
+ * request costs the statement exactly as much decoded work as an
103
+ * uncompressed one, so shrinking the wire must not shrink the size class.
104
+ */
105
+ wireBytes: number;
106
+ /** Admission class this statement was committed under. */
107
+ sizeClass: GatewayCommitSizeClass;
108
+ /** Longest time any merged request waited before the statement started. */
109
+ waitMs: number;
110
+ /**
111
+ * Time the commit spent waiting for its admission slot, as reported by the
112
+ * commit itself. Zero when the caller's commit takes no admission.
113
+ */
114
+ admissionWaitMs: number;
115
+ /** Wall time of the commit itself, including admission and client acquire. */
116
+ statementMs: number;
117
+ outcome: 'committed' | 'failed';
118
+ errorType: string | null;
119
+ /**
120
+ * Per-run share of the statement, so a readout can attribute gateway bytes
121
+ * and statement time to the runs that produced them. One coalesced
122
+ * statement may carry several runs of one org; the list is bounded and
123
+ * `runCount` says how many runs the statement really carried.
124
+ */
125
+ runs: ReceiptCompletionRunShare[];
126
+ runCount: number;
127
+ };
128
+
129
+ export type ReceiptCompletionRunShare = {
130
+ runId: string;
131
+ /**
132
+ * Transport attempt ids of the requests this run contributed, so a readout
133
+ * can pair the gateway's statement timing with the runner's flight for
134
+ * the same batch.
135
+ */
136
+ transportAttemptIds: string[];
137
+ requestCount: number;
138
+ receiptCount: number;
139
+ bytes: number;
140
+ /** This run's share of the statement's on-the-wire body bytes. */
141
+ wireBytes: number;
142
+ };
143
+
144
+ /** Runs listed per committed statement; the rest are counted, not listed. */
145
+ export const RUNTIME_RECEIPT_GATEWAY_COMMITTED_RUNS_MAX = 16;
146
+
147
+ type PendingRequest<TReceipt> = {
148
+ receipts: readonly TReceipt[];
149
+ transportAttemptId: string | null;
150
+ bytes: number;
151
+ wireBytes: number;
152
+ enqueuedAt: number;
153
+ };
154
+
155
+ type ReceiptCompletionWindowKey = {
156
+ key: ReceiptCompletionCommitKey;
157
+ sizeClass: GatewayCommitSizeClass;
158
+ };
159
+
160
+ export class ReceiptCompletionResultCountError extends Error {
161
+ constructor(
162
+ readonly expected: number,
163
+ readonly received: number,
164
+ ) {
165
+ super(
166
+ `Coalesced receipt completion returned ${received} results for ${expected} receipts.`,
167
+ );
168
+ this.name = 'ReceiptCompletionResultCountError';
169
+ }
170
+ }
171
+
172
+ export class ReceiptCompletionCoalescer<TReceipt, TResult> {
173
+ readonly #commit: (input: {
174
+ key: ReceiptCompletionCommitKey;
175
+ receipts: TReceipt[];
176
+ sizeClass: GatewayCommitSizeClass;
177
+ bytes: number;
178
+ onAdmitted: () => void;
179
+ }) => Promise<TResult[]>;
180
+ readonly #lightMaxBytes: number;
181
+ readonly #onStatement:
182
+ | ((record: ReceiptCompletionStatementRecord) => void)
183
+ | null;
184
+ readonly #requestRunId:
185
+ | ((receipts: readonly TReceipt[]) => string | null)
186
+ | null;
187
+ readonly #now: () => number;
188
+ readonly #batches: BoundedBatchFunction<
189
+ PendingRequest<TReceipt>,
190
+ TResult[],
191
+ ReceiptCompletionWindowKey
192
+ >;
193
+
194
+ constructor(input: {
195
+ /**
196
+ * Commit these receipts as one statement. The caller owns heavy-request
197
+ * admission, the pooled client, and the storage call. Results MUST be
198
+ * index-aligned with the supplied receipts.
199
+ */
200
+ commit: (input: {
201
+ key: ReceiptCompletionCommitKey;
202
+ receipts: TReceipt[];
203
+ /** Admission class the caller must commit this statement under. */
204
+ sizeClass: GatewayCommitSizeClass;
205
+ /** Caller-estimated bytes of the merged statement. */
206
+ bytes: number;
207
+ /**
208
+ * Call once the commit holds its admission slot. Reports
209
+ * `admissionWaitMs` for this statement; a commit that takes no admission
210
+ * simply never calls it.
211
+ */
212
+ onAdmitted: () => void;
213
+ }) => Promise<TResult[]>;
214
+ /** Receipts one statement may carry; matches the runner's request chunk. */
215
+ maxReceipts: number;
216
+ /** Estimated request bytes one statement may carry. */
217
+ maxBytes: number;
218
+ /**
219
+ * Bytes at or above which a statement is heavy. A window stays strictly
220
+ * below this while it is light, so a light statement's cost stays bounded
221
+ * and its own admission lane keeps draining.
222
+ */
223
+ lightMaxBytes: number;
224
+ windowMs?: number;
225
+ onStatement?: (record: ReceiptCompletionStatementRecord) => void;
226
+ /**
227
+ * Run identity of one request's receipts (every request carries receipts
228
+ * of exactly one run). Without it statements record no run shares.
229
+ */
230
+ requestRunId?: (receipts: readonly TReceipt[]) => string | null;
231
+ now?: () => number;
232
+ /**
233
+ * Bounded retained requests across queued and active commit statements.
234
+ * Defaults keep four maximum receipt statements resident per Machine.
235
+ */
236
+ maxRetainedRequests?: number;
237
+ maxInFlightStatements?: number;
238
+ }) {
239
+ if (!Number.isSafeInteger(input.maxReceipts) || input.maxReceipts < 1) {
240
+ throw new Error(
241
+ 'Receipt completion coalescer requires a positive maxReceipts.',
242
+ );
243
+ }
244
+ if (!Number.isSafeInteger(input.maxBytes) || input.maxBytes < 1) {
245
+ throw new Error(
246
+ 'Receipt completion coalescer requires a positive maxBytes.',
247
+ );
248
+ }
249
+ if (!Number.isSafeInteger(input.lightMaxBytes) || input.lightMaxBytes < 1) {
250
+ throw new Error(
251
+ 'Receipt completion coalescer requires a positive lightMaxBytes.',
252
+ );
253
+ }
254
+ this.#commit = input.commit;
255
+ this.#lightMaxBytes = input.lightMaxBytes;
256
+ this.#onStatement = input.onStatement ?? null;
257
+ this.#requestRunId = input.requestRunId ?? null;
258
+ this.#now = input.now ?? Date.now;
259
+ const maxRetainedRequests =
260
+ input.maxRetainedRequests ??
261
+ Math.max(input.maxReceipts, input.maxReceipts * 4);
262
+ const maxInFlightStatements = input.maxInFlightStatements ?? 4;
263
+ this.#batches = new BoundedBatchFunction({
264
+ keyId: (window) =>
265
+ receiptCompletionWindowId(window.key, window.sizeClass),
266
+ // `light` stays strictly below the heavy threshold. A request exactly at
267
+ // that threshold belongs to the heavy lane, matching the prior gateway
268
+ // coalescer contract.
269
+ maxBatchBytesForKey: (window) =>
270
+ window.sizeClass === 'light'
271
+ ? Math.max(1, input.lightMaxBytes - 1)
272
+ : input.maxBytes,
273
+ estimateBytes: (request) => request.bytes,
274
+ units: (request) => request.receipts.length,
275
+ maxBatchItems: input.maxReceipts,
276
+ maxBatchBytes: input.maxBytes,
277
+ maxBatchUnits: input.maxReceipts,
278
+ maxRetainedItems: maxRetainedRequests,
279
+ maxRetainedBytes: input.maxBytes * 4,
280
+ maxRetainedUnits: input.maxReceipts * 4,
281
+ maxOpenGroups: Math.min(maxRetainedRequests, 128),
282
+ maxInFlightBatches: maxInFlightStatements,
283
+ flushMs: input.windowMs ?? RUNTIME_RECEIPT_GATEWAY_COMPLETION_COALESCE_MS,
284
+ now: this.#now,
285
+ execute: async ({ key, values }) =>
286
+ await this.#commitRequests(key, values),
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Submit one request's receipts. Resolves with exactly this request's
292
+ * results, in the order its receipts were supplied. A statement failure
293
+ * rejects every merged request with that same error, so each caller sees the
294
+ * response it would have seen alone and its own retry stays safe.
295
+ */
296
+ submit(input: {
297
+ key: ReceiptCompletionCommitKey;
298
+ receipts: readonly TReceipt[];
299
+ /** Caller-estimated request bytes, used only for the byte bound. */
300
+ bytes?: number;
301
+ /**
302
+ * On-the-wire body bytes of the request that carried these receipts.
303
+ * Telemetry only — it never affects the byte bound or the size class.
304
+ * Defaults to `bytes`, which is the truth for an uncompressed request.
305
+ */
306
+ wireBytes?: number;
307
+ /** Runner transport identity, used only for the bounded telemetry share. */
308
+ transportAttemptId?: string | null;
309
+ }): Promise<TResult[]> {
310
+ if (input.receipts.length === 0) return Promise.resolve([]);
311
+ const bytes = Math.max(0, Math.floor(input.bytes ?? 0));
312
+ const wireBytes = Math.max(0, Math.floor(input.wireBytes ?? bytes));
313
+ // A request that is already heavy on its own can never make a light
314
+ // statement, so it goes straight to a heavy window instead of closing the
315
+ // light one that other shapes are still filling.
316
+ const sizeClass: GatewayCommitSizeClass =
317
+ bytes >= this.#lightMaxBytes ? 'heavy' : 'light';
318
+ return this.#batches.submit({
319
+ key: { key: input.key, sizeClass },
320
+ value: {
321
+ receipts: input.receipts,
322
+ transportAttemptId: input.transportAttemptId ?? null,
323
+ bytes,
324
+ wireBytes,
325
+ enqueuedAt: this.#now(),
326
+ },
327
+ });
328
+ }
329
+
330
+ /** Commit every open window now and wait for all in-flight statements. */
331
+ async drain(): Promise<void> {
332
+ await this.#batches.drain();
333
+ }
334
+
335
+ async #commitRequests(
336
+ window: ReceiptCompletionWindowKey,
337
+ requests: readonly PendingRequest<TReceipt>[],
338
+ ): Promise<TResult[][]> {
339
+ const startedAt = this.#now();
340
+ const waitMs = requests.reduce(
341
+ (longest, request) =>
342
+ Math.max(longest, Math.max(0, startedAt - request.enqueuedAt)),
343
+ 0,
344
+ );
345
+ const receipts = requests.flatMap((request) => [...request.receipts]);
346
+ let admissionWaitMs = 0;
347
+ const onAdmitted = () => {
348
+ admissionWaitMs = Math.max(0, this.#now() - startedAt);
349
+ };
350
+ const outcome: Pick<
351
+ ReceiptCompletionStatementRecord,
352
+ 'waitMs' | 'admissionWaitMs' | 'statementMs' | 'outcome' | 'errorType'
353
+ > = {
354
+ waitMs,
355
+ admissionWaitMs,
356
+ statementMs: 0,
357
+ outcome: 'failed',
358
+ errorType: null,
359
+ };
360
+ try {
361
+ const results = await this.#commit({
362
+ key: window.key,
363
+ receipts,
364
+ sizeClass: window.sizeClass,
365
+ bytes: requests.reduce((total, request) => total + request.bytes, 0),
366
+ onAdmitted,
367
+ });
368
+ if (results.length !== receipts.length) {
369
+ throw new ReceiptCompletionResultCountError(
370
+ receipts.length,
371
+ results.length,
372
+ );
373
+ }
374
+ let offset = 0;
375
+ const requestResults: TResult[][] = [];
376
+ for (const request of requests) {
377
+ const slice = results.slice(offset, offset + request.receipts.length);
378
+ offset += request.receipts.length;
379
+ requestResults.push(slice);
380
+ }
381
+ outcome.admissionWaitMs = admissionWaitMs;
382
+ outcome.statementMs = this.#now() - startedAt;
383
+ outcome.outcome = 'committed';
384
+ this.#record(window, requests, outcome);
385
+ return requestResults;
386
+ } catch (error) {
387
+ outcome.admissionWaitMs = admissionWaitMs;
388
+ outcome.statementMs = this.#now() - startedAt;
389
+ outcome.errorType = error instanceof Error ? error.name : typeof error;
390
+ this.#record(window, requests, outcome);
391
+ throw error;
392
+ }
393
+ }
394
+
395
+ #record(
396
+ window: ReceiptCompletionWindowKey,
397
+ requests: readonly PendingRequest<TReceipt>[],
398
+ outcome: Pick<
399
+ ReceiptCompletionStatementRecord,
400
+ 'waitMs' | 'admissionWaitMs' | 'statementMs' | 'outcome' | 'errorType'
401
+ >,
402
+ ): void {
403
+ if (!this.#onStatement) return;
404
+ try {
405
+ const shares = new Map<string, ReceiptCompletionRunShare>();
406
+ if (this.#requestRunId) {
407
+ for (const request of requests) {
408
+ const runId = this.#requestRunId(request.receipts);
409
+ if (!runId) continue;
410
+ const share = shares.get(runId) ?? {
411
+ runId,
412
+ transportAttemptIds: [],
413
+ requestCount: 0,
414
+ receiptCount: 0,
415
+ bytes: 0,
416
+ wireBytes: 0,
417
+ };
418
+ share.requestCount += 1;
419
+ if (request.transportAttemptId) {
420
+ share.transportAttemptIds.push(request.transportAttemptId);
421
+ }
422
+ share.receiptCount += request.receipts.length;
423
+ share.bytes += request.bytes;
424
+ share.wireBytes += request.wireBytes;
425
+ shares.set(runId, share);
426
+ }
427
+ }
428
+ this.#onStatement({
429
+ key: window.key,
430
+ requestCount: requests.length,
431
+ receiptCount: requests.reduce(
432
+ (total, request) => total + request.receipts.length,
433
+ 0,
434
+ ),
435
+ bytes: requests.reduce((total, request) => total + request.bytes, 0),
436
+ wireBytes: requests.reduce(
437
+ (total, request) => total + request.wireBytes,
438
+ 0,
439
+ ),
440
+ sizeClass: window.sizeClass,
441
+ runs: [...shares.values()].slice(
442
+ 0,
443
+ RUNTIME_RECEIPT_GATEWAY_COMMITTED_RUNS_MAX,
444
+ ),
445
+ runCount: shares.size,
446
+ ...outcome,
447
+ });
448
+ } catch {
449
+ // Telemetry must never affect durable delivery.
450
+ }
451
+ }
452
+ }