deepline 0.3.114 → 0.3.116

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.
@@ -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
  };
@@ -328,8 +328,8 @@ function previewDetailFor(substep: PlayStaticSubstep): string | null {
328
328
  ? `${substep.steps.length} fallbacks`
329
329
  : 'first match wins';
330
330
  case 'step_suite': {
331
- const count = stepSuiteToolIds(substep).length;
332
- return count > 0 ? `${count} providers, first match wins` : null;
331
+ const count = effectiveStepCount(substep);
332
+ return `${count} step${count === 1 ? '' : 's'}`;
333
333
  }
334
334
  case 'control_flow': {
335
335
  const count = substep.steps.reduce(
package/dist/cli/index.js CHANGED
@@ -1298,7 +1298,7 @@ var SDK_RELEASE = {
1298
1298
  // getters keep their established compatibility behavior.
1299
1299
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1300
1300
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
1301
- version: "0.3.114",
1301
+ version: "0.3.116",
1302
1302
  updateSummary: "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.",
1303
1303
  packageCapabilities: {
1304
1304
  updatePreferences: 1
@@ -1271,7 +1271,7 @@ var SDK_RELEASE = {
1271
1271
  // getters keep their established compatibility behavior.
1272
1272
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1273
1273
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
1274
- version: "0.3.114",
1274
+ version: "0.3.116",
1275
1275
  updateSummary: "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.",
1276
1276
  packageCapabilities: {
1277
1277
  updatePreferences: 1
package/dist/index.js CHANGED
@@ -860,7 +860,7 @@ var SDK_RELEASE = {
860
860
  // getters keep their established compatibility behavior.
861
861
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
862
862
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
863
- version: "0.3.114",
863
+ version: "0.3.116",
864
864
  updateSummary: "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.",
865
865
  packageCapabilities: {
866
866
  updatePreferences: 1
package/dist/index.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.114",
771
+ version: "0.3.116",
772
772
  updateSummary: "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.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -88,6 +88,7 @@
88
88
  "dist/bundling-sources/shared_libs/monitors/tsconfig.json",
89
89
  "dist/bundling-sources/shared_libs/monitors/validation.ts",
90
90
  "dist/bundling-sources/shared_libs/observability/build-context.ts",
91
+ "dist/bundling-sources/shared_libs/observability/dlq.ts",
91
92
  "dist/bundling-sources/shared_libs/observability/log-metadata.ts",
92
93
  "dist/bundling-sources/shared_libs/observability/node-tracing.ts",
93
94
  "dist/bundling-sources/shared_libs/observability/queue-health.ts",
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.114";
152
+ readonly version: "0.3.116";
153
153
  readonly updateSummary: "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.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.d.ts CHANGED
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.114";
152
+ readonly version: "0.3.116";
153
153
  readonly updateSummary: "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.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.js CHANGED
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
74
74
  // getters keep their established compatibility behavior.
75
75
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
76
76
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
77
- version: "0.3.114",
77
+ version: "0.3.116",
78
78
  updateSummary: "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.",
79
79
  packageCapabilities: {
80
80
  updatePreferences: 1
package/dist/release.mjs CHANGED
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
48
48
  // getters keep their established compatibility behavior.
49
49
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
50
50
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
51
- version: "0.3.114",
51
+ version: "0.3.116",
52
52
  updateSummary: "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.",
53
53
  packageCapabilities: {
54
54
  updatePreferences: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.114",
3
+ "version": "0.3.116",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",