deepline 0.3.245 → 0.3.247

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.
@@ -113,7 +113,10 @@ import type { MonitorFleetDefinition } from './monitor-fleets.js';
113
113
  import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
114
114
  import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
115
115
  import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
116
- import { RUNTIME_ENVIRONMENT_TOKEN_HEADER } from '../../shared_libs/play-runtime/coordinator-headers.js';
116
+ import {
117
+ RUNTIME_ENVIRONMENT_TOKEN_HEADER,
118
+ RETAINED_PIPELINE_RECEIVERS_HEADER,
119
+ } from '../../shared_libs/play-runtime/coordinator-headers.js';
117
120
  import {
118
121
  normalizePlayRuntimeEnvironment,
119
122
  normalizePlayRuntimeNamespace,
@@ -123,6 +126,7 @@ import {
123
126
  } from '../../shared_libs/play-runtime/runtime-environment.js';
124
127
  import {
125
128
  decodePlayRunPublicStatus,
129
+ isTerminalPlayRunLifecycleStatus,
126
130
  type PlayRunPublicStatus,
127
131
  } from '../../shared_libs/play-runtime/run-lifecycle-policy.js';
128
132
  export type SlackNotificationTarget =
@@ -209,7 +213,6 @@ function notificationObjectKey(kind: 'group' | 'destination') {
209
213
  return `${kind}.${globalThis.crypto.randomUUID()}`;
210
214
  }
211
215
 
212
- const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
213
216
  const MONITOR_NON_RETRYABLE_MUTATION_OPTIONS = {
214
217
  maxRetries: 0,
215
218
  exactUrlOnly: true,
@@ -403,7 +406,12 @@ function runtimeSelectionHeaders(
403
406
  'RUNTIME_ENVIRONMENT_TOKEN_REQUIRED',
404
407
  );
405
408
  }
406
- return { [RUNTIME_ENVIRONMENT_TOKEN_HEADER]: token };
409
+ const receivers =
410
+ process.env.DEEPLINE_RUNTIME_RETAINED_PIPELINE_RECEIVERS?.trim();
411
+ return {
412
+ [RUNTIME_ENVIRONMENT_TOKEN_HEADER]: token,
413
+ ...(receivers ? { [RETAINED_PIPELINE_RECEIVERS_HEADER]: receivers } : {}),
414
+ };
407
415
  }
408
416
 
409
417
  function normalizeTestPolicyOverrides(
@@ -2306,7 +2314,9 @@ function normalizePlayRunStart(raw: Record<string, unknown>): PlayRunStart {
2306
2314
  ...(runPackage.run.dashboardUrl
2307
2315
  ? { dashboardUrl: runPackage.run.dashboardUrl }
2308
2316
  : {}),
2309
- ...(TERMINAL_PLAY_STATUSES.has(status) ? { finalStatus: runPackage } : {}),
2317
+ ...(isTerminalPlayRunLifecycleStatus(status)
2318
+ ? { finalStatus: runPackage }
2319
+ : {}),
2310
2320
  package: runPackage,
2311
2321
  };
2312
2322
  }
@@ -2469,18 +2479,22 @@ function getPlayLiveEventPayload(
2469
2479
  : {};
2470
2480
  }
2471
2481
 
2472
- function normalizeLiveStatus(value: unknown): PlayStatus['status'] | null {
2482
+ /** Run lifecycle events are the only events that can finish a watch. */
2483
+ function playLiveRunStatus(event: PlayLiveEvent): PlayStatus['status'] | null {
2473
2484
  if (
2474
- value === 'queued' ||
2475
- value === 'running' ||
2476
- value === 'waiting' ||
2477
- value === 'completed' ||
2478
- value === 'failed' ||
2479
- value === 'cancelled'
2485
+ event.type !== 'play.run.snapshot' &&
2486
+ event.type !== 'play.run.status' &&
2487
+ event.type !== 'play.run.final_status'
2480
2488
  ) {
2481
- return value;
2489
+ return null;
2482
2490
  }
2483
- return null;
2491
+ const payload = getPlayLiveEventPayload(event);
2492
+ return (
2493
+ decodePlayRunPublicStatus(payload.status) ??
2494
+ (isPlayRunPackage(payload)
2495
+ ? decodePlayRunPublicStatus(payload.run.status)
2496
+ : null)
2497
+ );
2484
2498
  }
2485
2499
 
2486
2500
  function appendPlayLiveLogLines(
@@ -2545,12 +2559,7 @@ function updatePlayLiveStatusState(
2545
2559
  : isPlayRunPackage(payload)
2546
2560
  ? payload.run.id
2547
2561
  : state.runId;
2548
- const status =
2549
- normalizeLiveStatus(payload.status) ??
2550
- (isPlayRunPackage(payload)
2551
- ? normalizeLiveStatus(payload.run.status)
2552
- : null) ??
2553
- state.status;
2562
+ const status = playLiveRunStatus(event) ?? state.status;
2554
2563
  const progressPayload = isRecord(payload.progress) ? payload.progress : {};
2555
2564
  // Snapshots no longer REPLACE accumulated logs (ADR-0009): the snapshot
2556
2565
  // only retains a bounded tail, so replacing would clobber the seq-keyed
@@ -4221,13 +4230,8 @@ export class DeeplineClient {
4221
4230
  runId: string,
4222
4231
  options?: RunsWatchOptions,
4223
4232
  ): Promise<PlayStatus> {
4224
- const state: PlayLiveStatusState = {
4225
- runId,
4226
- status: 'running',
4227
- logs: [],
4228
- lastLogSeq: 0,
4229
- latest: null,
4230
- };
4233
+ // Forward events without retaining their log history. Only lifecycle
4234
+ // evidence is needed here; the final package comes from the durable read.
4231
4235
  let terminalObserved = false;
4232
4236
  for await (const event of this.watchRunEvents(runId, {
4233
4237
  signal: options?.signal,
@@ -4235,19 +4239,20 @@ export class DeeplineClient {
4235
4239
  onRetry: options?.onRetry ?? options?.onReconnect,
4236
4240
  })) {
4237
4241
  options?.onEvent?.(event);
4238
- const status = updatePlayLiveStatusState(state, event);
4239
- if (status && TERMINAL_PLAY_STATUSES.has(status.status)) {
4242
+ if (isTerminalPlayRunLifecycleStatus(playLiveRunStatus(event))) {
4240
4243
  terminalObserved = true;
4241
4244
  }
4242
4245
  }
4243
4246
  if (options?.signal?.aborted) {
4244
4247
  throw new DeeplineError('Run watch aborted.', undefined, 'ABORTED');
4245
4248
  }
4246
- if (terminalObserved) {
4247
- return await this.getRunStatus(runId);
4248
- }
4249
4249
  const refreshed = await this.getRunStatus(runId);
4250
- if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) return refreshed;
4250
+ if (
4251
+ terminalObserved ||
4252
+ isTerminalPlayRunLifecycleStatus(refreshed.status)
4253
+ ) {
4254
+ return refreshed;
4255
+ }
4251
4256
  throw new DeeplineError(
4252
4257
  `Run watch for ${runId} ended before a terminal status.`,
4253
4258
  undefined,
@@ -4474,23 +4479,7 @@ export class DeeplineClient {
4474
4479
  `/api/v2/runs/${encodeURIComponent(runId)}${query}`,
4475
4480
  { headers: { 'x-deepline-run-status': '1' } },
4476
4481
  );
4477
- const responseRunStatus = isPlayRunPackage(response)
4478
- ? response.runStatus
4479
- : isPlayRunPackage(response.package)
4480
- ? response.package.runStatus
4481
- : response.runStatus;
4482
- const responseOverview = isPlayRunPackage(response)
4483
- ? response.overview
4484
- : isPlayRunPackage(response.package)
4485
- ? response.package.overview
4486
- : response.overview;
4487
4482
  const status = normalizePlayStatus(response);
4488
- if (isRecord(responseRunStatus)) {
4489
- status.runStatus = responseRunStatus as unknown as RunGetStatus;
4490
- }
4491
- if (isRecord(responseOverview)) {
4492
- status.overview = responseOverview as unknown as RunLifecycleStatus;
4493
- }
4494
4483
  if (options?.failedLogs !== true || status.status !== 'failed') {
4495
4484
  return status;
4496
4485
  }
@@ -5796,7 +5785,7 @@ export class DeeplineClient {
5796
5785
  options?.onProgress?.(status);
5797
5786
  }
5798
5787
 
5799
- if (TERMINAL_PLAY_STATUSES.has(status.status)) {
5788
+ if (isTerminalPlayRunLifecycleStatus(status.status)) {
5800
5789
  terminalObserved = status;
5801
5790
  }
5802
5791
  }
@@ -38,6 +38,7 @@ import { readSdkSkillsLocalVersion } from './skills-version.js';
38
38
  import { deploymentProtectionHeaders } from './deployment-protection.js';
39
39
  import {
40
40
  ABSURD_RELEASE_OVERRIDE_HEADER,
41
+ RETAINED_PIPELINE_RECEIVERS_HEADER,
41
42
  COORDINATOR_INTERNAL_TOKEN_HEADER,
42
43
  COORDINATOR_URL_OVERRIDE_HEADER,
43
44
  PREFERRED_RECEIPT_GATEWAY_MACHINE_ID_HEADER,
@@ -345,6 +346,15 @@ export class HttpClient {
345
346
  if (runtimeTestFault?.trim()) {
346
347
  headers[PLAY_RUNTIME_TEST_FAULT_HEADER] = runtimeTestFault.trim();
347
348
  }
349
+ const retainedReceivers =
350
+ typeof process !== 'undefined'
351
+ ? process.env?.DEEPLINE_RUNTIME_RETAINED_PIPELINE_RECEIVERS?.trim()
352
+ : undefined;
353
+ if (retainedReceivers && coordinatorInternalToken?.trim()) {
354
+ headers[RETAINED_PIPELINE_RECEIVERS_HEADER] = retainedReceivers;
355
+ headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
356
+ coordinatorInternalToken.trim();
357
+ }
348
358
  if (absurdReleaseOverride?.trim() && coordinatorInternalToken?.trim()) {
349
359
  headers[ABSURD_RELEASE_OVERRIDE_HEADER] = absurdReleaseOverride.trim();
350
360
  }
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.245',
203
+ version: '0.3.247',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -0,0 +1,19 @@
1
+ import { DeeplineError } from '../errors.js';
2
+
3
+ export function isTransientPlayRunReadError(error: unknown): boolean {
4
+ if (
5
+ error instanceof DeeplineError &&
6
+ error.code === 'PLAY_STREAM_NETWORK_ABORTED'
7
+ ) {
8
+ return true;
9
+ }
10
+ if (error instanceof DeeplineError && typeof error.statusCode === 'number') {
11
+ // 5xx responses and network-level failures may recover. Client errors,
12
+ // especially 404, stay visible instead of disappearing into a retry loop.
13
+ return error.statusCode >= 500 && error.statusCode < 600;
14
+ }
15
+ const text = error instanceof Error ? error.message : String(error);
16
+ return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
17
+ text,
18
+ );
19
+ }
@@ -7,7 +7,7 @@ import {
7
7
  type PlayRunStreamDiffState,
8
8
  type PlayRunStreamEvent,
9
9
  } from '../../../shared_libs/play-runtime/run-snapshot-stream.js';
10
- import { isTransientPlayRunReadError } from '../stream-reconnect.js';
10
+ import { isTransientPlayRunReadError } from './read-errors.js';
11
11
 
12
12
  /** The watch endpoint is a small authenticated REST read, polled every two seconds. */
13
13
  export const RUN_WATCH_POLL_INTERVAL_MS = 2_000;
@@ -849,6 +849,24 @@
849
849
  "failureAlert": "convex_action_wrapper",
850
850
  "domainReportRequired": true
851
851
  },
852
+ {
853
+ "id": "convex.cleanup-expired-org-day-usage-shards",
854
+ "name": "Cleanup expired org-day usage shards",
855
+ "scheduler": "convex_cron",
856
+ "cadence": "47 5 * * *",
857
+ "timezone": "UTC",
858
+ "source": "packages/convex/crons.ts",
859
+ "locator": "cleanup expired org-day usage shards",
860
+ "owner": "billing",
861
+ "criticality": "maintenance",
862
+ "purpose": "Delete expired org-day usage shards independently of display rollup folding.",
863
+ "successDefinition": "Each expired shard page is deleted and full pages schedule a continuation.",
864
+ "failureDefinition": "Expired shards cannot be deleted or a required continuation is not scheduled.",
865
+ "startGraceMs": 120000,
866
+ "runTimeoutMs": 1800000,
867
+ "failureAlert": "convex_action_wrapper",
868
+ "domainReportRequired": true
869
+ },
852
870
  {
853
871
  "id": "convex.record-neon-managed-project-usage",
854
872
  "name": "Record Neon managed project usage",
@@ -9,6 +9,7 @@
9
9
  */
10
10
  export const MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS = [
11
11
  'convex.fold-usage-rollups',
12
+ 'convex.cleanup-expired-org-day-usage-shards',
12
13
  'convex.record-neon-managed-project-usage',
13
14
  'convex.cleanup-expired-mcp-oauth-codes',
14
15
  'convex.cleanup-expired-api-key-claims',
@@ -19063,11 +19063,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19063
19063
  ambiguousTransportReplayAllowed ||
19064
19064
  fixtureReplaySafe)
19065
19065
  ) {
19066
- // A transport retry has no gateway-qualified provider outcome
19067
- // to inherit. In particular, never carry a prior rate-limit
19068
- // claim across an ambiguous delivery retry.
19069
- providerRateLimitRetryFromInvocationAttempt = null;
19070
- invocationAttempt += 1;
19066
+ // Redeliver a fenced request to the same saved outcome. Its
19067
+ // attempt and any prior provider-429 proof belong to the logical
19068
+ // invocation; only a definite retryable HTTP outcome advances it.
19069
+ // Ordinary transport retries retain their physical attempt count.
19070
+ if (!usesDurableInvocationFence) {
19071
+ providerRateLimitRetryFromInvocationAttempt = null;
19072
+ invocationAttempt += 1;
19073
+ }
19071
19074
  await this.governor.chargeBudget('retry');
19072
19075
  const retryAfterMs =
19073
19076
  TOOL_RETRY_AFTER_FALLBACK_MS * transportAttempt;
@@ -22,6 +22,16 @@ export const RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER =
22
22
  */
23
23
  export const RUNTIME_ENVIRONMENT_TOKEN_HEADER =
24
24
  'x-deepline-runtime-environment-token';
25
+ /** Authenticated target-owner assertion, issued only after deployment reproof. */
26
+ export const RETAINED_PIPELINE_RECEIVERS_HEADER =
27
+ 'x-deepline-retained-pipeline-receivers';
28
+ export type RetainedPipelineReceivers = {
29
+ namespace: string;
30
+ schedulerSchema?: string;
31
+ backend: string;
32
+ callbackOrigin: string;
33
+ runtimeApiOrigin: string;
34
+ };
25
35
  /**
26
36
  * Internal top-level `profile=absurd` launches, including deploy canaries, use
27
37
  * this header to pin an exact candidate release before activation. Public API
@@ -41,7 +41,7 @@ const MAX_TIMEOUT_MS = 2_147_483_647;
41
41
  * therefore a network/projection concurrency budget, deliberately independent
42
42
  * of the projector's much smaller Postgres pool.
43
43
  *
44
- * A single successful probe starts a bounded wave; an idle projector still
44
+ * A single successful probe opens bounded delivery slots; an idle projector still
45
45
  * performs only one indexed head read per safety wake. `SKIP LOCKED` leases
46
46
  * distinct runs, retaining strict causal ordering within every run.
47
47
  */
@@ -130,6 +130,7 @@ export function startRunLedgerOutboxRecovery(
130
130
  let unsubscribeNotify: (() => void) | undefined;
131
131
  let unsubscribeState: (() => void) | undefined;
132
132
  let requestDrain: (reason: string) => void = () => undefined;
133
+ let wakeDrainer: (() => void) | null = null;
133
134
 
134
135
  const clearWakeTimer = () => {
135
136
  if (!timer) return;
@@ -162,7 +163,10 @@ export function startRunLedgerOutboxRecovery(
162
163
  requested = true;
163
164
  if (cooldownUntilMs !== null && now() < cooldownUntilMs) return;
164
165
  clearWakeTimer();
165
- if (drainPromise) return;
166
+ if (drainPromise) {
167
+ wakeDrainer?.();
168
+ return;
169
+ }
166
170
  drainPromise = drain(reason)
167
171
  .catch((error) => {
168
172
  // drain() handles expected database/projection faults itself. This is a
@@ -179,7 +183,6 @@ export function startRunLedgerOutboxRecovery(
179
183
  };
180
184
 
181
185
  const recordProjectionRetry = (retrying: number) => {
182
- failureCount += 1;
183
186
  logger.warn('[run-ledger-outbox.recovery_retry]', {
184
187
  stage: 'projection',
185
188
  retrying,
@@ -205,6 +208,7 @@ export function startRunLedgerOutboxRecovery(
205
208
  delayMs,
206
209
  error: error instanceof Error ? error.message : String(error),
207
210
  });
211
+ if (closed) return;
208
212
  clearWakeTimer();
209
213
  cooldownUntilMs = now() + delayMs;
210
214
  timer = setTimer(() => {
@@ -216,6 +220,7 @@ export function startRunLedgerOutboxRecovery(
216
220
  };
217
221
 
218
222
  const scheduleNextDue = async (): Promise<void> => {
223
+ if (closed) return;
219
224
  let dueAt: Date | null;
220
225
  try {
221
226
  dueAt = await deps.readNextDueAt();
@@ -246,7 +251,47 @@ export function startRunLedgerOutboxRecovery(
246
251
  timer.unref?.();
247
252
  };
248
253
 
254
+ type DeliveryOutcome = {
255
+ delivery: Promise<DeliveryOutcome>;
256
+ } & (
257
+ | { status: 'fulfilled'; value: PostgresSchedulerOutboxProjectionResult }
258
+ | { status: 'rejected' | 'skipped' }
259
+ );
260
+
249
261
  const drain = async (reason: string): Promise<void> => {
262
+ const volume = { attempted: 0, projected: 0, retrying: 0, blocked: 0 };
263
+ const reportVolume = () => {
264
+ if (volume.attempted === 0) return;
265
+ logger.info('[run-ledger-outbox.recovery_wave]', {
266
+ reason,
267
+ parallelism,
268
+ ...volume,
269
+ });
270
+ volume.attempted = 0;
271
+ volume.projected = 0;
272
+ volume.retrying = 0;
273
+ volume.blocked = 0;
274
+ };
275
+ const recordResult = (
276
+ result: PostgresSchedulerOutboxProjectionResult,
277
+ resetFailures = true,
278
+ ) => {
279
+ volume.attempted += result.attempted;
280
+ volume.projected += result.projected;
281
+ volume.retrying += result.retrying;
282
+ volume.blocked += result.failed;
283
+ if (result.retrying > 0) {
284
+ // Each retrying run owns its durable retry clock. Independent runs
285
+ // remain eligible; this is not a controller-wide delivery cooldown.
286
+ recordProjectionRetry(result.retrying);
287
+ } else if (resetFailures) {
288
+ failureCount = 0;
289
+ }
290
+ // Streaming delivery has no wave barrier at which to report progress.
291
+ // Keep logs bounded per committed volume, and flush at idle/shutdown.
292
+ if (volume.attempted >= parallelism) reportVolume();
293
+ };
294
+
250
295
  while (requested && !closed) {
251
296
  requested = false;
252
297
  // Probe first so an idle projector issues one small indexed read per
@@ -260,86 +305,81 @@ export function startRunLedgerOutboxRecovery(
260
305
  scheduleFailureRetry('dispatch', error);
261
306
  return;
262
307
  }
263
- const settled = [
264
- {
265
- status: 'fulfilled' as const,
266
- value: first,
267
- },
268
- ...(first.attempted === 0
269
- ? []
270
- : await Promise.allSettled(
271
- Array.from({ length: parallelism - 1 }, () =>
272
- deps.dispatchOnce(),
273
- ),
274
- )),
275
- ];
276
- const rejected = settled.find(
277
- (result): result is PromiseRejectedResult =>
278
- result.status === 'rejected',
279
- );
280
- if (rejected) {
281
- scheduleFailureRetry('dispatch', rejected.reason);
282
- return;
283
- }
284
-
285
- const results = settled.map(
286
- (result) =>
287
- (
288
- result as PromiseFulfilledResult<PostgresSchedulerOutboxProjectionResult>
289
- ).value,
290
- );
291
- const retrying = results.reduce(
292
- (total, result) => total + result.retrying,
293
- 0,
294
- );
295
- const attempted = results.reduce(
296
- (total, result) => total + result.attempted,
297
- 0,
298
- );
299
- const projected = results.reduce(
300
- (total, result) => total + result.projected,
301
- 0,
302
- );
303
- const blocked = results.reduce(
304
- (total, result) => total + result.failed,
305
- 0,
306
- );
307
-
308
- if (retrying > 0) {
309
- // The failed row owns its own durable retry clock. Do NOT stop this
310
- // drain: the claim query preserves causal order within a run, while
311
- // independent runs are safe to project now. Returning here turns one
312
- // transient projection failure into global head-of-line blocking when
313
- // this is the only healthy projector replica.
314
- recordProjectionRetry(retrying);
315
- } else {
316
- failureCount = 0;
308
+ recordResult(first);
309
+ if (first.attempted === 0) {
310
+ await scheduleNextDue();
311
+ continue;
317
312
  }
318
313
 
319
- if (attempted > 0) {
320
- // This is the durable queue's high-signal throughput event. It makes
321
- // a delayed public Run observable as a projector/outbox problem,
322
- // without emitting one noisy log line per normal database checkout.
323
- logger.info('[run-ledger-outbox.recovery_wave]', {
324
- reason,
325
- parallelism,
326
- attempted,
327
- projected,
328
- retrying,
329
- blocked,
314
+ const active = new Set<Promise<DeliveryOutcome>>();
315
+ let dispatchFailed = false;
316
+ const startDelivery = () => {
317
+ const delivery: Promise<DeliveryOutcome> = Promise.resolve().then(
318
+ async (): Promise<DeliveryOutcome> => {
319
+ if (closed || dispatchFailed)
320
+ return { delivery, status: 'skipped' };
321
+ try {
322
+ return {
323
+ delivery,
324
+ status: 'fulfilled',
325
+ value: await deps.dispatchOnce(),
326
+ };
327
+ } catch (error) {
328
+ // Stop refills as soon as this rejection is observed, including
329
+ // when another already-completed slot wins Promise.race first.
330
+ if (!dispatchFailed) {
331
+ dispatchFailed = true;
332
+ scheduleFailureRetry('dispatch', error);
333
+ }
334
+ return { delivery, status: 'rejected' };
335
+ }
336
+ },
337
+ );
338
+ active.add(delivery);
339
+ };
340
+ const fillSlots = () => {
341
+ if (closed || dispatchFailed) return;
342
+ while (active.size < parallelism) {
343
+ startDelivery();
344
+ }
345
+ };
346
+ fillSlots();
347
+ while (active.size > 0) {
348
+ const wake = new Promise<null>((resolve) => {
349
+ wakeDrainer = () => resolve(null);
330
350
  });
351
+ const outcome = await Promise.race([...active, wake]);
352
+ wakeDrainer = null;
353
+ if (outcome !== null) {
354
+ active.delete(outcome.delivery);
355
+ if (outcome.status === 'fulfilled') {
356
+ recordResult(outcome.value, !dispatchFailed);
357
+ if (outcome.value.attempted > 0 && !closed && !dispatchFailed) {
358
+ // Refill this completed slot immediately. A slower independent
359
+ // run never owns another run's delivery capacity.
360
+ startDelivery();
361
+ }
362
+ }
363
+ }
364
+ if (
365
+ requested &&
366
+ !closed &&
367
+ !dispatchFailed &&
368
+ active.size < parallelism
369
+ ) {
370
+ // A new notification can use slots that previously found no work,
371
+ // even while another slot is still awaiting its gateway response.
372
+ // Keep the hint while all slots are occupied: an already-started
373
+ // empty claim does not establish idle after this notification.
374
+ requested = false;
375
+ fillSlots();
376
+ }
331
377
  }
332
-
333
- // Continue until no event was claimable. A transient failure schedules
334
- // its own durable retry, so the next pass naturally skips that run while
335
- // moving unrelated runs forward. A permanent failure is settled once and
336
- // likewise cannot block another run's causal prefix.
337
- if (attempted > 0) {
338
- requested = true;
339
- continue;
340
- }
378
+ reportVolume();
379
+ if (dispatchFailed || closed) return;
341
380
  await scheduleNextDue();
342
381
  }
382
+ reportVolume();
343
383
  };
344
384
 
345
385
  // Subscribe first, then scan. Notifications are lossy by design, so every
@@ -353,6 +393,7 @@ export function startRunLedgerOutboxRecovery(
353
393
  close: async () => {
354
394
  if (closed) return;
355
395
  closed = true;
396
+ wakeDrainer?.();
356
397
  clearWakeTimer();
357
398
  cooldownUntilMs = null;
358
399
  unsubscribeNotify?.();
@@ -20,12 +20,13 @@ export function transientServiceUnavailableDetail(
20
20
  ) {
21
21
  return detail;
22
22
  }
23
- // Convex uses explicit ServiceUnavailable and ExpiredInQueue responses, as
23
+ // Convex uses explicit ServiceUnavailable, ExpiredInQueue and
24
+ // TooManyConcurrentRequests admission responses, as
24
25
  // well as this generic InternalServerError envelope for short control-plane
25
26
  // outages. The latter text is intentionally exact: application exceptions
26
27
  // must not become retryable merely because they mention an internal-server
27
28
  // error.
28
- return /ServiceUnavailable|Service temporarily unavailable|temporarily unavailable \(Code:\s*\d+\)|"code"\s*:\s*"ExpiredInQueue"|"code"\s*:\s*"InternalServerError"[\s\S]*(?:Your request couldn't be completed\. Try again later\.|Your request timed out performing too many system operations\.)/i.test(
29
+ return /ServiceUnavailable|Service temporarily unavailable|temporarily unavailable \(Code:\s*\d+\)|"code"\s*:\s*"(?:ExpiredInQueue|TooManyConcurrentRequests)"|"code"\s*:\s*"InternalServerError"[\s\S]*(?:Your request couldn't be completed\. Try again later\.|Your request timed out performing too many system operations\.)/i.test(
29
30
  detail,
30
31
  )
31
32
  ? detail
@@ -4,9 +4,11 @@ import { staticPipelineStorageCodec } from './static-pipeline-storage-codec';
4
4
 
5
5
  export {
6
6
  retainedPlayPipelineFields,
7
+ WRITE_RETAINED_PIPELINE_GRAPHS,
7
8
  type RetainedPlayContract,
8
9
  type RetainedPipelineCarrier,
9
10
  encodeRetainedPlayPipelines,
11
+ createRetainedPlayPipelinesForTransport,
10
12
  hydrateRetainedPlayPipelines,
11
13
  prepareRetainedPlayPipelinesForTransport,
12
14
  type RetainedPipelineGraph,
@@ -6,7 +6,7 @@
6
6
  * This is an additive named representation. Readers must be deployed before
7
7
  * WRITE_RETAINED_PIPELINE_GRAPHS is enabled in a separate release.
8
8
  */
9
- export const WRITE_RETAINED_PIPELINE_GRAPHS = false;
9
+ export const WRITE_RETAINED_PIPELINE_GRAPHS = true;
10
10
 
11
11
  type Scalar = null | boolean | number | string;
12
12
  type GraphValue = Scalar | { ref: number };
@@ -255,12 +255,18 @@ function hydrateContractRoots(
255
255
  return result;
256
256
  }
257
257
 
258
- /** Production writers remain inert until the separate reader rollout completes. */
258
+ /** Create a graph only when the owner has selected reader-capable receivers. */
259
+ export function createRetainedPlayPipelinesForTransport<T>(input: T): T {
260
+ return WRITE_RETAINED_PIPELINE_GRAPHS
261
+ ? encodeRetainedPlayPipelines(input)
262
+ : prepareRetainedPlayPipelinesForTransport(input);
263
+ }
264
+
265
+ /** Forward an immutable launch in its original representation. */
259
266
  export function prepareRetainedPlayPipelinesForTransport<T>(input: T): T {
260
- // Reader-only builds must preserve new representations when forwarding
261
- // them. The rollout switch controls creation from legacy envelopes only.
262
- return WRITE_RETAINED_PIPELINE_GRAPHS ||
263
- (record(input) && input.retainedPipelineGraph !== undefined)
267
+ // A historical launch may still call an older pinned app after adoption.
268
+ // New workers and projectors must not upgrade that receiver's wire format.
269
+ return record(input) && input.retainedPipelineGraph !== undefined
264
270
  ? encodeRetainedPlayPipelines(input)
265
271
  : input;
266
272
  }
@@ -10,7 +10,6 @@ import {
10
10
  retainedPlayPipelineFields,
11
11
  encodeRetainedPlayPipelines,
12
12
  hydrateRetainedPlayPipelines,
13
- WRITE_RETAINED_PIPELINE_GRAPHS,
14
13
  type RetainedPipelineGraph,
15
14
  type RetainedPipelineCarrier,
16
15
  } from './retained-pipeline-graph';
@@ -29,7 +28,7 @@ function readExecutionPipeline(
29
28
  return readStoredStaticPipeline(record);
30
29
  }
31
30
 
32
- /** Runs use the shared graph after all storage readers have been deployed. */
31
+ /** Preserve the representation selected by the immutable launch owner. */
33
32
  function executionStorageFields(
34
33
  pipeline: PlayStaticPipeline | null | undefined,
35
34
  carrier?: RetainedPipelineCarrier,
@@ -38,11 +37,7 @@ function executionStorageFields(
38
37
  staticPipelineV2?: ReturnType<typeof createStoredStaticPipelineV2>;
39
38
  retainedPipelineGraph?: RetainedPipelineGraph;
40
39
  } {
41
- if (
42
- (!WRITE_RETAINED_PIPELINE_GRAPHS &&
43
- carrier?.retainedPipelineGraph === undefined) ||
44
- pipeline === undefined
45
- ) {
40
+ if (carrier?.retainedPipelineGraph === undefined || pipeline === undefined) {
46
41
  return createStaticPipelineStorageFields(pipeline);
47
42
  }
48
43
  const compact = encodeRetainedPlayPipelines(