deepline 0.3.90 → 0.3.92

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 (87) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +101 -6
  2. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +2 -556
  3. package/dist/bundling-sources/sdk/src/monitor-fleets.ts +2 -2
  4. package/dist/bundling-sources/sdk/src/release.ts +7 -12
  5. package/dist/bundling-sources/sdk/src/types.ts +6 -1
  6. package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +549 -0
  7. package/dist/bundling-sources/shared_libs/monitors/fleets/errors.ts +96 -0
  8. package/dist/bundling-sources/shared_libs/monitors/fleets/limits.ts +8 -0
  9. package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +320 -0
  10. package/dist/bundling-sources/shared_libs/monitors/fleets/verdict.ts +106 -0
  11. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +5 -1
  12. package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +2 -12
  13. package/dist/bundling-sources/shared_libs/monitors/package.json +107 -5
  14. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +12 -7
  15. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +48 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/db-session-lease-store.ts +578 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +521 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-receipt-gateway.ts +1378 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/runtime-topology.ts +1024 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-gateway-route-probe.ts +193 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machine-lifecycle.ts +104 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +1844 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +21 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/deployment/process-tree.ts +40 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +262 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-surface.ts +98 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +303 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-publication-guardian.ts +70 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-recovery.ts +519 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-fly-rollback-alignment.ts +280 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-production-gate.ts +159 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/memory-store.ts +114 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +44 -5
  35. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/child-play-resolver.ts +26 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +3662 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +47 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +84 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +91 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-heartbeat.ts +424 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +321 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +110 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +625 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +46 -3
  45. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +1 -1
  46. package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-retry.ts +50 -0
  47. package/dist/bundling-sources/shared_libs/plays/bundling/repository-adapter.ts +24 -0
  48. package/dist/bundling-sources/shared_libs/plays/child-run-projection.ts +174 -0
  49. package/dist/bundling-sources/shared_libs/plays/deploy/api-client.ts +267 -0
  50. package/dist/bundling-sources/shared_libs/plays/deploy/control-plane-types.ts +73 -0
  51. package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +162 -0
  52. package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +87 -0
  53. package/dist/bundling-sources/shared_libs/plays/deploy/source-file-path.ts +26 -0
  54. package/dist/bundling-sources/shared_libs/plays/deploy/target-paths.ts +36 -0
  55. package/dist/bundling-sources/shared_libs/plays/launch/input-contract.ts +400 -0
  56. package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +96 -0
  57. package/dist/bundling-sources/shared_libs/plays/local-file-discovery.ts +385 -0
  58. package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +79 -6
  59. package/dist/bundling-sources/shared_libs/plays/play-reference.ts +117 -0
  60. package/dist/bundling-sources/shared_libs/plays/run-input-summary.ts +260 -0
  61. package/dist/bundling-sources/shared_libs/plays/run-summary-types.ts +49 -0
  62. package/dist/bundling-sources/shared_libs/plays/secrets/errors.ts +19 -0
  63. package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +347 -0
  64. package/dist/bundling-sources/shared_libs/plays/standard-webhooks.ts +117 -0
  65. package/dist/bundling-sources/shared_libs/plays/status-result.ts +388 -0
  66. package/dist/bundling-sources/shared_libs/plays/transform-factor-structure.ts +377 -0
  67. package/dist/bundling-sources/shared_libs/plays/webhook-payload.ts +113 -0
  68. package/dist/cli/index.js +369 -162
  69. package/dist/cli/index.mjs +369 -162
  70. package/dist/{compiler-manifest-BaJfE-5H.d.mts → compiler-manifest-x14KL83E.d.mts} +1 -1
  71. package/dist/{compiler-manifest-BaJfE-5H.d.ts → compiler-manifest-x14KL83E.d.ts} +1 -1
  72. package/dist/index.d.mts +45 -8
  73. package/dist/index.d.ts +45 -8
  74. package/dist/index.js +61 -14
  75. package/dist/index.mjs +61 -14
  76. package/dist/install-integrity.json +54 -2
  77. package/dist/monitor-fleet-contract.d.mts +3 -10
  78. package/dist/monitor-fleet-contract.d.ts +3 -10
  79. package/dist/monitor-fleet-contract.js +2 -0
  80. package/dist/monitor-fleet-contract.mjs +1 -1
  81. package/dist/plays/bundle-play-file.d.mts +2 -2
  82. package/dist/plays/bundle-play-file.d.ts +2 -2
  83. package/dist/release.d.mts +3 -3
  84. package/dist/release.d.ts +3 -3
  85. package/dist/release.js +7 -12
  86. package/dist/release.mjs +7 -12
  87. package/package.json +2 -2
@@ -0,0 +1,321 @@
1
+ import type { PlayRunnerEvent } from '../protocol';
2
+ import {
3
+ RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
4
+ } from '../runtime-contract';
5
+
6
+ /**
7
+ * Push-execution live progress channel (runs INSIDE the Daytona sandbox).
8
+ *
9
+ * The babysitting model streamed progress by having the WORKER poll a progress
10
+ * JSONL sidecar file out of the sandbox every second. Push execution deletes
11
+ * that file poll: the runner instead PUSHES the same progress events to the
12
+ * receipt gateway's `runner_progress_events` action on a small batched cadence.
13
+ * The gateway folds them into the durable run-log/progress path (the same
14
+ * postgres-progress reporter the worker used), so live progress survives without
15
+ * per-second sandbox filesystem reads.
16
+ *
17
+ * Progress is best-effort observability: the authoritative terminal result still
18
+ * flows through the runner's stdout `result` event and the gateway's receipt
19
+ * terminal. A push failure must therefore never fail the play — it is logged and
20
+ * dropped.
21
+ */
22
+
23
+ export const RUNNER_PROGRESS_PUSH_BATCH_MS = 1_000;
24
+ export const RUNNER_PROGRESS_PUSH_MAX_BATCH = 500;
25
+ /**
26
+ * Progress is best-effort. Never let a stalled observability upload consume the
27
+ * runner's terminal-settlement grace or delay the authoritative result record.
28
+ */
29
+ export const RUNNER_PROGRESS_PUSH_STOP_TIMEOUT_MS = 5_000;
30
+
31
+ const APP_RUNTIME_COMPAT_PATH = '/api/v2/plays/runtime';
32
+
33
+ export interface RunnerProgressPusher {
34
+ /** Enqueue one progress event for the next batch flush. */
35
+ enqueue: (event: PlayRunnerEvent) => void;
36
+ /** Stop delivery and return all observations not known to be delivered. */
37
+ sealForTerminal: () => {
38
+ events: PlayRunnerEvent[];
39
+ liveLogOffset: number;
40
+ };
41
+ /** Flush any pending events and stop the batch timer. */
42
+ stop: () => Promise<void>;
43
+ }
44
+
45
+ export interface RunnerProgressPushOptions {
46
+ gatewayBaseUrl: string;
47
+ executorToken: string;
48
+ runId: string;
49
+ playName: string;
50
+ /** Injectable for tests. Defaults to a bearer POST to the gateway. */
51
+ sendBatch?: (input: {
52
+ gatewayBaseUrl: string;
53
+ executorToken: string;
54
+ runId: string;
55
+ playName: string;
56
+ events: PlayRunnerEvent[];
57
+ liveLogOffset: number;
58
+ signal: AbortSignal;
59
+ }) => Promise<void>;
60
+ setIntervalImpl?: (fn: () => void, ms: number) => { unref?: () => void };
61
+ clearIntervalImpl?: (handle: unknown) => void;
62
+ warn?: (message: string, detail: Record<string, unknown>) => void;
63
+ batchMs?: number;
64
+ maxBatch?: number;
65
+ stopTimeoutMs?: number;
66
+ }
67
+
68
+ async function defaultSendBatch(input: {
69
+ gatewayBaseUrl: string;
70
+ executorToken: string;
71
+ runId: string;
72
+ playName: string;
73
+ events: PlayRunnerEvent[];
74
+ liveLogOffset: number;
75
+ signal: AbortSignal;
76
+ }): Promise<void> {
77
+ const url = `${input.gatewayBaseUrl.replace(/\/$/, '')}${APP_RUNTIME_COMPAT_PATH}`;
78
+ const response = await fetch(url, {
79
+ method: 'POST',
80
+ headers: {
81
+ 'content-type': 'application/json',
82
+ authorization: `Bearer ${input.executorToken}`,
83
+ },
84
+ body: JSON.stringify({
85
+ action: 'runner_progress_events',
86
+ runId: input.runId,
87
+ playName: input.playName,
88
+ events: input.events,
89
+ liveLogOffset: input.liveLogOffset,
90
+ }),
91
+ signal: input.signal,
92
+ redirect: 'manual',
93
+ });
94
+ if (!response.ok) {
95
+ const body = await response.text().catch(() => '');
96
+ throw new Error(
97
+ `runner_progress_events failed (${response.status}): ${body}`,
98
+ );
99
+ }
100
+ await response.text().catch(() => '');
101
+ }
102
+
103
+ type ProgressBatch = {
104
+ events: PlayRunnerEvent[];
105
+ /** Number of durable play-authored log lines before this batch. */
106
+ liveLogOffset: number;
107
+ };
108
+
109
+ function durableLogCount(events: readonly PlayRunnerEvent[]): number {
110
+ return events.reduce(
111
+ (count, event) =>
112
+ event.type === 'log' && event.source !== 'worker' && event.line.length > 0
113
+ ? count + 1
114
+ : count,
115
+ 0,
116
+ );
117
+ }
118
+
119
+ export function createRunnerProgressPusher(
120
+ options: RunnerProgressPushOptions,
121
+ ): RunnerProgressPusher {
122
+ const sendBatch = options.sendBatch ?? defaultSendBatch;
123
+ const setIntervalImpl =
124
+ options.setIntervalImpl ??
125
+ ((fn, ms) => setInterval(fn, ms) as unknown as { unref?: () => void });
126
+ const clearIntervalImpl =
127
+ options.clearIntervalImpl ??
128
+ ((handle) => clearInterval(handle as ReturnType<typeof setInterval>));
129
+ const warn =
130
+ options.warn ??
131
+ ((message, detail) =>
132
+ console.warn(`[play-runner.progress-push] ${message}`, detail));
133
+ const batchMs = options.batchMs ?? RUNNER_PROGRESS_PUSH_BATCH_MS;
134
+ const maxBatch = options.maxBatch ?? RUNNER_PROGRESS_PUSH_MAX_BATCH;
135
+ const stopTimeoutMs =
136
+ options.stopTimeoutMs ?? RUNNER_PROGRESS_PUSH_STOP_TIMEOUT_MS;
137
+
138
+ const pending: PlayRunnerEvent[] = [];
139
+ const queuedBatches: ProgressBatch[] = [];
140
+ // A gateway acknowledgement is the durability boundary. Preserve any batch
141
+ // that failed before that boundary so the terminal request can
142
+ // carry it to whichever healthy replica Fly selects next.
143
+ const failedBatches: ProgressBatch[] = [];
144
+ const activeBatches = new Map<AbortController, ProgressBatch>();
145
+ let nextBatchLiveLogOffset = 0;
146
+ let deliveryBroken = false;
147
+ let stopped = false;
148
+ let abandoned = false;
149
+ let suppressWarnings = false;
150
+ let inFlight: Promise<void> = Promise.resolve();
151
+ let handle: { unref?: () => void } | null = null;
152
+
153
+ const flush = (): Promise<void> => {
154
+ if (abandoned) {
155
+ pending.length = 0;
156
+ return inFlight;
157
+ }
158
+ if (pending.length === 0 || deliveryBroken) return inFlight;
159
+ const events = pending.splice(0, Math.min(pending.length, maxBatch));
160
+ const batch: ProgressBatch = {
161
+ events,
162
+ liveLogOffset: nextBatchLiveLogOffset,
163
+ };
164
+ nextBatchLiveLogOffset += durableLogCount(events);
165
+ queuedBatches.push(batch);
166
+ inFlight = inFlight
167
+ .catch(() => {})
168
+ .then(async () => {
169
+ // stop() can time out while this batch waits behind an earlier upload.
170
+ // Do not start it after the authoritative terminal has been emitted.
171
+ if (abandoned) return;
172
+ if (deliveryBroken) {
173
+ const queuedIndex = queuedBatches.indexOf(batch);
174
+ if (queuedIndex >= 0) queuedBatches.splice(queuedIndex, 1);
175
+ failedBatches.push(batch);
176
+ return;
177
+ }
178
+ const controller = new AbortController();
179
+ const queuedIndex = queuedBatches.indexOf(batch);
180
+ if (queuedIndex >= 0) queuedBatches.splice(queuedIndex, 1);
181
+ activeBatches.set(controller, batch);
182
+ try {
183
+ await sendBatch({
184
+ gatewayBaseUrl: options.gatewayBaseUrl,
185
+ executorToken: options.executorToken,
186
+ runId: options.runId,
187
+ playName: options.playName,
188
+ events: batch.events,
189
+ liveLogOffset: batch.liveLogOffset,
190
+ signal: controller.signal,
191
+ });
192
+ } catch (error) {
193
+ if (!abandoned) {
194
+ failedBatches.push(batch);
195
+ // Offsets are positional. After one unknown delivery outcome, keep
196
+ // the remaining contiguous suffix for the terminal request rather
197
+ // than sending holes that another replica could misinterpret.
198
+ deliveryBroken = true;
199
+ }
200
+ if (!suppressWarnings) {
201
+ warn('batch_failed', {
202
+ runId: options.runId,
203
+ count: batch.events.length,
204
+ error: error instanceof Error ? error.message : String(error),
205
+ });
206
+ }
207
+ } finally {
208
+ activeBatches.delete(controller);
209
+ }
210
+ });
211
+ return inFlight;
212
+ };
213
+
214
+ handle = setIntervalImpl(() => {
215
+ void flush();
216
+ }, batchMs);
217
+ handle.unref?.();
218
+
219
+ return {
220
+ enqueue(event) {
221
+ if (stopped) return;
222
+ pending.push(event);
223
+ if (pending.length >= maxBatch) {
224
+ void flush();
225
+ }
226
+ },
227
+ sealForTerminal() {
228
+ if (stopped) return { events: [], liveLogOffset: nextBatchLiveLogOffset };
229
+ stopped = true;
230
+ if (handle) {
231
+ clearIntervalImpl(handle);
232
+ handle = null;
233
+ }
234
+ // Preserve every batch removed from `pending`, including batches queued
235
+ // behind an earlier upload. The terminal request is the final owner of
236
+ // these observations; do not drop a burst merely because it has not yet
237
+ // acquired an AbortController.
238
+ const retainedBatches: ProgressBatch[] = [
239
+ ...failedBatches,
240
+ ...activeBatches.values(),
241
+ ...queuedBatches,
242
+ ...(pending.length
243
+ ? [{ events: [...pending], liveLogOffset: nextBatchLiveLogOffset }]
244
+ : []),
245
+ ];
246
+ const unboundedTail = retainedBatches.flatMap((batch) => batch.events);
247
+ const droppedEvents = Math.max(
248
+ 0,
249
+ unboundedTail.length - RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
250
+ );
251
+ const droppedPrefix = unboundedTail.slice(0, droppedEvents);
252
+ const tail = unboundedTail.slice(
253
+ -RUNTIME_RUNNER_TERMINAL_PROGRESS_MAX_EVENTS,
254
+ );
255
+ const liveLogOffset =
256
+ (retainedBatches[0]?.liveLogOffset ?? nextBatchLiveLogOffset) +
257
+ durableLogCount(droppedPrefix);
258
+ if (tail.length < unboundedTail.length) {
259
+ warn('terminal_tail_truncated', {
260
+ runId: options.runId,
261
+ retainedEvents: tail.length,
262
+ droppedEvents: unboundedTail.length - tail.length,
263
+ });
264
+ }
265
+ failedBatches.length = 0;
266
+ queuedBatches.length = 0;
267
+ pending.length = 0;
268
+ abandoned = true;
269
+ suppressWarnings = true;
270
+ for (const controller of activeBatches.keys()) controller.abort();
271
+ return { events: tail, liveLogOffset };
272
+ },
273
+ async stop() {
274
+ if (stopped) return;
275
+ stopped = true;
276
+ if (handle) {
277
+ clearIntervalImpl(handle);
278
+ handle = null;
279
+ }
280
+ const drain = async () => {
281
+ // Drain everything still buffered.
282
+ while (pending.length > 0 && !deliveryBroken) {
283
+ await flush();
284
+ }
285
+ await inFlight.catch(() => {});
286
+ if (deliveryBroken) pending.length = 0;
287
+ };
288
+ let stopTimeoutHandle: ReturnType<typeof setTimeout> | null = null;
289
+ const outcome = await Promise.race([
290
+ drain().then(() => 'drained' as const),
291
+ new Promise<'timed_out'>((resolve) => {
292
+ stopTimeoutHandle = setTimeout(
293
+ () => resolve('timed_out'),
294
+ stopTimeoutMs,
295
+ );
296
+ }),
297
+ ]);
298
+ if (stopTimeoutHandle) clearTimeout(stopTimeoutHandle);
299
+ if (outcome === 'drained') return;
300
+
301
+ // The authoritative terminal follows this method. Abort ordinary fetches
302
+ // and drop non-cooperative test/transport promises without letting their
303
+ // later rejection write a post-terminal diagnostic.
304
+ abandoned = true;
305
+ suppressWarnings = true;
306
+ const pendingEvents = pending.length;
307
+ pending.length = 0;
308
+ const inFlightEvents = [...activeBatches.values()].reduce(
309
+ (sum, batch) => sum + batch.events.length,
310
+ 0,
311
+ );
312
+ for (const controller of activeBatches.keys()) controller.abort();
313
+ warn('drain_timed_out', {
314
+ runId: options.runId,
315
+ timeoutMs: stopTimeoutMs,
316
+ pendingEvents,
317
+ inFlightEvents,
318
+ });
319
+ },
320
+ };
321
+ }
@@ -0,0 +1,110 @@
1
+ import { isAppRuntimeApiCapacityError } from '../app-runtime-api';
2
+ import { isRuntimeApiCapacityError } from '../runtime-api';
3
+
4
+ const RUNTIME_SHEET_CONTENTION_MAX_ATTEMPTS = 4;
5
+
6
+ function persistenceCapacityRetryAfterMs(error: unknown): number | null {
7
+ if (isAppRuntimeApiCapacityError(error) || isRuntimeApiCapacityError(error)) {
8
+ return Math.max(0, Math.ceil(error.retryAfterMs));
9
+ }
10
+ return null;
11
+ }
12
+
13
+ function capacityRetryDelayMs(
14
+ retryAfterMs: number,
15
+ random: () => number,
16
+ ): number {
17
+ // retryAfterMs is the server's lower bound. Add one-sided jitter so a
18
+ // synchronized cohort spreads out without retrying before capacity is
19
+ // expected to be available.
20
+ const jitter = Math.max(0, Math.min(1, random())) * 0.2 + 1;
21
+ return Math.ceil(retryAfterMs * jitter);
22
+ }
23
+
24
+ function isRetryableRuntimePersistenceContention(error: unknown): boolean {
25
+ const message = error instanceof Error ? error.message : String(error);
26
+ return /deadlock detected|Runtime Postgres connection timed out|could not serialize access|tuple concurrently updated/i.test(
27
+ message,
28
+ );
29
+ }
30
+
31
+ function throwIfAborted(signal: AbortSignal | undefined): void {
32
+ if (!signal?.aborted) return;
33
+ if (signal.reason !== undefined) throw signal.reason;
34
+ const error = new Error('Runtime Sheet persistence retry was aborted.');
35
+ error.name = 'AbortError';
36
+ throw error;
37
+ }
38
+
39
+ async function abortableSleep(
40
+ ms: number,
41
+ signal: AbortSignal | undefined,
42
+ ): Promise<void> {
43
+ throwIfAborted(signal);
44
+ await new Promise<void>((resolve, reject) => {
45
+ const timeout = setTimeout(() => {
46
+ signal?.removeEventListener('abort', onAbort);
47
+ resolve();
48
+ }, ms);
49
+ const onAbort = () => {
50
+ clearTimeout(timeout);
51
+ signal?.removeEventListener('abort', onAbort);
52
+ try {
53
+ throwIfAborted(signal);
54
+ } catch (error) {
55
+ reject(error);
56
+ }
57
+ };
58
+ signal?.addEventListener('abort', onAbort, { once: true });
59
+ });
60
+ }
61
+
62
+ export async function retryRuntimeSheetPersistence<T>(input: {
63
+ description: string;
64
+ log: (line: string) => void;
65
+ run: () => Promise<T>;
66
+ signal: AbortSignal;
67
+ random?: () => number;
68
+ sleep?: (ms: number, signal: AbortSignal | undefined) => Promise<void>;
69
+ }): Promise<T> {
70
+ const random = input.random ?? Math.random;
71
+ const sleep = input.sleep ?? abortableSleep;
72
+ let contentionAttempt = 0;
73
+ let capacityAttempt = 0;
74
+
75
+ for (;;) {
76
+ throwIfAborted(input.signal);
77
+ try {
78
+ return await input.run();
79
+ } catch (error) {
80
+ throwIfAborted(input.signal);
81
+ const retryAfterMs = persistenceCapacityRetryAfterMs(error);
82
+ if (retryAfterMs !== null) {
83
+ capacityAttempt += 1;
84
+ const delayMs = capacityRetryDelayMs(retryAfterMs, random);
85
+ input.log(
86
+ `[warn] ${input.description} waiting for persistence capacity attempt=${capacityAttempt} delay_ms=${delayMs}: ${
87
+ error instanceof Error ? error.message : String(error)
88
+ }`,
89
+ );
90
+ await sleep(delayMs, input.signal);
91
+ continue;
92
+ }
93
+
94
+ contentionAttempt += 1;
95
+ if (
96
+ contentionAttempt >= RUNTIME_SHEET_CONTENTION_MAX_ATTEMPTS ||
97
+ !isRetryableRuntimePersistenceContention(error)
98
+ ) {
99
+ throw error;
100
+ }
101
+ const delayMs = 150 * contentionAttempt;
102
+ input.log(
103
+ `[warn] ${input.description} retryable persistence contention attempt=${contentionAttempt}/${RUNTIME_SHEET_CONTENTION_MAX_ATTEMPTS} delay_ms=${delayMs}: ${
104
+ error instanceof Error ? error.message : String(error)
105
+ }`,
106
+ );
107
+ await sleep(delayMs, input.signal);
108
+ }
109
+ }
110
+ }