deepline 0.3.357 → 0.3.358

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 (103) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +14 -1
  3. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +52 -19
  4. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +35 -18
  5. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +202 -44
  6. package/dist/cli/{auth-5Y2YV5LY.mjs → auth-QSEB4OI6.mjs} +6 -4
  7. package/dist/cli/auth-VP5WIGFS.js +33 -0
  8. package/dist/cli/{billing-UOFR7KDB.mjs → billing-KNJ6ZHSN.mjs} +5 -5
  9. package/dist/cli/{billing-Y6IKDJ6H.js → billing-UE2CP5O5.js} +74 -74
  10. package/dist/cli/{chunk-O7IM72KW.mjs → chunk-3DP24EZA.mjs} +15 -15
  11. package/dist/cli/{chunk-IWBFG4XI.mjs → chunk-4IUOBUNS.mjs} +1 -1
  12. package/dist/cli/{chunk-C2EHMMME.js → chunk-5PJ6XYRV.js} +64 -60
  13. package/dist/cli/{chunk-CPQIVH6G.js → chunk-5RWJSROL.js} +8 -8
  14. package/dist/cli/{chunk-5UNTNKNX.js → chunk-BVPN57AD.js} +6 -6
  15. package/dist/cli/{chunk-PJYCAGKK.mjs → chunk-EL62MLRD.mjs} +1 -1
  16. package/dist/cli/{chunk-NZ6BT4NL.mjs → chunk-EVPWVNJV.mjs} +7 -7
  17. package/dist/cli/{chunk-SCY4UCEZ.js → chunk-GYDSDOXB.js} +75 -50
  18. package/dist/cli/{chunk-VXH4225Y.mjs → chunk-HGLESMAC.mjs} +27 -2
  19. package/dist/cli/{chunk-EWTVIN3O.js → chunk-HKRJUIRL.js} +47 -47
  20. package/dist/cli/{chunk-R5P3KFF5.js → chunk-HNAFCPM4.js} +8 -8
  21. package/dist/cli/{chunk-WQSV34KV.js → chunk-KIRDHNX5.js} +11 -11
  22. package/dist/cli/{chunk-NEMHJYA4.js → chunk-LOSZZCER.js} +2 -2
  23. package/dist/cli/{chunk-2AROJ4EZ.mjs → chunk-NMHQTEIQ.mjs} +2 -2
  24. package/dist/cli/{chunk-J4A4MRBK.js → chunk-OO3MYL2Y.js} +26 -26
  25. package/dist/cli/{chunk-SXXUOZRS.js → chunk-OVUDXZCK.js} +1 -1
  26. package/dist/cli/{chunk-2NZUC5OS.mjs → chunk-Q77PWYDF.mjs} +4 -4
  27. package/dist/cli/chunk-QFBHEQCQ.mjs +1421 -0
  28. package/dist/cli/{chunk-SMLKQAEB.mjs → chunk-QH6PV7IJ.mjs} +3 -3
  29. package/dist/cli/chunk-QILHWZFP.js +1421 -0
  30. package/dist/cli/{chunk-N47R7S3E.mjs → chunk-SJY4SUCH.mjs} +1 -1
  31. package/dist/cli/{chunk-LJTZVC2T.mjs → chunk-SPJ3IDR4.mjs} +2 -2
  32. package/dist/cli/{chunk-4YXJTQ5X.js → chunk-UZQWPQ6V.js} +5 -5
  33. package/dist/cli/{chunk-ZYPSNUDU.js → chunk-V4MVLHOW.js} +231 -231
  34. package/dist/cli/{chunk-VG5WMYSF.mjs → chunk-VZBNBCEB.mjs} +3 -3
  35. package/dist/cli/{chunk-6P235JSN.js → chunk-WVO6AJM7.js} +15 -15
  36. package/dist/cli/{chunk-XYD76KJB.mjs → chunk-WYVUMIEF.mjs} +8 -8
  37. package/dist/cli/{chunk-QYCAH7SM.mjs → chunk-YWGKGXSH.mjs} +6 -2
  38. package/dist/cli/client-I4RRFLPM.js +20 -0
  39. package/dist/cli/{client-23QTYYIR.mjs → client-M2TQV23S.mjs} +4 -4
  40. package/dist/cli/command-docs.d.mts +50 -0
  41. package/dist/cli/command-docs.d.ts +50 -0
  42. package/dist/cli/command-docs.js +13 -0
  43. package/dist/cli/command-docs.mjs +13 -0
  44. package/dist/cli/{csv-VKEF5C65.mjs → csv-OQUTHAMD.mjs} +3 -3
  45. package/dist/cli/{csv-SQIEBX66.js → csv-VSVZWQ24.js} +12 -12
  46. package/dist/cli/{db-Z33ZRE3N.js → db-6QNWRH47.js} +30 -30
  47. package/dist/cli/{db-KX7FDHL4.mjs → db-QH56PKPT.mjs} +5 -5
  48. package/dist/cli/{describe-5ZQ6KIJH.js → describe-BKJNG2I5.js} +21 -21
  49. package/dist/cli/{describe-K2SJ22B3.mjs → describe-NCUJW2JG.mjs} +16 -16
  50. package/dist/cli/{executions-KCZZY6SS.js → executions-63CSEYKE.js} +7 -7
  51. package/dist/cli/{executions-452G2REF.mjs → executions-NOZELHEN.mjs} +5 -5
  52. package/dist/cli/{feedback-55HU4EQY.mjs → feedback-GL7YHJAK.mjs} +3 -3
  53. package/dist/cli/{feedback-BM7ZNH7Y.js → feedback-T4FGM7DY.js} +7 -7
  54. package/dist/cli/git-4EBFGJAN.js +23 -0
  55. package/dist/cli/{git-CAPESVAJ.mjs → git-C3SPUEUB.mjs} +4 -4
  56. package/dist/cli/index.js +183 -83
  57. package/dist/cli/index.mjs +145 -45
  58. package/dist/cli/{legacy-noops-3B3MG33Y.mjs → legacy-noops-BBDNHMAW.mjs} +4 -4
  59. package/dist/cli/{legacy-noops-PV5AWTIZ.js → legacy-noops-W6UWORVY.js} +7 -7
  60. package/dist/cli/{monitors-43R6CUFK.mjs → monitors-LEGM63T3.mjs} +5 -5
  61. package/dist/cli/{monitors-DVFEJHQT.js → monitors-R32SKFE3.js} +86 -86
  62. package/dist/cli/{org-X5WSLJRD.mjs → org-RBIBSVP3.mjs} +5 -5
  63. package/dist/cli/{org-BGCB45VI.js → org-RYRHB22V.js} +61 -61
  64. package/dist/cli/{play-OFO6XZ5L.mjs → play-FMPNHLXU.mjs} +11 -11
  65. package/dist/cli/play-GDDOUU3I.js +153 -0
  66. package/dist/cli/{providers-L2MPYA3E.mjs → providers-5JHHZWMF.mjs} +5 -5
  67. package/dist/cli/{providers-RJYHQC4J.js → providers-WAUWYRTK.js} +8 -8
  68. package/dist/cli/{quickstart-NHFMGZ3B.mjs → quickstart-RYKNMAFN.mjs} +5 -5
  69. package/dist/cli/{quickstart-E6OWYYMT.js → quickstart-V5LHMW2X.js} +7 -7
  70. package/dist/cli/{search-IPTZAOUE.mjs → search-B2BB2MHO.mjs} +6 -6
  71. package/dist/cli/search-RQLY5D2A.js +26 -0
  72. package/dist/cli/{secrets-6LD4CA2X.js → secrets-BEAESK2H.js} +14 -14
  73. package/dist/cli/{secrets-TCOPPNXR.mjs → secrets-XJOXWVUG.mjs} +5 -5
  74. package/dist/cli/{sessions-QV4LUKEP.js → sessions-ELBTWKLS.js} +5 -5
  75. package/dist/cli/{sessions-D4JETMAW.mjs → sessions-VRDJCPKE.mjs} +4 -4
  76. package/dist/cli/{settings-IVJPCORG.mjs → settings-QEL2SD7N.mjs} +8 -8
  77. package/dist/cli/{settings-JOYKXSY7.js → settings-V5RI4UWE.js} +75 -75
  78. package/dist/cli/{setup-MIJZTYMH.js → setup-CS3IACGU.js} +43 -43
  79. package/dist/cli/{setup-WCKQK7FE.mjs → setup-FNVP5XQR.mjs} +8 -8
  80. package/dist/cli/{skills-GYWMUDDF.mjs → skills-73PM6I6F.mjs} +4 -4
  81. package/dist/cli/skills-OH3X3OUA.js +28 -0
  82. package/dist/cli/tools-BY6D7M4I.js +40 -0
  83. package/dist/cli/{tools-ILYNEPGQ.mjs → tools-N2H5HJYP.mjs} +8 -8
  84. package/dist/cli/{update-P3VIHMC6.mjs → update-MUMZ6L7J.mjs} +6 -6
  85. package/dist/cli/update-PEGDRQ3G.js +28 -0
  86. package/dist/cli/{workflow-A6ID4C6N.mjs → workflow-WY2CL73A.mjs} +9 -9
  87. package/dist/cli/{workflow-YWKK3A4X.js → workflow-X66V6L3H.js} +19 -19
  88. package/dist/index.js +13 -13
  89. package/dist/index.mjs +12 -12
  90. package/dist/install-integrity.json +80 -74
  91. package/dist/release.d.mts +1 -1
  92. package/dist/release.d.ts +1 -1
  93. package/dist/release.js +2 -2
  94. package/dist/release.mjs +1 -1
  95. package/package.json +6 -1
  96. package/dist/cli/auth-AABYKR2S.js +0 -31
  97. package/dist/cli/client-3DYV3HZ4.js +0 -20
  98. package/dist/cli/git-VESXOJ4M.js +0 -23
  99. package/dist/cli/play-ZO4HDGMC.js +0 -153
  100. package/dist/cli/search-2FJA6OH4.js +0 -26
  101. package/dist/cli/skills-JL4J5CII.js +0 -28
  102. package/dist/cli/tools-ZOKG4AX4.js +0 -40
  103. package/dist/cli/update-WUMDEAEE.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.357',
203
+ version: '0.3.358',
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: {
@@ -20,7 +20,7 @@ import { monitorBillingRequiresExplicitReactivation } from './billing/state';
20
20
  * this off" and "we did, and here is why" must not be the same state.
21
21
  */
22
22
 
23
- export type MonitorDeactivationOwner = 'policy' | 'fleet';
23
+ export type MonitorDeactivationOwner = 'policy' | 'fleet' | 'admin';
24
24
 
25
25
  /**
26
26
  * The delivery window, in days, that `monitors health` reports against.
@@ -43,6 +43,13 @@ export type MonitorDeactivationReasonDefinition = {
43
43
 
44
44
  export const MONITOR_DEACTIVATION_REASONS: readonly MonitorDeactivationReasonDefinition[] =
45
45
  [
46
+ {
47
+ id: 'workspace_archived',
48
+ owner: 'admin',
49
+ summary: 'Deactivated because the workspace is archived.',
50
+ resolution:
51
+ 'Contact a platform administrator. Credit changes never reactivate an archived workspace.',
52
+ },
46
53
  {
47
54
  id: 'insufficient_balance',
48
55
  owner: 'policy',
@@ -122,6 +129,12 @@ export function isPolicyDeactivation(id: string | undefined | null): boolean {
122
129
  return monitorDeactivationReason(id)?.owner === 'policy';
123
130
  }
124
131
 
132
+ /** Both policy and admin shutdown retain the definition; only policy owns recovery. */
133
+ export function isMonitorSuspension(id: string | undefined | null): boolean {
134
+ const owner = monitorDeactivationReason(id)?.owner;
135
+ return owner === 'policy' || owner === 'admin';
136
+ }
137
+
125
138
  // ---------------------------------------------------------------------------
126
139
  // The balance policy
127
140
  // ---------------------------------------------------------------------------
@@ -954,6 +954,17 @@ function asyncOperationRateLimitPollDelayMs(input: {
954
954
  return Math.max(input.retryAfterMs ?? 0, localBackoffMs);
955
955
  }
956
956
 
957
+ /** A paced read of an already accepted job must never restart its launch. */
958
+ export class AsyncOperationCompanionPacingError extends Error {
959
+ constructor(
960
+ message: string,
961
+ readonly retryAfterMs: number,
962
+ ) {
963
+ super(message);
964
+ this.name = 'AsyncOperationCompanionPacingError';
965
+ }
966
+ }
967
+
957
968
  /**
958
969
  * Runtime-neutral executor for a declared async lifecycle.
959
970
  *
@@ -1074,25 +1085,47 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
1074
1085
  }): Promise<
1075
1086
  { kind: 'response'; response: Response } | { kind: 'timed_out' }
1076
1087
  > => {
1077
- const remainingTimeoutMs = Math.max(
1078
- 0,
1079
- polling.timeoutMs - (now() - startedAt),
1080
- );
1081
- if (remainingTimeoutMs <= 0) return { kind: 'timed_out' };
1082
- const abortSignal = AbortSignal.timeout(Math.ceil(remainingTimeoutMs));
1083
- try {
1084
- const response = await input.executeAction({
1085
- ...request,
1086
- jobId,
1087
- timeoutMs: Math.ceil(remainingTimeoutMs),
1088
- abortSignal,
1089
- });
1090
- return now() - startedAt >= polling.timeoutMs || abortSignal.aborted
1091
- ? { kind: 'timed_out' }
1092
- : { kind: 'response', response };
1093
- } catch (error) {
1094
- if (abortSignal.aborted) return { kind: 'timed_out' };
1095
- throw error;
1088
+ let pacingAttempt = 0;
1089
+ while (true) {
1090
+ const remainingTimeoutMs = Math.max(
1091
+ 0,
1092
+ polling.timeoutMs - (now() - startedAt),
1093
+ );
1094
+ if (remainingTimeoutMs <= 0) return { kind: 'timed_out' };
1095
+ const abortSignal = AbortSignal.timeout(Math.ceil(remainingTimeoutMs));
1096
+ try {
1097
+ const response = await input.executeAction({
1098
+ ...request,
1099
+ jobId,
1100
+ timeoutMs: Math.ceil(remainingTimeoutMs),
1101
+ abortSignal,
1102
+ });
1103
+ return now() - startedAt >= polling.timeoutMs || abortSignal.aborted
1104
+ ? { kind: 'timed_out' }
1105
+ : { kind: 'response', response };
1106
+ } catch (error) {
1107
+ if (abortSignal.aborted) return { kind: 'timed_out' };
1108
+ if (!(error instanceof AsyncOperationCompanionPacingError)) throw error;
1109
+ // This refusal applies only to the current companion read. The launch
1110
+ // already owns its job and hold. Stay inside their original deadline
1111
+ // and repeat this exact action/page, never the enclosing start request.
1112
+ pacingAttempt += 1;
1113
+ const delayMs = Math.max(
1114
+ 1,
1115
+ asyncOperationRateLimitPollDelayMs({
1116
+ contract: effectiveContract,
1117
+ pollAttempt: request.pollAttempt,
1118
+ rateLimitAttempt: pacingAttempt,
1119
+ retryAfterMs: error.retryAfterMs,
1120
+ }),
1121
+ );
1122
+ const remainingAfterRefusal = Math.max(
1123
+ 0,
1124
+ polling.timeoutMs - (now() - startedAt),
1125
+ );
1126
+ if (remainingAfterRefusal <= 0) return { kind: 'timed_out' };
1127
+ await sleep(Math.min(delayMs, remainingAfterRefusal));
1128
+ }
1096
1129
  }
1097
1130
  };
1098
1131
  while (true) {
@@ -17609,6 +17609,22 @@ export async function readPostgresSchedulerObservationAdmissions(
17609
17609
  }));
17610
17610
  }
17611
17611
 
17612
+ /** Admission authority: includes live runs whose Convex projection has not landed. */
17613
+ export async function listPostgresSchedulerActiveRunsForOrg(
17614
+ client: PostgresSchedulerQueryClient,
17615
+ orgId: string,
17616
+ options?: Pick<PostgresSchedulerOptions, 'schema'>,
17617
+ ): Promise<string[]> {
17618
+ if (!orgId.trim()) throw new Error('Workspace ID is required.');
17619
+ const result = await client.query<{ run_id: string }>(
17620
+ `SELECT run_id FROM ${tableName('runs', options)}
17621
+ WHERE org_id = $1 AND status IN ('queued', 'running', 'waiting')
17622
+ ORDER BY created_at, run_id LIMIT 5`,
17623
+ [orgId],
17624
+ );
17625
+ return result.rows.map((row) => row.run_id);
17626
+ }
17627
+
17612
17628
  export async function readPostgresSchedulerRunStatus(
17613
17629
  client: PostgresSchedulerQueryClient,
17614
17630
  runId: string,
@@ -18770,26 +18786,25 @@ async function claimPostgresSchedulerOutboxPrefix(
18770
18786
  */
18771
18787
  export async function readPostgresSchedulerOutboxNextDueAt(
18772
18788
  client: PostgresSchedulerQueryClient,
18773
- options?: Pick<PostgresSchedulerOptions, 'schema'>,
18789
+ options?: Pick<PostgresSchedulerOptions, 'schema'> & {
18790
+ lane?: 'lifecycle' | 'observability';
18791
+ },
18774
18792
  ): Promise<Date | null> {
18793
+ const lanes =
18794
+ options?.lane === 'lifecycle'
18795
+ ? [false]
18796
+ : options?.lane === 'observability'
18797
+ ? [true]
18798
+ : [false, true];
18775
18799
  const result = await client.query<{ due_at: Date | string | null }>(
18776
18800
  `
18777
18801
  -- Seek distinct run identities without reading their pending fact tails.
18778
- WITH RECURSIVE ${runLedgerPendingRunsSql(options)}, heads AS MATERIALIZED (
18779
- SELECT pending_run.run_id, false AS search_only, head.*
18780
- FROM pending_runs AS pending_run
18781
- CROSS JOIN LATERAL (
18782
- SELECT pending.created_at, pending.next_attempt_at,
18783
- pending.failed_at, pending.lease_expires_at
18784
- FROM ${tableName('outbox', options)} AS pending
18785
- WHERE pending.run_id = pending_run.run_id
18786
- AND pending.delivered_at IS NULL
18787
- AND ${searchLaneSql('pending')} = false
18788
- ORDER BY ${runLedgerOrderSql('pending')}
18789
- LIMIT 1
18790
- ) AS head
18791
- UNION ALL
18792
- SELECT pending_run.run_id, true AS search_only, head.*
18802
+ WITH RECURSIVE ${runLedgerPendingRunsSql(options, options?.lane === undefined ? undefined : lanes[0])}, heads AS MATERIALIZED (
18803
+ ${lanes
18804
+ .map(
18805
+ (
18806
+ searchOnly,
18807
+ ) => ` SELECT pending_run.run_id, ${searchOnly} AS search_only, head.*
18793
18808
  FROM pending_runs AS pending_run
18794
18809
  CROSS JOIN LATERAL (
18795
18810
  SELECT pending.created_at, pending.next_attempt_at,
@@ -18797,10 +18812,12 @@ export async function readPostgresSchedulerOutboxNextDueAt(
18797
18812
  FROM ${tableName('outbox', options)} AS pending
18798
18813
  WHERE pending.run_id = pending_run.run_id
18799
18814
  AND pending.delivered_at IS NULL
18800
- AND ${searchLaneSql('pending')} = true
18815
+ AND ${searchLaneSql('pending')} = ${searchOnly}
18801
18816
  ORDER BY ${runLedgerOrderSql('pending')}
18802
18817
  LIMIT 1
18803
- ) AS head
18818
+ ) AS head`,
18819
+ )
18820
+ .join('\n UNION ALL\n')}
18804
18821
  )
18805
18822
  SELECT min(greatest(
18806
18823
  CASE
@@ -100,7 +100,7 @@ export type RunLedgerOutboxRecoveryDeps = {
100
100
  ) => Promise<PostgresSchedulerOutboxProjectionResult>;
101
101
  observabilityParallelism?: number;
102
102
  /** Earliest pending retry time or active lease expiry in the durable outbox. */
103
- readNextDueAt: () => Promise<Date | null>;
103
+ readNextDueAt: (lane?: 'lifecycle' | 'observability') => Promise<Date | null>;
104
104
  /** A direct LISTEN connection. It is a hint only; a null lease is safe. */
105
105
  acquireNotifyHub: () => PgNotifyHubLease | null;
106
106
  /** Bounded number of independent runs this replica may deliver at once. */
@@ -201,6 +201,15 @@ export function startRunLedgerOutboxRecovery(
201
201
  let requested = false;
202
202
  let failureCount = 0;
203
203
  let timer: RecoveryTimer | null = null;
204
+ let timerAtMs: number | null = null;
205
+ let dueClockGeneration = 0;
206
+ let dueClockRead: Promise<void> | null = null;
207
+ let dueClockRefreshRequested = false;
208
+ let dueClockReadGeneration = 0;
209
+ let dueClockKnownLane: 'both' | 'lifecycle' | 'observability' | null = null;
210
+ let producerHintGeneration = 0;
211
+ let dueClockReadHintGeneration = 0;
212
+ let dueClockKnownHintGeneration = -1;
204
213
  let drainPromise: Promise<void> | null = null;
205
214
  let wakeActiveDrain: (() => void) | null = null;
206
215
  let cooldownUntilMs: number | null = null;
@@ -208,23 +217,31 @@ export function startRunLedgerOutboxRecovery(
208
217
  let unsubscribeNotify: (() => void) | undefined;
209
218
  let unsubscribeState: (() => void) | undefined;
210
219
  let requestDrain: (reason: string, runId?: string) => void = () => undefined;
211
- const lifecycleRunHints = new Set<string>();
212
- const observabilityRunHints = new Set<string>();
220
+ // A producer hint may reveal a lease that the claim cannot acquire. A
221
+ // successful scoped claim's continuation does not force a metadata query
222
+ // while other deliveries keep the drain busy. Zero marks that continuation.
223
+ const lifecycleRunHints = new Map<string, number>();
224
+ const observabilityRunHints = new Map<string, number>();
213
225
  const hasRequests = () =>
214
226
  requested ||
215
227
  observabilityRequested ||
216
228
  lifecycleRunHints.size > 0 ||
217
229
  observabilityRunHints.size > 0;
218
- const addRunHint = (hints: Set<string>, runId: string) => {
219
- hints.add(runId);
230
+ const addRunHint = (
231
+ hints: Map<string, number>,
232
+ runId: string,
233
+ discoveryGeneration = producerHintGeneration,
234
+ ) => {
235
+ hints.set(runId, Math.max(discoveryGeneration, hints.get(runId) ?? 0));
220
236
  if (hints.size <= MAX_NOTIFICATION_RUN_HINTS) return true;
221
237
  hints.clear();
222
238
  return false;
223
239
  };
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;
240
+ const takeRunHint = (hints: Map<string, number>) => {
241
+ const hint = hints.entries().next().value;
242
+ if (!hint) return undefined;
243
+ hints.delete(hint[0]);
244
+ return { runId: hint[0], discoveryGeneration: hint[1] };
228
245
  };
229
246
  const inFlightStartedAt = new Map<number, number>();
230
247
  let nextDispatchId = 0;
@@ -261,6 +278,7 @@ export function startRunLedgerOutboxRecovery(
261
278
  if (!timer) return;
262
279
  clearTimer(timer);
263
280
  timer = null;
281
+ timerAtMs = null;
264
282
  };
265
283
 
266
284
  const listenerIsAvailable = () => hubLease?.hub.state() === 'listening';
@@ -285,8 +303,8 @@ export function startRunLedgerOutboxRecovery(
285
303
 
286
304
  const startDrain = (reason: string) => {
287
305
  if (closed) return;
288
- if (cooldownUntilMs !== null && now() < cooldownUntilMs) return;
289
- clearWakeTimer();
306
+ if (cooldownUntilMs !== null) return;
307
+ // Scoped producer hints cannot cancel another run's retry/lease clock.
290
308
  if (drainPromise) {
291
309
  wakeActiveDrain?.();
292
310
  return;
@@ -308,6 +326,10 @@ export function startRunLedgerOutboxRecovery(
308
326
 
309
327
  requestDrain = (reason: string, runId?: string) => {
310
328
  if (closed) return;
329
+ // Tag producer hints so a later missed claim can reject an older empty
330
+ // snapshot. Productive hints need no metadata refresh while work is busy.
331
+ producerHintGeneration += 1;
332
+ dueClockKnownLane = null;
311
333
  if (runId === undefined) {
312
334
  requested = true;
313
335
  observabilityRequested = !!deps.dispatchObservabilityOnce;
@@ -354,27 +376,32 @@ export function startRunLedgerOutboxRecovery(
354
376
  error: error instanceof Error ? error.message : String(error),
355
377
  });
356
378
  if (closed) return;
379
+ dueClockGeneration += 1;
380
+ dueClockKnownLane = null;
357
381
  clearWakeTimer();
358
382
  cooldownUntilMs = now() + delayMs;
383
+ timerAtMs = cooldownUntilMs;
359
384
  timer = setTimer(() => {
360
385
  timer = null;
386
+ timerAtMs = null;
361
387
  cooldownUntilMs = null;
362
388
  requestDrain('recovery_error_retry');
363
389
  }, delayMs);
364
390
  timer.unref?.();
365
391
  };
366
392
 
367
- const scheduleNextDue = async (): Promise<void> => {
368
- if (closed) return;
369
- let dueAt: Date | null;
370
- try {
371
- dueAt = await deps.readNextDueAt();
372
- failureCount = 0;
373
- } catch (error) {
374
- scheduleFailureRetry('next_due', error);
375
- return;
393
+ // A retained global probe already owns discovery for its lane. Read only
394
+ // the other lane, so a full lane's ready head cannot create a zero-delay loop.
395
+ const dueClockLane = (): 'both' | 'lifecycle' | 'observability' | null => {
396
+ if (requested) {
397
+ return deps.dispatchObservabilityOnce && !observabilityRequested
398
+ ? 'observability'
399
+ : null;
376
400
  }
377
- if (closed || hasRequests()) return;
401
+ return observabilityRequested ? 'lifecycle' : 'both';
402
+ };
403
+
404
+ const scheduleDueWake = (dueAt: Date | null, provisional = false) => {
378
405
  // When LISTEN is healthy the exact durable due time is sufficient. If the
379
406
  // optional listener is absent, down, or lost an election, an indexed
380
407
  // short safety wake discovers a fresh row that otherwise had no
@@ -383,24 +410,101 @@ export function startRunLedgerOutboxRecovery(
383
410
  const delayMs = listenerIsAvailable()
384
411
  ? exactDueMs
385
412
  : Math.min(exactDueMs ?? Infinity, notifyUnavailableSafetyWakeMs);
386
- if (delayMs === null || !Number.isFinite(delayMs)) return;
413
+ if (provisional && (delayMs === null || !Number.isFinite(delayMs))) return;
414
+ const boundedDelayMs = Math.min(MAX_TIMEOUT_MS, delayMs ?? Infinity);
415
+ const wakeAtMs = now() + boundedDelayMs;
416
+ if (provisional && timerAtMs !== null && timerAtMs <= wakeAtMs) return;
387
417
  clearWakeTimer();
388
- timer = setTimer(
389
- () => {
390
- timer = null;
391
- if (!listenerIsAvailable()) attachNotifyHub();
392
- requestDrain(dueAt ? 'next_due' : 'listener_safety_wake');
393
- },
394
- Math.min(MAX_TIMEOUT_MS, delayMs),
395
- );
418
+ if (delayMs === null || !Number.isFinite(delayMs)) return;
419
+ timerAtMs = wakeAtMs;
420
+ timer = setTimer(() => {
421
+ timer = null;
422
+ timerAtMs = null;
423
+ dueClockGeneration += 1;
424
+ if (!listenerIsAvailable()) attachNotifyHub();
425
+ requestDrain(dueAt ? 'next_due' : 'listener_safety_wake');
426
+ }, boundedDelayMs);
396
427
  timer.unref?.();
397
428
  };
398
429
 
430
+ const readNextDueClock = async (
431
+ generation: number,
432
+ lane: Exclude<ReturnType<typeof dueClockLane>, null>,
433
+ hintGeneration: number,
434
+ ): Promise<void> => {
435
+ let dueAt: Date | null;
436
+ try {
437
+ dueAt = await deps.readNextDueAt(lane === 'both' ? undefined : lane);
438
+ } catch (error) {
439
+ if (
440
+ closed ||
441
+ cooldownUntilMs !== null ||
442
+ generation !== dueClockGeneration
443
+ ) {
444
+ logger.warn('[run-ledger-outbox.recovery_retry]', {
445
+ stage: 'next_due',
446
+ superseded: true,
447
+ error: error instanceof Error ? error.message : String(error),
448
+ });
449
+ return;
450
+ }
451
+ scheduleFailureRetry('next_due', error);
452
+ return;
453
+ }
454
+ if (closed || cooldownUntilMs !== null) return;
455
+ const currentLane = dueClockLane();
456
+ if (
457
+ generation !== dueClockGeneration ||
458
+ lane !== currentLane ||
459
+ dueClockRefreshRequested
460
+ ) {
461
+ dueClockRefreshRequested = true;
462
+ // Hints and retry completions invalidate freshness, not a known due
463
+ // time. Only the currently discoverable lane may supply a provisional
464
+ // wake, which advances the timer without caching a stale snapshot.
465
+ if (lane === currentLane && dueAt) scheduleDueWake(dueAt, true);
466
+ return;
467
+ }
468
+ failureCount = 0;
469
+ dueClockKnownLane = lane;
470
+ dueClockKnownHintGeneration = hintGeneration;
471
+ scheduleDueWake(dueAt);
472
+ };
473
+
474
+ const scheduleNextDue = (refresh = false): Promise<void> => {
475
+ if (closed || cooldownUntilMs !== null) return Promise.resolve();
476
+ if (dueClockRead) {
477
+ // A fired clock invalidates even an unchanged coalesced read. Retain
478
+ // that discovery debt until the old read settles.
479
+ dueClockRefreshRequested ||=
480
+ refresh || dueClockReadGeneration !== dueClockGeneration;
481
+ return dueClockRead;
482
+ }
483
+ const lane = dueClockLane();
484
+ if (lane === null || (dueClockKnownLane === lane && !refresh))
485
+ return Promise.resolve();
486
+ dueClockReadGeneration = dueClockGeneration;
487
+ dueClockReadHintGeneration = producerHintGeneration;
488
+ dueClockRead = readNextDueClock(
489
+ dueClockReadGeneration,
490
+ lane,
491
+ dueClockReadHintGeneration,
492
+ ).finally(() => {
493
+ dueClockRead = null;
494
+ if (dueClockRefreshRequested) {
495
+ dueClockRefreshRequested = false;
496
+ if (!closed && cooldownUntilMs === null) void scheduleNextDue(true);
497
+ }
498
+ });
499
+ return dueClockRead;
500
+ };
501
+
399
502
  const drain = async (reason: string): Promise<void> => {
400
503
  type Delivery = {
401
504
  id: symbol;
402
505
  observability: boolean;
403
506
  runId?: string;
507
+ discoveryGeneration: number;
404
508
  result:
405
509
  | PromiseSettledResult<PostgresSchedulerOutboxProjectionResult>
406
510
  | { status: 'skipped' };
@@ -435,7 +539,11 @@ export function startRunLedgerOutboxRecovery(
435
539
  completed = attempted = projected = retrying = blocked = 0;
436
540
  };
437
541
 
438
- const launch = (observability = false, runId?: string) => {
542
+ const launch = (
543
+ observability = false,
544
+ runId?: string,
545
+ discoveryGeneration = producerHintGeneration,
546
+ ) => {
439
547
  const id = Symbol();
440
548
  pending.add(id);
441
549
  if (observability) pendingObservability.add(id);
@@ -444,13 +552,19 @@ export function startRunLedgerOutboxRecovery(
444
552
  dispatchFailed = true;
445
553
  scheduleFailureRetry('dispatch', result.reason);
446
554
  }
447
- deliveries.push({ id, observability, runId, result });
555
+ deliveries.push({
556
+ id,
557
+ observability,
558
+ runId,
559
+ discoveryGeneration,
560
+ result,
561
+ });
448
562
  wakeActiveDrain?.();
449
563
  };
450
564
  // Subscribe once per delivery. Repeated Promise.race calls would retain
451
565
  // a new reaction on a slow callback every time a faster slot completes.
452
566
  void Promise.resolve().then(async () => {
453
- if (closed || dispatchFailed) {
567
+ if (closed || dispatchFailed || cooldownUntilMs !== null) {
454
568
  settle({ status: 'skipped' });
455
569
  return;
456
570
  }
@@ -468,32 +582,34 @@ export function startRunLedgerOutboxRecovery(
468
582
  const canProbeLifecycle = () =>
469
583
  !closed &&
470
584
  !dispatchFailed &&
585
+ cooldownUntilMs === null &&
471
586
  (requested || lifecycleRunHints.size > 0) &&
472
587
  pending.size < parallelism &&
473
588
  pending.size - pendingObservability.size < lifecycleParallelism;
474
589
  const canProbeObservability = () =>
475
590
  !closed &&
476
591
  !dispatchFailed &&
592
+ cooldownUntilMs === null &&
477
593
  (observabilityRequested || observabilityRunHints.size > 0) &&
478
594
  pending.size < parallelism &&
479
595
  pendingObservability.size < observabilityParallelism;
480
596
 
481
597
  for (;;) {
482
- if (!closed && !dispatchFailed) {
598
+ if (!closed && !dispatchFailed && cooldownUntilMs === null) {
483
599
  // A wake grants one indexed probe, including when other slots are
484
600
  // still delivering. Keep it pending when full so an empty result
485
601
  // cannot consume a notification that arrived after its query began.
486
602
  if (canProbeObservability()) {
487
- const runId = observabilityRequested
603
+ const hint = observabilityRequested
488
604
  ? undefined
489
605
  : takeRunHint(observabilityRunHints);
490
606
  observabilityRequested = false;
491
- launch(true, runId);
607
+ launch(true, hint?.runId, hint?.discoveryGeneration);
492
608
  }
493
609
  if (canProbeLifecycle()) {
494
- const runId = requested ? undefined : takeRunHint(lifecycleRunHints);
610
+ const hint = requested ? undefined : takeRunHint(lifecycleRunHints);
495
611
  requested = false;
496
- launch(false, runId);
612
+ launch(false, hint?.runId, hint?.discoveryGeneration);
497
613
  }
498
614
  // Productive completion permits immediate use of every free slot.
499
615
  // Empty results grant no refill, so idle work cannot spin on scans.
@@ -516,7 +632,9 @@ export function startRunLedgerOutboxRecovery(
516
632
  }
517
633
  if (pending.size === 0) {
518
634
  reportDeliveries();
519
- if (!closed && !dispatchFailed) await scheduleNextDue();
635
+ // Metadata discovery is single-flight, but never owns a claim slot.
636
+ // A slow snapshot must not delay a producer's next scoped claim.
637
+ if (!closed && !dispatchFailed) void scheduleNextDue();
520
638
  return;
521
639
  }
522
640
 
@@ -541,12 +659,46 @@ export function startRunLedgerOutboxRecovery(
541
659
  continue;
542
660
  }
543
661
  const result = delivery.result.value;
662
+ const hintLane = delivery.observability ? 'observability' : 'lifecycle';
663
+ const missedProducerHint =
664
+ delivery.discoveryGeneration > 0 &&
665
+ result.attempted === 0 &&
666
+ (dueClockKnownHintGeneration < delivery.discoveryGeneration ||
667
+ (dueClockKnownLane !== 'both' && dueClockKnownLane !== hintLane));
668
+ if (result.attempted > 0 || missedProducerHint) dueClockKnownLane = null;
669
+ // A claim returning empty may have lost to an inline owner. If its
670
+ // notification followed the pending metadata snapshot, retain discovery
671
+ // debt so that snapshot cannot cache an obsolete empty result.
672
+ if (
673
+ missedProducerHint &&
674
+ dueClockRead &&
675
+ dueClockReadHintGeneration < delivery.discoveryGeneration
676
+ )
677
+ dueClockRefreshRequested = true;
678
+ if (result.retrying > 0) dueClockGeneration += 1;
544
679
  completed += 1;
545
680
  attempted += result.attempted;
546
681
  projected += result.projected;
547
682
  retrying += result.retrying;
548
683
  blocked += result.failed;
549
684
  if (completed >= parallelism) reportDeliveries();
685
+ // Discovery cannot wait for every independent HTTP delivery to finish.
686
+ // A retry commits a new clock; an empty global probe can expose that
687
+ // clock while another slot remains busy. A missed producer hint may
688
+ // expose an inline owner's lease; one later snapshot covers all earlier
689
+ // hints. Scoped success keeps its cheap continuation, and the idle drain
690
+ // still refreshes metadata after those continuations settle.
691
+ if (
692
+ !closed &&
693
+ !dispatchFailed &&
694
+ pending.size > deliveries.length &&
695
+ (result.retrying > 0 ||
696
+ missedProducerHint ||
697
+ ((timer === null || dueClockKnownLane !== dueClockLane()) &&
698
+ delivery.runId === undefined &&
699
+ result.attempted === 0))
700
+ )
701
+ void scheduleNextDue(result.retrying > 0);
550
702
  // Retryable rows own durable retry clocks. The SQL claim keeps their
551
703
  // run ordered while successful slots keep unrelated runs moving.
552
704
  if (delivery.runId !== undefined) {
@@ -556,12 +708,12 @@ export function startRunLedgerOutboxRecovery(
556
708
  if (delivery.observability) {
557
709
  if (
558
710
  !observabilityRequested &&
559
- !addRunHint(observabilityRunHints, delivery.runId)
711
+ !addRunHint(observabilityRunHints, delivery.runId, 0)
560
712
  )
561
713
  observabilityRequested = true;
562
714
  } else if (
563
715
  !requested &&
564
- !addRunHint(lifecycleRunHints, delivery.runId)
716
+ !addRunHint(lifecycleRunHints, delivery.runId, 0)
565
717
  )
566
718
  requested = true;
567
719
  }
@@ -594,6 +746,7 @@ export function startRunLedgerOutboxRecovery(
594
746
  close: async () => {
595
747
  if (closed) return;
596
748
  closed = true;
749
+ dueClockGeneration += 1;
597
750
  wakeActiveDrain?.();
598
751
  clearWakeTimer();
599
752
  cooldownUntilMs = null;
@@ -602,6 +755,7 @@ export function startRunLedgerOutboxRecovery(
602
755
  unsubscribeNotify?.();
603
756
  unsubscribeState?.();
604
757
  await drainPromise;
758
+ await dueClockRead;
605
759
  hubLease?.release();
606
760
  },
607
761
  };
@@ -859,10 +1013,11 @@ export function startRunLedgerOutboxRecoveryController(input: {
859
1013
  runId?: string,
860
1014
  ) =>
861
1015
  dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope(
862
- (operation) =>
1016
+ (operation, trace) =>
863
1017
  withRuntimeSchedulerClient(
864
1018
  searchOnly ? observabilityPoolOptions : input.poolOptions,
865
1019
  operation,
1020
+ trace,
866
1021
  ),
867
1022
  {
868
1023
  tokenAuthority: 'gateway',
@@ -901,9 +1056,12 @@ export function startRunLedgerOutboxRecoveryController(input: {
901
1056
  notifyUnavailableSafetyWakeMs: runLedgerOutboxNotifySafetyWakeMs(
902
1057
  schedulerOptions.schema,
903
1058
  ),
904
- readNextDueAt: () =>
1059
+ readNextDueAt: (lane) =>
905
1060
  withRuntimeSchedulerClient(input.poolOptions, (client) =>
906
- readPostgresSchedulerOutboxNextDueAt(client, schedulerOptions),
1061
+ readPostgresSchedulerOutboxNextDueAt(client, {
1062
+ ...schedulerOptions,
1063
+ lane,
1064
+ }),
907
1065
  ),
908
1066
  });
909
1067
  }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  EXIT_WORKSPACE_MISMATCH,
3
+ authStatusLine,
3
4
  clearPendingClaim,
4
5
  clearSatisfiedPendingClaim,
5
6
  handleRegister,
@@ -9,16 +10,17 @@ import {
9
10
  registerAuthCommands,
10
11
  reportSetupCompleted,
11
12
  resolvePendingAuthClaimForResume
12
- } from "./chunk-VXH4225Y.mjs";
13
- import "./chunk-PJYCAGKK.mjs";
14
- import "./chunk-VG5WMYSF.mjs";
13
+ } from "./chunk-HGLESMAC.mjs";
14
+ import "./chunk-EL62MLRD.mjs";
15
+ import "./chunk-VZBNBCEB.mjs";
15
16
  import "./chunk-DLIRSHV6.mjs";
16
- import "./chunk-N47R7S3E.mjs";
17
17
  import "./chunk-NXGCO4JB.mjs";
18
+ import "./chunk-SJY4SUCH.mjs";
18
19
  import "./chunk-WZYKRD7T.mjs";
19
20
  import "./chunk-KRD6H754.mjs";
20
21
  export {
21
22
  EXIT_WORKSPACE_MISMATCH,
23
+ authStatusLine,
22
24
  clearPendingClaim,
23
25
  clearSatisfiedPendingClaim,
24
26
  handleRegister,