deepline 0.3.349 → 0.3.350

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 (96) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +30 -6
  3. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +3 -1
  4. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +114 -25
  5. package/dist/cli/{auth-MPHXSP64.mjs → auth-7X3G2MCE.mjs} +4 -4
  6. package/dist/cli/auth-CYXUWRLU.js +31 -0
  7. package/dist/cli/{billing-2KUUGVDZ.js → billing-2J4WREAL.js} +73 -73
  8. package/dist/cli/{billing-K5WCTRBE.mjs → billing-DOVYL5TT.mjs} +4 -4
  9. package/dist/cli/{chunk-QCSLIEMC.js → chunk-2COBRUUH.js} +6 -6
  10. package/dist/cli/{chunk-GXN7DGNL.mjs → chunk-2KLYUZI7.mjs} +2 -2
  11. package/dist/cli/{chunk-SPB6CF4S.mjs → chunk-2NI75UNO.mjs} +2 -2
  12. package/dist/cli/{chunk-WOFHIUOT.mjs → chunk-2Z6HV7BK.mjs} +2 -2
  13. package/dist/cli/{chunk-S347PHNB.js → chunk-36MFVS4Q.js} +9 -9
  14. package/dist/cli/{chunk-ZQ6UF55Y.mjs → chunk-4ODBHH6X.mjs} +1 -1
  15. package/dist/cli/{chunk-UE7DS2YV.mjs → chunk-5ZFDADDM.mjs} +3 -3
  16. package/dist/cli/{chunk-QAM2K3CG.js → chunk-AWJ2A2CP.js} +48 -48
  17. package/dist/cli/{chunk-O2FM2MS5.mjs → chunk-CYCJBDW6.mjs} +6 -6
  18. package/dist/cli/{chunk-IHYVEM5Q.js → chunk-FUQQ3U62.js} +8 -8
  19. package/dist/cli/{chunk-BEGPN6NG.mjs → chunk-FYZFB2CS.mjs} +1 -1
  20. package/dist/cli/{chunk-KW25DKRC.js → chunk-GMOMXQR6.js} +1 -1
  21. package/dist/cli/{chunk-JKJSTQ2I.mjs → chunk-H4MOKDCV.mjs} +1 -1
  22. package/dist/cli/{chunk-MVQ3JAJA.js → chunk-IHMNFNL3.js} +225 -225
  23. package/dist/cli/{chunk-DOHULRK2.mjs → chunk-LDILBDLL.mjs} +4 -4
  24. package/dist/cli/{chunk-I4VZKXMX.js → chunk-LE7DZZVE.js} +26 -26
  25. package/dist/cli/{chunk-M42H2BPA.js → chunk-NWN23EZM.js} +8 -8
  26. package/dist/cli/{chunk-6OVLJLYH.mjs → chunk-NZMVPYBQ.mjs} +1 -1
  27. package/dist/cli/{chunk-TVS3TAFR.js → chunk-QMMMMGAB.js} +15 -15
  28. package/dist/cli/{chunk-HVD3DK7S.mjs → chunk-TFGKO7XG.mjs} +5 -5
  29. package/dist/cli/{chunk-GHYMVZID.js → chunk-UKOXXA75.js} +60 -60
  30. package/dist/cli/{chunk-VDOH4A34.mjs → chunk-VC7D7OXL.mjs} +1 -1
  31. package/dist/cli/{chunk-BPJTGASI.mjs → chunk-WWJOE6JQ.mjs} +2 -2
  32. package/dist/cli/{chunk-6YZDQZE6.js → chunk-YCBWMF2O.js} +2 -2
  33. package/dist/cli/{chunk-CRSMVSER.js → chunk-Z5RG3AOP.js} +45 -45
  34. package/dist/cli/{chunk-JBGR6U57.js → chunk-ZIUELPGR.js} +4 -4
  35. package/dist/cli/client-R4VF7KSK.js +20 -0
  36. package/dist/cli/{client-JWKYNLCR.mjs → client-YM7EV7JZ.mjs} +3 -3
  37. package/dist/cli/{csv-IW75DRFX.js → csv-3CE5IY4L.js} +12 -12
  38. package/dist/cli/{csv-Y2O237NO.mjs → csv-IERAXUNG.mjs} +3 -3
  39. package/dist/cli/{db-JW6UUJXC.mjs → db-M2ONKJRD.mjs} +4 -4
  40. package/dist/cli/{db-6Q4VK5T2.js → db-VYSLD53P.js} +29 -29
  41. package/dist/cli/{describe-DI65QISF.js → describe-2XFXS2SX.js} +15 -15
  42. package/dist/cli/{describe-JLN4RTGM.mjs → describe-W6FCAXZR.mjs} +8 -8
  43. package/dist/cli/{executions-UK6LA2O5.mjs → executions-OUP6IVMO.mjs} +4 -4
  44. package/dist/cli/{executions-GB2E3BU6.js → executions-P7EK3RC7.js} +6 -6
  45. package/dist/cli/{feedback-N2KAFS3R.mjs → feedback-Q5QTIZSG.mjs} +3 -3
  46. package/dist/cli/{feedback-MJE3A5ON.js → feedback-UHG2PISO.js} +7 -7
  47. package/dist/cli/{git-NWRJFNII.mjs → git-DMG3Z36A.mjs} +4 -4
  48. package/dist/cli/git-IQRVNMQL.js +23 -0
  49. package/dist/cli/index.js +79 -79
  50. package/dist/cli/index.mjs +31 -31
  51. package/dist/cli/{legacy-noops-RR4W2TTE.mjs → legacy-noops-ARAPGQUN.mjs} +4 -4
  52. package/dist/cli/{legacy-noops-EIO65N6W.js → legacy-noops-DZJRLQUN.js} +7 -7
  53. package/dist/cli/{monitors-HNUCJYET.mjs → monitors-7GSSQILT.mjs} +4 -4
  54. package/dist/cli/{monitors-U7NVJDQL.js → monitors-B7HLL45A.js} +85 -85
  55. package/dist/cli/{org-O7MFRQNG.mjs → org-UR72VY3A.mjs} +4 -4
  56. package/dist/cli/{org-UBHNTBSB.js → org-VIY2LH6L.js} +60 -60
  57. package/dist/cli/{play-4EIIEFQK.mjs → play-ATTKGZLK.mjs} +7 -7
  58. package/dist/cli/play-FT7EUE2Y.js +153 -0
  59. package/dist/cli/{providers-CJ56HZIT.js → providers-RDOEU4FO.js} +7 -7
  60. package/dist/cli/{providers-REJIT7CX.mjs → providers-ZBZED4TK.mjs} +4 -4
  61. package/dist/cli/{quickstart-DQN2LYBC.mjs → quickstart-JT5FW7EB.mjs} +3 -3
  62. package/dist/cli/{quickstart-BZUQWQK5.js → quickstart-Y2GPRMC7.js} +5 -5
  63. package/dist/cli/{search-35UOMFL2.mjs → search-73OTM3ZZ.mjs} +5 -5
  64. package/dist/cli/search-LHD6BP42.js +26 -0
  65. package/dist/cli/{secrets-YJB66UXU.mjs → secrets-AHDWUIVL.mjs} +4 -4
  66. package/dist/cli/{secrets-G4RYNUUD.js → secrets-EEXG43XS.js} +13 -13
  67. package/dist/cli/{sessions-BUWSRUSJ.mjs → sessions-LDZMTST4.mjs} +4 -4
  68. package/dist/cli/{sessions-DGS7S6XX.js → sessions-SHSSRPN5.js} +5 -5
  69. package/dist/cli/{settings-OTWDMCM5.js → settings-FMM4KYDS.js} +74 -74
  70. package/dist/cli/{settings-OCMJXIFD.mjs → settings-JBCSG3JV.mjs} +7 -7
  71. package/dist/cli/{setup-UXYFRGIX.mjs → setup-4M6GZEV6.mjs} +6 -6
  72. package/dist/cli/{setup-MYD34DUG.js → setup-VF3WHT2K.js} +41 -41
  73. package/dist/cli/{skills-LOWITZWQ.mjs → skills-5GSHPGQR.mjs} +4 -4
  74. package/dist/cli/skills-RKY6BV2Y.js +28 -0
  75. package/dist/cli/{tools-YJHZJLES.mjs → tools-7G6TW3MO.mjs} +6 -6
  76. package/dist/cli/tools-RSVWPCZT.js +40 -0
  77. package/dist/cli/{update-PB5UCW4I.mjs → update-NY2LA7HX.mjs} +6 -6
  78. package/dist/cli/update-ZD7UN6FA.js +28 -0
  79. package/dist/cli/{workflow-IH2FQSYZ.mjs → workflow-JPNJ5D2W.mjs} +5 -5
  80. package/dist/cli/{workflow-DJYTA2YM.js → workflow-TZ4UU3MV.js} +15 -15
  81. package/dist/index.js +6 -6
  82. package/dist/index.mjs +3 -3
  83. package/dist/install-integrity.json +74 -74
  84. package/dist/release.d.mts +1 -1
  85. package/dist/release.d.ts +1 -1
  86. package/dist/release.js +2 -2
  87. package/dist/release.mjs +1 -1
  88. package/package.json +1 -1
  89. package/dist/cli/auth-KGXZELZ3.js +0 -31
  90. package/dist/cli/client-O4DPNWVJ.js +0 -20
  91. package/dist/cli/git-A7J4SSRV.js +0 -23
  92. package/dist/cli/play-IHLNNVPR.js +0 -153
  93. package/dist/cli/search-4EPCIU2T.js +0 -26
  94. package/dist/cli/skills-7UHWU7XL.js +0 -28
  95. package/dist/cli/tools-CBYKQ7IY.js +0 -40
  96. package/dist/cli/update-5M3OKPEU.js +0 -28
@@ -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.349',
203
+ version: '0.3.350',
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: {
@@ -8,6 +8,26 @@ import { stringifyPostgresJson } from '../postgres-json';
8
8
 
9
9
  const MAX_BUDGET_CHARGES_PER_REQUEST = 16;
10
10
 
11
+ /** The caller sent a charge this request can never satisfy. Retrying is futile. */
12
+ export class BudgetStateRequestError extends Error {
13
+ constructor(message: string) {
14
+ super(message);
15
+ this.name = 'BudgetStateRequestError';
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Only a limit or a malformed request is the budget's own answer. Anything
21
+ * else (a dropped connection, an aborted transaction) says nothing about the
22
+ * budget and must stay retryable.
23
+ */
24
+ export function isBudgetStateRejection(error: unknown): boolean {
25
+ return (
26
+ error instanceof BudgetStateLimitError ||
27
+ error instanceof BudgetStateRequestError
28
+ );
29
+ }
30
+
11
31
  function quoteIdent(value: string): string {
12
32
  return `"${value.replace(/"/g, '""')}"`;
13
33
  }
@@ -52,7 +72,7 @@ export async function ensurePostgresBudgetStateSchema(
52
72
 
53
73
  function normalizedCharges(input: PlayRunnerBudgetChargeInput) {
54
74
  if (input.charges.length > MAX_BUDGET_CHARGES_PER_REQUEST) {
55
- throw new Error(
75
+ throw new BudgetStateRequestError(
56
76
  `Governor budget accepts at most ${MAX_BUDGET_CHARGES_PER_REQUEST} charges per request.`,
57
77
  );
58
78
  }
@@ -60,15 +80,17 @@ function normalizedCharges(input: PlayRunnerBudgetChargeInput) {
60
80
  for (const charge of input.charges) {
61
81
  const key = charge.key.trim();
62
82
  if (!key || key.length > 256) {
63
- throw new Error('Governor budget key must be 1-256 characters.');
83
+ throw new BudgetStateRequestError(
84
+ 'Governor budget key must be 1-256 characters.',
85
+ );
64
86
  }
65
87
  if (!Number.isSafeInteger(charge.amount) || charge.amount <= 0) {
66
- throw new Error(
88
+ throw new BudgetStateRequestError(
67
89
  `Governor budget ${key} amount must be a positive integer.`,
68
90
  );
69
91
  }
70
92
  if (!Number.isSafeInteger(charge.limit) || charge.limit <= 0) {
71
- throw new Error(
93
+ throw new BudgetStateRequestError(
72
94
  `Governor budget ${key} limit must be a positive integer.`,
73
95
  );
74
96
  }
@@ -108,7 +130,9 @@ export async function chargePostgresBudgetState(
108
130
  if (charges.length === 0) return { counters: {} };
109
131
  const reservationId = input.reservationId.trim();
110
132
  if (!reservationId || reservationId.length > 256) {
111
- throw new Error('Governor budget reservationId must be 1-256 characters.');
133
+ throw new BudgetStateRequestError(
134
+ 'Governor budget reservationId must be 1-256 characters.',
135
+ );
112
136
  }
113
137
  for (const charge of charges) {
114
138
  if (charge.amount > charge.limit) {
@@ -155,7 +179,7 @@ export async function chargePostgresBudgetState(
155
179
  throw new Error('Governor budget reservation disappeared.');
156
180
  }
157
181
  if (!sameCharges(reservation.charges_json, charges)) {
158
- throw new Error(
182
+ throw new BudgetStateRequestError(
159
183
  'Governor budget reservationId was reused with different charges.',
160
184
  );
161
185
  }
@@ -608,7 +608,9 @@ export function isRuntimeSchedulerServerUnavailableError(
608
608
  code === '57P03' ||
609
609
  (code === '08P01' &&
610
610
  typeof message === 'string' &&
611
- recoveryMessage.test(message.trim())) ||
611
+ (recoveryMessage.test(message.trim()) ||
612
+ // PgBouncer's exact report that its backend connection dropped.
613
+ message.trim() === 'server conn crashed?')) ||
612
614
  // Preserve legacy connection messages without accepting every 08* code.
613
615
  (legacyConnectionCode &&
614
616
  typeof message === 'string' &&
@@ -80,6 +80,9 @@ const RUN_LEDGER_OUTBOX_RECOVERY_INDEX_IN_FLIGHT = 4;
80
80
  // from starving behind continuous new admissions. An empty class never costs
81
81
  // another query: either order can select any eligible run.
82
82
  const ADMISSION_TURNS_PER_OLDEST_READY_TURN = 3;
83
+ // Notifications are hints, not another durable queue. Overflow requests the
84
+ // existing global drain instead of retaining an unbounded set of run ids.
85
+ const MAX_NOTIFICATION_RUN_HINTS = RUN_LEDGER_OUTBOX_RECOVERY_MAX_IN_FLIGHT;
83
86
 
84
87
  type RecoveryTimer = ReturnType<typeof setTimeout>;
85
88
 
@@ -89,9 +92,12 @@ export type RunLedgerOutboxRecoveryDeps = {
89
92
  /** One bounded durable claim/delivery attempt. */
90
93
  dispatchOnce: (
91
94
  headPriority: RunLedgerOutboxHeadPriority,
95
+ runId?: string,
92
96
  ) => Promise<PostgresSchedulerOutboxProjectionResult>;
93
97
  /** Independent index deliveries share this controller and notification source. */
94
- dispatchObservabilityOnce?: () => Promise<PostgresSchedulerOutboxProjectionResult>;
98
+ dispatchObservabilityOnce?: (
99
+ runId?: string,
100
+ ) => Promise<PostgresSchedulerOutboxProjectionResult>;
95
101
  observabilityParallelism?: number;
96
102
  /** Earliest pending retry time or active lease expiry in the durable outbox. */
97
103
  readNextDueAt: () => Promise<Date | null>;
@@ -139,6 +145,18 @@ function boundedFailureDelayMs(failures: number): number {
139
145
  );
140
146
  }
141
147
 
148
+ function notificationRunId(payload: string): string | undefined {
149
+ try {
150
+ const message = JSON.parse(payload) as { runId?: unknown } | null;
151
+ if (typeof message?.runId !== 'string') return undefined;
152
+ const runId = message.runId.trim();
153
+ return runId && runId.length <= 256 ? runId : undefined;
154
+ } catch {
155
+ // Legacy, retry and repair notifications may carry no run identity.
156
+ return undefined;
157
+ }
158
+ }
159
+
142
160
  /**
143
161
  * Start a bounded, work-conserving recovery controller from injected seams.
144
162
  * Subscription happens before the first database scan, so a terminal commit
@@ -189,24 +207,50 @@ export function startRunLedgerOutboxRecovery(
189
207
  let hubLease: PgNotifyHubLease | null = null;
190
208
  let unsubscribeNotify: (() => void) | undefined;
191
209
  let unsubscribeState: (() => void) | undefined;
192
- let requestDrain: (reason: string) => void = () => undefined;
210
+ let requestDrain: (reason: string, runId?: string) => void = () => undefined;
211
+ const lifecycleRunHints = new Set<string>();
212
+ const observabilityRunHints = new Set<string>();
213
+ const hasRequests = () =>
214
+ requested ||
215
+ observabilityRequested ||
216
+ lifecycleRunHints.size > 0 ||
217
+ observabilityRunHints.size > 0;
218
+ const addRunHint = (hints: Set<string>, runId: string) => {
219
+ hints.add(runId);
220
+ if (hints.size <= MAX_NOTIFICATION_RUN_HINTS) return true;
221
+ hints.clear();
222
+ return false;
223
+ };
224
+ const takeRunHint = (hints: Set<string>): string | undefined => {
225
+ const runId = hints.values().next().value;
226
+ if (runId !== undefined) hints.delete(runId);
227
+ return runId;
228
+ };
193
229
  const inFlightStartedAt = new Map<number, number>();
194
230
  let nextDispatchId = 0;
195
231
  let lastDispatchSettledAt: number | null = null;
196
232
  let admissionTurns = 0;
197
233
  const dispatchOnce = async (
198
234
  observability = false,
235
+ runId?: string,
199
236
  ): Promise<PostgresSchedulerOutboxProjectionResult> => {
200
237
  const oldestReady =
201
238
  admissionTurns === ADMISSION_TURNS_PER_OLDEST_READY_TURN;
202
- if (!observability) admissionTurns = oldestReady ? 0 : admissionTurns + 1;
239
+ // Scoped claims cannot change which run wins, so they must not consume
240
+ // the global drain's starvation-prevention turns.
241
+ if (!observability && runId === undefined)
242
+ admissionTurns = oldestReady ? 0 : admissionTurns + 1;
203
243
  const id = nextDispatchId++;
204
244
  inFlightStartedAt.set(id, now());
205
245
  try {
206
- if (observability) return await deps.dispatchObservabilityOnce!();
207
- return await deps.dispatchOnce(
208
- oldestReady ? 'oldest_ready' : 'admission_first',
209
- );
246
+ if (observability)
247
+ return await (runId === undefined
248
+ ? deps.dispatchObservabilityOnce!()
249
+ : deps.dispatchObservabilityOnce!(runId));
250
+ const headPriority = oldestReady ? 'oldest_ready' : 'admission_first';
251
+ return await (runId === undefined
252
+ ? deps.dispatchOnce(headPriority)
253
+ : deps.dispatchOnce(headPriority, runId));
210
254
  } finally {
211
255
  inFlightStartedAt.delete(id);
212
256
  lastDispatchSettledAt = now();
@@ -226,8 +270,8 @@ export function startRunLedgerOutboxRecovery(
226
270
  const lease = deps.acquireNotifyHub();
227
271
  if (!lease) return;
228
272
  hubLease = lease;
229
- unsubscribeNotify = lease.hub.subscribe(() =>
230
- requestDrain('postgres_notify'),
273
+ unsubscribeNotify = lease.hub.subscribe((payload) =>
274
+ requestDrain('postgres_notify', notificationRunId(payload)),
231
275
  );
232
276
  unsubscribeState = lease.hub.subscribeState((state) => {
233
277
  // A reconnect is a durable catch-up boundary. The controller re-reads
@@ -239,10 +283,8 @@ export function startRunLedgerOutboxRecovery(
239
283
  });
240
284
  };
241
285
 
242
- requestDrain = (reason: string) => {
286
+ const startDrain = (reason: string) => {
243
287
  if (closed) return;
244
- requested = true;
245
- observabilityRequested = !!deps.dispatchObservabilityOnce;
246
288
  if (cooldownUntilMs !== null && now() < cooldownUntilMs) return;
247
289
  clearWakeTimer();
248
290
  if (drainPromise) {
@@ -260,10 +302,31 @@ export function startRunLedgerOutboxRecovery(
260
302
  })
261
303
  .finally(() => {
262
304
  drainPromise = null;
263
- if (requested && !closed) requestDrain('coalesced_wake');
305
+ if (hasRequests() && !closed) startDrain('coalesced_wake');
264
306
  });
265
307
  };
266
308
 
309
+ requestDrain = (reason: string, runId?: string) => {
310
+ if (closed) return;
311
+ if (runId === undefined) {
312
+ requested = true;
313
+ observabilityRequested = !!deps.dispatchObservabilityOnce;
314
+ lifecycleRunHints.clear();
315
+ observabilityRunHints.clear();
316
+ } else {
317
+ // A pending global scan already covers these hints. Once that scan is
318
+ // launched its flag is consumed, so a later notification is retained.
319
+ if (!requested && !addRunHint(lifecycleRunHints, runId)) requested = true;
320
+ if (
321
+ deps.dispatchObservabilityOnce &&
322
+ !observabilityRequested &&
323
+ !addRunHint(observabilityRunHints, runId)
324
+ )
325
+ observabilityRequested = true;
326
+ }
327
+ startDrain(reason);
328
+ };
329
+
267
330
  const recordProjectionRetry = (retrying: number) => {
268
331
  logger.warn('[run-ledger-outbox.recovery_retry]', {
269
332
  stage: 'projection',
@@ -311,7 +374,7 @@ export function startRunLedgerOutboxRecovery(
311
374
  scheduleFailureRetry('next_due', error);
312
375
  return;
313
376
  }
314
- if (closed || requested) return;
377
+ if (closed || hasRequests()) return;
315
378
  // When LISTEN is healthy the exact durable due time is sufficient. If the
316
379
  // optional listener is absent, down, or lost an election, an indexed
317
380
  // short safety wake discovers a fresh row that otherwise had no
@@ -337,6 +400,7 @@ export function startRunLedgerOutboxRecovery(
337
400
  type Delivery = {
338
401
  id: symbol;
339
402
  observability: boolean;
403
+ runId?: string;
340
404
  result:
341
405
  | PromiseSettledResult<PostgresSchedulerOutboxProjectionResult>
342
406
  | { status: 'skipped' };
@@ -371,7 +435,7 @@ export function startRunLedgerOutboxRecovery(
371
435
  completed = attempted = projected = retrying = blocked = 0;
372
436
  };
373
437
 
374
- const launch = (observability = false) => {
438
+ const launch = (observability = false, runId?: string) => {
375
439
  const id = Symbol();
376
440
  pending.add(id);
377
441
  if (observability) pendingObservability.add(id);
@@ -380,7 +444,7 @@ export function startRunLedgerOutboxRecovery(
380
444
  dispatchFailed = true;
381
445
  scheduleFailureRetry('dispatch', result.reason);
382
446
  }
383
- deliveries.push({ id, observability, result });
447
+ deliveries.push({ id, observability, runId, result });
384
448
  wakeActiveDrain?.();
385
449
  };
386
450
  // Subscribe once per delivery. Repeated Promise.race calls would retain
@@ -393,7 +457,7 @@ export function startRunLedgerOutboxRecovery(
393
457
  try {
394
458
  settle({
395
459
  status: 'fulfilled',
396
- value: await dispatchOnce(observability),
460
+ value: await dispatchOnce(observability, runId),
397
461
  });
398
462
  } catch (reason) {
399
463
  settle({ status: 'rejected', reason });
@@ -404,13 +468,13 @@ export function startRunLedgerOutboxRecovery(
404
468
  const canProbeLifecycle = () =>
405
469
  !closed &&
406
470
  !dispatchFailed &&
407
- requested &&
471
+ (requested || lifecycleRunHints.size > 0) &&
408
472
  pending.size < parallelism &&
409
473
  pending.size - pendingObservability.size < lifecycleParallelism;
410
474
  const canProbeObservability = () =>
411
475
  !closed &&
412
476
  !dispatchFailed &&
413
- observabilityRequested &&
477
+ (observabilityRequested || observabilityRunHints.size > 0) &&
414
478
  pending.size < parallelism &&
415
479
  pendingObservability.size < observabilityParallelism;
416
480
 
@@ -420,12 +484,16 @@ export function startRunLedgerOutboxRecovery(
420
484
  // still delivering. Keep it pending when full so an empty result
421
485
  // cannot consume a notification that arrived after its query began.
422
486
  if (canProbeObservability()) {
487
+ const runId = observabilityRequested
488
+ ? undefined
489
+ : takeRunHint(observabilityRunHints);
423
490
  observabilityRequested = false;
424
- launch(true);
491
+ launch(true, runId);
425
492
  }
426
493
  if (canProbeLifecycle()) {
494
+ const runId = requested ? undefined : takeRunHint(lifecycleRunHints);
427
495
  requested = false;
428
- launch();
496
+ launch(false, runId);
429
497
  }
430
498
  // Productive completion permits immediate use of every free slot.
431
499
  // Empty results grant no refill, so idle work cannot spin on scans.
@@ -481,14 +549,31 @@ export function startRunLedgerOutboxRecovery(
481
549
  if (completed >= parallelism) reportDeliveries();
482
550
  // Retryable rows own durable retry clocks. The SQL claim keeps their
483
551
  // run ordered while successful slots keep unrelated runs moving.
484
- if (delivery.observability) {
552
+ if (delivery.runId !== undefined) {
553
+ // Continue only the notified run. Refilling with global claims here
554
+ // would turn one productive scoped claim back into whole-outbox scans.
555
+ if (result.attempted > 0) {
556
+ if (delivery.observability) {
557
+ if (
558
+ !observabilityRequested &&
559
+ !addRunHint(observabilityRunHints, delivery.runId)
560
+ )
561
+ observabilityRequested = true;
562
+ } else if (
563
+ !requested &&
564
+ !addRunHint(lifecycleRunHints, delivery.runId)
565
+ )
566
+ requested = true;
567
+ }
568
+ } else if (delivery.observability) {
485
569
  observabilityRefill = result.attempted > 0;
486
570
  } else refill = result.attempted > 0;
487
571
  }
488
572
  };
489
573
 
490
574
  // Subscribe first, then scan. Notifications are lossy by design, so every
491
- // wake re-reads durable state and reconnect transitions also trigger a scan.
575
+ // named wake re-reads that run's durable prefix. Startup, reconnect, retries
576
+ // and identity-less notifications retain the global recovery scan.
492
577
  attachNotifyHub();
493
578
  requestDrain('startup');
494
579
 
@@ -512,6 +597,8 @@ export function startRunLedgerOutboxRecovery(
512
597
  wakeActiveDrain?.();
513
598
  clearWakeTimer();
514
599
  cooldownUntilMs = null;
600
+ lifecycleRunHints.clear();
601
+ observabilityRunHints.clear();
515
602
  unsubscribeNotify?.();
516
603
  unsubscribeState?.();
517
604
  await drainPromise;
@@ -769,6 +856,7 @@ export function startRunLedgerOutboxRecoveryController(input: {
769
856
  const dispatch = (
770
857
  headPriority: RunLedgerOutboxHeadPriority,
771
858
  searchOnly: boolean,
859
+ runId?: string,
772
860
  ) =>
773
861
  dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
774
862
  (operation) =>
@@ -780,6 +868,7 @@ export function startRunLedgerOutboxRecoveryController(input: {
780
868
  tokenAuthority: 'gateway',
781
869
  materializeObservability: true,
782
870
  searchOnly,
871
+ ...(runId === undefined ? {} : { runId }),
783
872
  withObservabilityClient: (operation) =>
784
873
  withRuntimeSchedulerClient(observabilityPoolOptions, operation),
785
874
  limit: 8,
@@ -801,8 +890,8 @@ export function startRunLedgerOutboxRecoveryController(input: {
801
890
  schedulerOptions,
802
891
  }),
803
892
  ),
804
- dispatchOnce: (headPriority) => dispatch(headPriority, false),
805
- dispatchObservabilityOnce: () => dispatch('oldest_ready', true),
893
+ dispatchOnce: (headPriority, runId) => dispatch(headPriority, false, runId),
894
+ dispatchObservabilityOnce: (runId) => dispatch('oldest_ready', true, runId),
806
895
  observabilityParallelism: RUN_LEDGER_OUTBOX_RECOVERY_INDEX_IN_FLIGHT,
807
896
  // `dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope` releases each
808
897
  // short scheduler checkout before it calls the Vercel gateway. The two
@@ -9,11 +9,11 @@ import {
9
9
  registerAuthCommands,
10
10
  reportSetupCompleted,
11
11
  resolvePendingAuthClaimForResume
12
- } from "./chunk-GXN7DGNL.mjs";
13
- import "./chunk-ZQ6UF55Y.mjs";
14
- import "./chunk-JKJSTQ2I.mjs";
12
+ } from "./chunk-2KLYUZI7.mjs";
13
+ import "./chunk-4ODBHH6X.mjs";
14
+ import "./chunk-H4MOKDCV.mjs";
15
15
  import "./chunk-DLIRSHV6.mjs";
16
- import "./chunk-6OVLJLYH.mjs";
16
+ import "./chunk-NZMVPYBQ.mjs";
17
17
  import "./chunk-NXGCO4JB.mjs";
18
18
  import "./chunk-WZYKRD7T.mjs";
19
19
  import "./chunk-KRD6H754.mjs";
@@ -0,0 +1,31 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ var _chunkAWJ2A2CPjs = require('./chunk-AWJ2A2CP.js');
13
+ require('./chunk-2COBRUUH.js');
14
+ require('./chunk-ZIUELPGR.js');
15
+ require('./chunk-XNYIZMNQ.js');
16
+ require('./chunk-GMOMXQR6.js');
17
+ require('./chunk-7P5AA3SN.js');
18
+ require('./chunk-OG64RBAC.js');
19
+ require('./chunk-COGTRVWW.js');
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+ exports.EXIT_WORKSPACE_MISMATCH = _chunkAWJ2A2CPjs.EXIT_WORKSPACE_MISMATCH; exports.clearPendingClaim = _chunkAWJ2A2CPjs.clearPendingClaim; exports.clearSatisfiedPendingClaim = _chunkAWJ2A2CPjs.clearSatisfiedPendingClaim; exports.handleRegister = _chunkAWJ2A2CPjs.handleRegister; exports.handleStatus = _chunkAWJ2A2CPjs.handleStatus; exports.handleWait = _chunkAWJ2A2CPjs.handleWait; exports.readPendingAuthClaim = _chunkAWJ2A2CPjs.readPendingAuthClaim; exports.registerAuthCommands = _chunkAWJ2A2CPjs.registerAuthCommands; exports.reportSetupCompleted = _chunkAWJ2A2CPjs.reportSetupCompleted; exports.resolvePendingAuthClaimForResume = _chunkAWJ2A2CPjs.resolvePendingAuthClaimForResume;