deepline 0.3.113 → 0.3.115

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 (50) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +3 -1
  2. package/dist/bundling-sources/sdk/src/compat.ts +167 -11
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/skills-version.ts +27 -1
  5. package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +6 -2
  6. package/dist/bundling-sources/shared_libs/observability/dlq.ts +942 -0
  7. package/dist/bundling-sources/shared_libs/observability/queue-health.ts +361 -6
  8. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +24 -24
  9. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +2 -0
  10. package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +29 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/admission-transport-policy.ts +91 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +2 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/direct-admission-deadline.ts +34 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +1 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +325 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/play-admission-outcome.ts +134 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +13 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +4 -1
  19. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +13 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/play-webhook-admission-probe.ts +32 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/play-webhook-identity.ts +28 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +7 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-cron-fire-task.ts +100 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +313 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +111 -3
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +16 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +67 -8
  28. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1734 -147
  29. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +74 -13
  30. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +245 -81
  31. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +79 -15
  32. package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-retry.ts +21 -21
  33. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +4 -1
  34. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +64 -0
  35. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +1 -1
  36. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +20 -1
  37. package/dist/bundling-sources/shared_libs/security/receipt-admission-token.ts +224 -0
  38. package/dist/cli/index.js +574 -114
  39. package/dist/cli/index.mjs +592 -133
  40. package/dist/index.d.mts +54 -1
  41. package/dist/index.d.ts +54 -1
  42. package/dist/index.js +8 -3
  43. package/dist/index.mjs +8 -3
  44. package/dist/install-integrity.json +8 -0
  45. package/dist/plays/bundle-play-file.mjs +1 -1
  46. package/dist/release.d.mts +1 -1
  47. package/dist/release.d.ts +1 -1
  48. package/dist/release.js +1 -1
  49. package/dist/release.mjs +1 -1
  50. package/package.json +1 -1
@@ -34,11 +34,24 @@ const RECOVERY_FAILURE_RETRY_MAX_MS = 60_000;
34
34
  export const NOTIFY_UNAVAILABLE_SAFETY_WAKE_MS = 1_000;
35
35
  const MAX_TIMEOUT_MS = 2_147_483_647;
36
36
  /**
37
- * Two short claim/deliver waves match each projector's bounded scheduler pool.
38
- * The claim query uses SKIP LOCKED, so the wave can advance separate runs
39
- * without splitting the ordered lifecycle of any one run.
37
+ * One projector can keep this many independent Run Ledger deliveries in
38
+ * flight after it has found durable work. Each delivery owns only short
39
+ * Postgres checkouts: claim, token-context read, and acknowledgement. It does
40
+ * not hold a scheduler connection across either gateway HTTP request. This is
41
+ * therefore a network/projection concurrency budget, deliberately independent
42
+ * of the projector's much smaller Postgres pool.
43
+ *
44
+ * A single successful probe starts a bounded wave; an idle projector still
45
+ * performs only one indexed head read per safety wake. `SKIP LOCKED` leases
46
+ * distinct runs, retaining strict causal ordering within every run.
40
47
  */
41
- export const RUN_LEDGER_OUTBOX_RECOVERY_MAX_IN_FLIGHT = 2;
48
+ // `run.created` has one event per admitted Run, so a burst must project
49
+ // independently rather than wait for execution. Each Fly delivery first asks
50
+ // the launch-pinned Vercel deployment for a lease-fenced executor token,
51
+ // though. Keep that remote fan-out inside the Vercel control-plane budget:
52
+ // the Fly checkout is short, but 128 simultaneous token requests previously
53
+ // saturated the shared serverless scheduler pool before projection began.
54
+ export const RUN_LEDGER_OUTBOX_RECOVERY_MAX_IN_FLIGHT = 32;
42
55
 
43
56
  type RecoveryTimer = ReturnType<typeof setTimeout>;
44
57
 
@@ -226,12 +239,33 @@ export function startRunLedgerOutboxRecovery(
226
239
  timer.unref?.();
227
240
  };
228
241
 
229
- const drain = async (_reason: string): Promise<void> => {
242
+ const drain = async (reason: string): Promise<void> => {
230
243
  while (requested && !closed) {
231
244
  requested = false;
232
- const settled = await Promise.allSettled(
233
- Array.from({ length: parallelism }, () => deps.dispatchOnce()),
234
- );
245
+ // Probe first so an idle projector issues one small indexed read per
246
+ // wake rather than `parallelism` identical empty scans. Once the probe
247
+ // claims a row, the remaining slots can safely lease unrelated runs;
248
+ // each dispatcher releases its database client before gateway I/O.
249
+ let first: PostgresSchedulerOutboxProjectionResult;
250
+ try {
251
+ first = await deps.dispatchOnce();
252
+ } catch (error) {
253
+ scheduleFailureRetry('dispatch', error);
254
+ return;
255
+ }
256
+ const settled = [
257
+ {
258
+ status: 'fulfilled' as const,
259
+ value: first,
260
+ },
261
+ ...(first.attempted === 0
262
+ ? []
263
+ : await Promise.allSettled(
264
+ Array.from({ length: parallelism - 1 }, () =>
265
+ deps.dispatchOnce(),
266
+ ),
267
+ )),
268
+ ];
235
269
  const rejected = settled.find(
236
270
  (result): result is PromiseRejectedResult =>
237
271
  result.status === 'rejected',
@@ -251,6 +285,18 @@ export function startRunLedgerOutboxRecovery(
251
285
  (total, result) => total + result.retrying,
252
286
  0,
253
287
  );
288
+ const attempted = results.reduce(
289
+ (total, result) => total + result.attempted,
290
+ 0,
291
+ );
292
+ const projected = results.reduce(
293
+ (total, result) => total + result.projected,
294
+ 0,
295
+ );
296
+ const blocked = results.reduce(
297
+ (total, result) => total + result.failed,
298
+ 0,
299
+ );
254
300
 
255
301
  if (retrying > 0) {
256
302
  // The failed row owns its own durable retry clock. Do NOT stop this
@@ -263,11 +309,25 @@ export function startRunLedgerOutboxRecovery(
263
309
  failureCount = 0;
264
310
  }
265
311
 
312
+ if (attempted > 0) {
313
+ // This is the durable queue's high-signal throughput event. It makes
314
+ // a delayed public Run observable as a projector/outbox problem,
315
+ // without emitting one noisy log line per normal database checkout.
316
+ logger.info('[run-ledger-outbox.recovery_wave]', {
317
+ reason,
318
+ parallelism,
319
+ attempted,
320
+ projected,
321
+ retrying,
322
+ blocked,
323
+ });
324
+ }
325
+
266
326
  // Continue until no event was claimable. A transient failure schedules
267
327
  // its own durable retry, so the next pass naturally skips that run while
268
328
  // moving unrelated runs forward. A permanent failure is settled once and
269
329
  // likewise cannot block another run's causal prefix.
270
- if (results.some((result) => result.attempted > 0)) {
330
+ if (attempted > 0) {
271
331
  requested = true;
272
332
  continue;
273
333
  }
@@ -324,10 +384,11 @@ export function startRunLedgerOutboxRecoveryController(input: {
324
384
  { tokenAuthority: 'gateway', limit: 8 },
325
385
  schedulerOptions,
326
386
  ),
327
- parallelism: Math.min(
328
- RUN_LEDGER_OUTBOX_RECOVERY_MAX_IN_FLIGHT,
329
- Math.max(1, input.poolOptions.maxClients ?? 1),
330
- ),
387
+ // `dispatchRunLedgerOutboxToLedgerBatchOnceWithClientScope` releases each
388
+ // short scheduler checkout before it calls the Vercel gateway. Keep the
389
+ // durable projection wave large enough for a webhook burst without
390
+ // expanding the process' Postgres connection budget.
391
+ parallelism: RUN_LEDGER_OUTBOX_RECOVERY_MAX_IN_FLIGHT,
331
392
  readNextDueAt: () =>
332
393
  withRuntimeSchedulerClient(input.poolOptions, (client) =>
333
394
  readPostgresSchedulerOutboxNextDueAt(client, schedulerOptions),
@@ -9,6 +9,11 @@ import {
9
9
  type QueueHealth,
10
10
  type QueueObservation,
11
11
  } from '../../observability/queue-health';
12
+ import {
13
+ dlqInventoryFromQueueObservation,
14
+ getDlqTransitionPaths,
15
+ type DlqInventory,
16
+ } from '../../observability/dlq';
12
17
 
13
18
  /**
14
19
  * Bounded, read-only health projection for the durable runtime queues.
@@ -59,13 +64,15 @@ export type RuntimeQueueFlow = {
59
64
 
60
65
  export type RuntimeQueueHealthProjection = {
61
66
  id: string;
62
- kind: 'absurd' | 'run_ledger_outbox' | 'sandbox_cleanup';
67
+ kind: 'absurd' | 'run_ledger_outbox' | 'compute_billing' | 'sandbox_cleanup';
63
68
  source: 'absurd' | 'runtime_postgres';
64
69
  observedAt: string;
65
70
  status: RuntimeQueueHealthStatus;
66
71
  statusReason: string;
67
72
  /** Shared scheduler-neutral contract consumed by transports and dashboards. */
68
73
  observation: QueueObservation;
74
+ /** Shared DLQ contract consumed by operators and the admin CLI. */
75
+ dlq: DlqInventory;
69
76
  health: QueueHealth;
70
77
  depth: RuntimeQueueDepth;
71
78
  oldestAgeMs: RuntimeQueueOldestAge;
@@ -86,6 +93,8 @@ export type RuntimeQueueHealthProjection = {
86
93
  export type RuntimeQueueHealthSnapshot = {
87
94
  observedAt: string;
88
95
  staleAfterMs: number;
96
+ /** True when the control-plane lane inventory was capped before sampling. */
97
+ inventoryTruncated?: boolean;
89
98
  queues: RuntimeQueueHealthProjection[];
90
99
  summary: {
91
100
  green: number;
@@ -96,6 +105,8 @@ export type RuntimeQueueHealthSnapshot = {
96
105
  };
97
106
 
98
107
  export type RuntimeQueueHealthOptions = {
108
+ /** Explicit deployment label carried into the shared DLQ inventory. */
109
+ environment?: string;
99
110
  schema?: string;
100
111
  now?: Date;
101
112
  staleAfterMs?: number;
@@ -103,6 +114,8 @@ export type RuntimeQueueHealthOptions = {
103
114
  failAgeMs?: number;
104
115
  /** Restrict Absurd rows to these queue names. Registry values are validated. */
105
116
  absurdQueueNames?: string[];
117
+ /** The caller could not enumerate every live lane within the bounded cap. */
118
+ absurdQueueInventoryTruncated?: boolean;
106
119
  maxAbsurdQueues?: number;
107
120
  };
108
121
 
@@ -134,6 +147,8 @@ type CountRow = {
134
147
  in_flight_truncated?: boolean;
135
148
  blocked_truncated?: boolean;
136
149
  dead_lettered_truncated?: boolean;
150
+ /** False for durable worksets that expose blocked quarantine, not a DLQ. */
151
+ dead_lettered_available?: boolean;
137
152
  flow_available?: boolean;
138
153
  flow_unavailable_reason?: string | null;
139
154
  };
@@ -164,7 +179,9 @@ function numberValue(value: string | number | null | undefined): number {
164
179
  function ageValue(value: string | number | null | undefined): number | null {
165
180
  if (value === null || value === undefined) return null;
166
181
  const parsed = numberValue(value);
167
- return parsed > 0 ? Math.floor(parsed) : null;
182
+ // Zero is a real age for freshly admitted work. Keep it distinct from
183
+ // NULL, which means that the bucket is empty and should render as N/A.
184
+ return Math.floor(parsed);
168
185
  }
169
186
 
170
187
  function optionsWithDefaults(
@@ -246,6 +263,7 @@ function projectionFromRow(input: {
246
263
  source: RuntimeQueueHealthProjection['source'];
247
264
  row: CountRow;
248
265
  observedAt: Date;
266
+ environment?: string;
249
267
  options: RuntimeQueueHealthThresholds;
250
268
  }): RuntimeQueueHealthProjection {
251
269
  const depth: RuntimeQueueDepth = {
@@ -262,6 +280,7 @@ function projectionFromRow(input: {
262
280
  blocked: ageValue(input.row.oldest_blocked_ms),
263
281
  deadLettered: ageValue(input.row.oldest_dead_lettered_ms),
264
282
  };
283
+ const deadLetteredAvailable = input.row.dead_lettered_available !== false;
265
284
  const lifecycle = {
266
285
  started: numberValue(input.row.started),
267
286
  completed: numberValue(input.row.completed),
@@ -299,7 +318,9 @@ function projectionFromRow(input: {
299
318
  ? 'Run admitted Play runtime tasks to terminal state'
300
319
  : input.kind === 'run_ledger_outbox'
301
320
  ? 'Deliver causal Run Ledger events to control-plane projections'
302
- : 'Complete durable sandbox deletion obligations',
321
+ : input.kind === 'compute_billing'
322
+ ? 'Settle durable compute billing obligations after a runtime run ends'
323
+ : 'Complete durable sandbox deletion obligations',
303
324
  unit: 'items',
304
325
  kind: input.kind === 'absurd' ? 'fifo' : 'leased_workset',
305
326
  ageSlo: {
@@ -328,20 +349,21 @@ function projectionFromRow(input: {
328
349
  delayed: measureDepth(depth.delayed, input.row.delayed_truncated),
329
350
  inFlight: measureDepth(depth.inFlight, input.row.in_flight_truncated),
330
351
  blocked: measureDepth(depth.blocked, input.row.blocked_truncated),
331
- deadLettered: measureDepth(
332
- depth.deadLettered,
333
- input.row.dead_lettered_truncated,
334
- ),
352
+ deadLettered: deadLetteredAvailable
353
+ ? measureDepth(depth.deadLettered, input.row.dead_lettered_truncated)
354
+ : notApplicable<number>('This workset has blocked quarantine, not a DLQ.'),
335
355
  },
336
356
  oldestAgeMs: {
337
357
  ready: measureAge(oldestAgeMs.ready, input.row.ready_truncated),
338
358
  delayed: measureAge(oldestAgeMs.delayed, input.row.delayed_truncated),
339
359
  inFlight: measureAge(oldestAgeMs.inFlight, input.row.in_flight_truncated),
340
360
  blocked: measureAge(oldestAgeMs.blocked, input.row.blocked_truncated),
341
- deadLettered: measureAge(
342
- oldestAgeMs.deadLettered,
343
- input.row.dead_lettered_truncated,
344
- ),
361
+ deadLettered: deadLetteredAvailable
362
+ ? measureAge(
363
+ oldestAgeMs.deadLettered,
364
+ input.row.dead_lettered_truncated,
365
+ )
366
+ : notApplicable<number>('This workset has blocked quarantine, not a DLQ.'),
345
367
  },
346
368
  flow: {
347
369
  admitted: flowAvailable
@@ -369,6 +391,20 @@ function projectionFromRow(input: {
369
391
  observation,
370
392
  input.options.now.getTime(),
371
393
  );
394
+ const dlq = dlqInventoryFromQueueObservation({
395
+ queueId: input.id,
396
+ environment:
397
+ input.environment?.trim() ||
398
+ process.env.DEEPLINE_ENV?.trim() ||
399
+ 'unknown',
400
+ source: input.source,
401
+ observation,
402
+ paths: getDlqTransitionPaths({
403
+ queueId: input.id,
404
+ owner: 'runtime',
405
+ source: input.source,
406
+ }),
407
+ });
372
408
  return {
373
409
  id: input.id,
374
410
  kind: input.kind,
@@ -377,6 +413,7 @@ function projectionFromRow(input: {
377
413
  status: derived.status,
378
414
  statusReason: derived.statusReason,
379
415
  observation,
416
+ dlq,
380
417
  health,
381
418
  depth,
382
419
  oldestAgeMs,
@@ -465,6 +502,7 @@ function absurdProjectionQuery(queueTable: string): string {
465
502
  0::bigint AS completed,
466
503
  0::bigint AS failed,
467
504
  0::bigint AS retried,
505
+ true AS dead_lettered_available,
468
506
  false AS flow_available,
469
507
  'Absurd run history has no bounded exact rolling flow projection'::text AS flow_unavailable_reason
470
508
  FROM sampled
@@ -474,114 +512,216 @@ function absurdProjectionQuery(queueTable: string): string {
474
512
  function outboxProjectionQuery(schema: string): string {
475
513
  const table = `${schema}."outbox"`;
476
514
  return `
477
- WITH pending_sample AS (
478
- SELECT
479
- CASE
480
- WHEN lease_owner IS NULL AND coalesce(next_attempt_at, created_at) <= $1::timestamptz THEN 'ready'
481
- WHEN lease_owner IS NULL THEN 'delayed'
482
- WHEN lease_expires_at > $1::timestamptz THEN 'in_flight'
483
- ELSE 'blocked'
484
- END AS bucket,
485
- CASE
486
- WHEN lease_owner IS NULL AND coalesce(next_attempt_at, created_at) > $1::timestamptz
487
- THEN coalesce(next_attempt_at, created_at)
488
- WHEN lease_owner IS NULL THEN created_at
489
- WHEN lease_expires_at > $1::timestamptz
490
- THEN coalesce(delivery_batch_started_at, created_at)
491
- ELSE coalesce(lease_expires_at, created_at)
492
- END AS age_at
515
+ WITH ready AS (
516
+ SELECT coalesce(next_attempt_at, created_at) AS age_at
493
517
  FROM ${table}
494
- WHERE delivered_at IS NULL AND failed_at IS NULL
518
+ WHERE delivered_at IS NULL
519
+ AND failed_at IS NULL
520
+ AND lease_owner IS NULL
521
+ AND coalesce(next_attempt_at, created_at) <= $1::timestamptz
495
522
  ORDER BY coalesce(next_attempt_at, created_at), created_at, event_id
496
523
  LIMIT $2::int
497
- ), dead_sample AS (
498
- SELECT 'dead_lettered'::text AS bucket, coalesce(failed_at, created_at) AS age_at
524
+ ), delayed AS (
525
+ SELECT coalesce(next_attempt_at, created_at) AS age_at
526
+ FROM ${table}
527
+ WHERE delivered_at IS NULL
528
+ AND failed_at IS NULL
529
+ AND lease_owner IS NULL
530
+ AND coalesce(next_attempt_at, created_at) > $1::timestamptz
531
+ ORDER BY coalesce(next_attempt_at, created_at), created_at, event_id
532
+ LIMIT $2::int
533
+ ), in_flight AS (
534
+ SELECT coalesce(delivery_batch_started_at, created_at) AS age_at
535
+ FROM ${table}
536
+ WHERE delivered_at IS NULL
537
+ AND failed_at IS NULL
538
+ AND lease_owner IS NOT NULL
539
+ AND lease_expires_at > $1::timestamptz
540
+ ORDER BY lease_expires_at, created_at, event_id
541
+ LIMIT $2::int
542
+ ), blocked AS (
543
+ SELECT coalesce(lease_expires_at, created_at) AS age_at
544
+ FROM ${table}
545
+ WHERE delivered_at IS NULL
546
+ AND failed_at IS NULL
547
+ AND lease_owner IS NOT NULL
548
+ AND (lease_expires_at IS NULL OR lease_expires_at <= $1::timestamptz)
549
+ ORDER BY coalesce(lease_expires_at, created_at), created_at, event_id
550
+ LIMIT $2::int
551
+ ), dead_lettered AS (
552
+ SELECT coalesce(failed_at, created_at) AS age_at
499
553
  FROM ${table}
500
554
  WHERE delivered_at IS NULL AND failed_at IS NOT NULL
501
555
  ORDER BY failed_at, event_id
502
556
  LIMIT $2::int
503
- ), sampled AS (
504
- SELECT * FROM pending_sample
505
- UNION ALL
506
- SELECT * FROM dead_sample
507
557
  )
508
558
  SELECT
509
- least(count(*) FILTER (WHERE bucket = 'ready'), $3) AS ready,
510
- least(count(*) FILTER (WHERE bucket = 'delayed'), $3) AS delayed,
511
- least(count(*) FILTER (WHERE bucket = 'in_flight'), $3) AS in_flight,
512
- least(count(*) FILTER (WHERE bucket = 'blocked'), $3) AS blocked,
513
- least(count(*) FILTER (WHERE bucket = 'dead_lettered'), $3) AS dead_lettered,
514
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'ready') AS oldest_ready_ms,
515
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'delayed') AS oldest_delayed_ms,
516
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'in_flight') AS oldest_in_flight_ms,
517
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'blocked') AS oldest_blocked_ms,
518
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'dead_lettered') AS oldest_dead_lettered_ms,
519
- count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS ready_truncated,
520
- count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS delayed_truncated,
521
- count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS in_flight_truncated,
522
- count(*) FILTER (WHERE bucket <> 'dead_lettered') > $3 AS blocked_truncated,
523
- count(*) FILTER (WHERE bucket = 'dead_lettered') > $3 AS dead_lettered_truncated,
559
+ least((SELECT count(*) FROM ready), $3) AS ready,
560
+ least((SELECT count(*) FROM delayed), $3) AS delayed,
561
+ least((SELECT count(*) FROM in_flight), $3) AS in_flight,
562
+ least((SELECT count(*) FROM blocked), $3) AS blocked,
563
+ least((SELECT count(*) FROM dead_lettered), $3) AS dead_lettered,
564
+ (SELECT max(${intervalExpr('age_at')}) FROM ready) AS oldest_ready_ms,
565
+ (SELECT max(${intervalExpr('age_at')}) FROM delayed) AS oldest_delayed_ms,
566
+ (SELECT max(${intervalExpr('age_at')}) FROM in_flight) AS oldest_in_flight_ms,
567
+ (SELECT max(${intervalExpr('age_at')}) FROM blocked) AS oldest_blocked_ms,
568
+ (SELECT max(${intervalExpr('age_at')}) FROM dead_lettered) AS oldest_dead_lettered_ms,
569
+ (SELECT count(*) FROM ready) > $3 AS ready_truncated,
570
+ (SELECT count(*) FROM delayed) > $3 AS delayed_truncated,
571
+ (SELECT count(*) FROM in_flight) > $3 AS in_flight_truncated,
572
+ (SELECT count(*) FROM blocked) > $3 AS blocked_truncated,
573
+ (SELECT count(*) FROM dead_lettered) > $3 AS dead_lettered_truncated,
524
574
  0::bigint AS admitted,
525
575
  0::bigint AS started,
526
576
  0::bigint AS completed,
527
577
  0::bigint AS failed,
528
578
  0::bigint AS retried,
579
+ true AS dead_lettered_available,
529
580
  false AS flow_available,
530
581
  'Outbox health has no bounded exact rolling flow projection'::text AS flow_unavailable_reason
531
- FROM sampled
532
582
  `;
533
583
  }
534
584
 
535
585
  function cleanupProjectionQuery(schema: string): string {
536
586
  const table = `${schema}."sandbox_cleanup_jobs_v2"`;
537
587
  return `
538
- WITH sampled AS (
588
+ WITH ready AS (
589
+ SELECT created_at AS age_at
590
+ FROM ${table}
591
+ WHERE status <> 'deleted'
592
+ AND blocked_at IS NULL
593
+ AND lease_owner IS NULL
594
+ AND status <> 'deleting'
595
+ AND available_at <= $1::timestamptz
596
+ ORDER BY available_at, created_at, provider, sandbox_id
597
+ LIMIT $2::int
598
+ ), delayed AS (
599
+ SELECT available_at AS age_at
600
+ FROM ${table}
601
+ WHERE status <> 'deleted'
602
+ AND blocked_at IS NULL
603
+ AND lease_owner IS NULL
604
+ AND status <> 'deleting'
605
+ AND available_at > $1::timestamptz
606
+ ORDER BY available_at, created_at, provider, sandbox_id
607
+ LIMIT $2::int
608
+ ), in_flight AS (
609
+ SELECT coalesce(updated_at, created_at) AS age_at
610
+ FROM ${table}
611
+ WHERE status <> 'deleted'
612
+ AND blocked_at IS NULL
613
+ AND lease_owner IS NOT NULL
614
+ AND lease_expires_at > $1::timestamptz
615
+ ORDER BY lease_expires_at, created_at, provider, sandbox_id
616
+ LIMIT $2::int
617
+ ), blocked AS (
539
618
  SELECT
540
- CASE
541
- WHEN blocked_at IS NOT NULL THEN 'blocked'
542
- WHEN lease_owner IS NOT NULL AND lease_expires_at > $1::timestamptz THEN 'in_flight'
543
- WHEN lease_owner IS NOT NULL OR status = 'deleting' THEN 'blocked'
544
- WHEN available_at > $1::timestamptz THEN 'delayed'
545
- ELSE 'ready'
546
- END AS bucket,
547
619
  CASE
548
620
  WHEN blocked_at IS NOT NULL THEN blocked_at
549
- WHEN lease_owner IS NOT NULL AND lease_expires_at > $1::timestamptz
550
- THEN coalesce(updated_at, created_at)
551
- WHEN lease_owner IS NOT NULL OR status = 'deleting'
552
- THEN coalesce(lease_expires_at, created_at)
553
- WHEN available_at > $1::timestamptz THEN available_at
554
- ELSE created_at
621
+ ELSE coalesce(lease_expires_at, created_at)
555
622
  END AS age_at
556
623
  FROM ${table}
557
624
  WHERE status <> 'deleted'
558
- ORDER BY available_at, created_at, provider, sandbox_id
625
+ AND (
626
+ blocked_at IS NOT NULL
627
+ OR lease_owner IS NOT NULL AND (lease_expires_at IS NULL OR lease_expires_at <= $1::timestamptz)
628
+ OR lease_owner IS NULL AND status = 'deleting'
629
+ )
630
+ ORDER BY coalesce(blocked_at, lease_expires_at, created_at), created_at, provider, sandbox_id
559
631
  LIMIT $2::int
560
632
  )
561
633
  SELECT
562
- least(count(*) FILTER (WHERE bucket = 'ready'), $3) AS ready,
563
- least(count(*) FILTER (WHERE bucket = 'delayed'), $3) AS delayed,
564
- least(count(*) FILTER (WHERE bucket = 'in_flight'), $3) AS in_flight,
565
- least(count(*) FILTER (WHERE bucket = 'blocked'), $3) AS blocked,
634
+ least((SELECT count(*) FROM ready), $3) AS ready,
635
+ least((SELECT count(*) FROM delayed), $3) AS delayed,
636
+ least((SELECT count(*) FROM in_flight), $3) AS in_flight,
637
+ least((SELECT count(*) FROM blocked), $3) AS blocked,
566
638
  0::bigint AS dead_lettered,
567
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'ready') AS oldest_ready_ms,
568
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'delayed') AS oldest_delayed_ms,
569
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'in_flight') AS oldest_in_flight_ms,
570
- max(${intervalExpr('age_at')}) FILTER (WHERE bucket = 'blocked') AS oldest_blocked_ms,
639
+ (SELECT max(${intervalExpr('age_at')}) FROM ready) AS oldest_ready_ms,
640
+ (SELECT max(${intervalExpr('age_at')}) FROM delayed) AS oldest_delayed_ms,
641
+ (SELECT max(${intervalExpr('age_at')}) FROM in_flight) AS oldest_in_flight_ms,
642
+ (SELECT max(${intervalExpr('age_at')}) FROM blocked) AS oldest_blocked_ms,
571
643
  NULL::bigint AS oldest_dead_lettered_ms,
572
- count(*) > $3 AS ready_truncated,
573
- count(*) > $3 AS delayed_truncated,
574
- count(*) > $3 AS in_flight_truncated,
575
- count(*) > $3 AS blocked_truncated,
644
+ (SELECT count(*) FROM ready) > $3 AS ready_truncated,
645
+ (SELECT count(*) FROM delayed) > $3 AS delayed_truncated,
646
+ (SELECT count(*) FROM in_flight) > $3 AS in_flight_truncated,
647
+ (SELECT count(*) FROM blocked) > $3 AS blocked_truncated,
576
648
  false AS dead_lettered_truncated,
577
649
  0::bigint AS admitted,
578
650
  0::bigint AS started,
579
651
  0::bigint AS completed,
580
652
  0::bigint AS failed,
581
653
  0::bigint AS retried,
654
+ false AS dead_lettered_available,
582
655
  false AS flow_available,
583
656
  'Sandbox cleanup health has no bounded exact rolling flow projection'::text AS flow_unavailable_reason
584
- FROM sampled
657
+ `;
658
+ }
659
+
660
+ function computeBillingProjectionQuery(schema: string): string {
661
+ const table = `${schema}."compute_billing_jobs_v2"`;
662
+ return `
663
+ WITH ready AS (
664
+ SELECT created_at AS age_at
665
+ FROM ${table}
666
+ WHERE status <> 'settled'
667
+ AND blocked_at IS NULL
668
+ AND lease_owner IS NULL
669
+ AND available_at <= $1::timestamptz
670
+ ORDER BY available_at, created_at, run_id
671
+ LIMIT $2::int
672
+ ), delayed AS (
673
+ SELECT available_at AS age_at
674
+ FROM ${table}
675
+ WHERE status <> 'settled'
676
+ AND blocked_at IS NULL
677
+ AND lease_owner IS NULL
678
+ AND available_at > $1::timestamptz
679
+ ORDER BY available_at, created_at, run_id
680
+ LIMIT $2::int
681
+ ), in_flight AS (
682
+ SELECT coalesce(updated_at, created_at) AS age_at
683
+ FROM ${table}
684
+ WHERE status <> 'settled'
685
+ AND blocked_at IS NULL
686
+ AND lease_owner IS NOT NULL
687
+ AND lease_expires_at > $1::timestamptz
688
+ ORDER BY lease_expires_at, created_at, run_id
689
+ LIMIT $2::int
690
+ ), blocked AS (
691
+ SELECT coalesce(blocked_at, lease_expires_at, created_at) AS age_at
692
+ FROM ${table}
693
+ WHERE status <> 'settled'
694
+ AND (
695
+ blocked_at IS NOT NULL
696
+ OR lease_owner IS NOT NULL AND (lease_expires_at IS NULL OR lease_expires_at <= $1::timestamptz)
697
+ )
698
+ ORDER BY coalesce(blocked_at, lease_expires_at, created_at), created_at, run_id
699
+ LIMIT $2::int
700
+ )
701
+ SELECT
702
+ least((SELECT count(*) FROM ready), $3) AS ready,
703
+ least((SELECT count(*) FROM delayed), $3) AS delayed,
704
+ least((SELECT count(*) FROM in_flight), $3) AS in_flight,
705
+ least((SELECT count(*) FROM blocked), $3) AS blocked,
706
+ 0::bigint AS dead_lettered,
707
+ (SELECT max(${intervalExpr('age_at')}) FROM ready) AS oldest_ready_ms,
708
+ (SELECT max(${intervalExpr('age_at')}) FROM delayed) AS oldest_delayed_ms,
709
+ (SELECT max(${intervalExpr('age_at')}) FROM in_flight) AS oldest_in_flight_ms,
710
+ (SELECT max(${intervalExpr('age_at')}) FROM blocked) AS oldest_blocked_ms,
711
+ NULL::bigint AS oldest_dead_lettered_ms,
712
+ (SELECT count(*) FROM ready) > $3 AS ready_truncated,
713
+ (SELECT count(*) FROM delayed) > $3 AS delayed_truncated,
714
+ (SELECT count(*) FROM in_flight) > $3 AS in_flight_truncated,
715
+ (SELECT count(*) FROM blocked) > $3 AS blocked_truncated,
716
+ false AS dead_lettered_truncated,
717
+ 0::bigint AS admitted,
718
+ 0::bigint AS started,
719
+ 0::bigint AS completed,
720
+ 0::bigint AS failed,
721
+ 0::bigint AS retried,
722
+ false AS dead_lettered_available,
723
+ false AS flow_available,
724
+ 'Compute billing health has no bounded exact rolling flow projection'::text AS flow_unavailable_reason
585
725
  `;
586
726
  }
587
727
 
@@ -649,9 +789,13 @@ export async function readRuntimeQueueHealthFromClient(
649
789
  )
650
790
  : await client.query<{ queue_name: string }>(
651
791
  `SELECT queue_name FROM absurd.queues ORDER BY queue_name LIMIT $1::int`,
652
- [maxQueues],
792
+ [maxQueues + 1],
653
793
  );
654
- const queueNames = registry.rows.map((row) => row.queue_name);
794
+ const registryTruncated =
795
+ requestedNames === undefined && registry.rows.length > maxQueues;
796
+ const queueNames = registry.rows
797
+ .slice(0, maxQueues)
798
+ .map((row) => row.queue_name);
655
799
  if (requestedNames) {
656
800
  const found = new Set(queueNames);
657
801
  const missing = requestedNames.filter((name) => !found.has(name));
@@ -674,16 +818,22 @@ export async function readRuntimeQueueHealthFromClient(
674
818
  source: 'absurd',
675
819
  row: rowFromResult(result),
676
820
  observedAt: now,
821
+ environment: input.environment,
677
822
  options,
678
823
  }),
679
824
  );
680
825
  }
681
- const [outbox, cleanup] = await Promise.all([
826
+ const [outbox, computeBilling, cleanup] = await Promise.all([
682
827
  client.query<CountRow>(outboxProjectionQuery(schema), [
683
828
  now,
684
829
  sampleLimit + 1,
685
830
  sampleLimit,
686
831
  ]),
832
+ client.query<CountRow>(computeBillingProjectionQuery(schema), [
833
+ now,
834
+ sampleLimit + 1,
835
+ sampleLimit,
836
+ ]),
687
837
  client.query<CountRow>(cleanupProjectionQuery(schema), [
688
838
  now,
689
839
  sampleLimit + 1,
@@ -697,6 +847,16 @@ export async function readRuntimeQueueHealthFromClient(
697
847
  source: 'runtime_postgres',
698
848
  row: rowFromResult(outbox),
699
849
  observedAt: now,
850
+ environment: input.environment,
851
+ options,
852
+ }),
853
+ projectionFromRow({
854
+ id: 'runtime:compute-billing-jobs-v2',
855
+ kind: 'compute_billing',
856
+ source: 'runtime_postgres',
857
+ row: rowFromResult(computeBilling),
858
+ observedAt: now,
859
+ environment: input.environment,
700
860
  options,
701
861
  }),
702
862
  projectionFromRow({
@@ -705,6 +865,7 @@ export async function readRuntimeQueueHealthFromClient(
705
865
  source: 'runtime_postgres',
706
866
  row: rowFromResult(cleanup),
707
867
  observedAt: now,
868
+ environment: input.environment,
708
869
  options,
709
870
  }),
710
871
  );
@@ -717,6 +878,9 @@ export async function readRuntimeQueueHealthFromClient(
717
878
  return {
718
879
  observedAt: now.toISOString(),
719
880
  staleAfterMs: options.staleAfterMs,
881
+ ...(input.absurdQueueInventoryTruncated || registryTruncated
882
+ ? { inventoryTruncated: true }
883
+ : {}),
720
884
  queues: projections,
721
885
  summary,
722
886
  };