deepline 0.3.141 → 0.3.142
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.
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +65 -2
- package/dist/bundling-sources/shared_libs/observability/dlq.ts +11 -0
- package/dist/bundling-sources/shared_libs/observability/queue-health.ts +111 -0
- package/dist/bundling-sources/shared_libs/play-runtime/bettercontact-batching.ts +34 -8
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +271 -44
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +25 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +132 -8
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +3 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +49 -54
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +14 -7
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +118 -29
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +183 -36
- package/dist/bundling-sources/shared_libs/product-notifications/events.ts +4 -1
- package/dist/cli/index.js +242 -12
- package/dist/cli/index.mjs +242 -12
- package/dist/index.d.mts +54 -2
- package/dist/index.d.ts +54 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type QueueDefinition,
|
|
9
9
|
type QueueHealth,
|
|
10
10
|
type QueueObservation,
|
|
11
|
+
type QueueRetirementCandidate,
|
|
11
12
|
} from '../../observability/queue-health';
|
|
12
13
|
import {
|
|
13
14
|
dlqInventoryFromQueueObservation,
|
|
@@ -73,6 +74,8 @@ export type RuntimeQueueHealthProjection = {
|
|
|
73
74
|
observation: QueueObservation;
|
|
74
75
|
/** Shared DLQ contract consumed by operators and the admin CLI. */
|
|
75
76
|
dlq: DlqInventory;
|
|
77
|
+
/** Internal-only bounded sample used to join operator dispositions. */
|
|
78
|
+
operatorRetirementCandidates?: QueueRetirementCandidate[];
|
|
76
79
|
health: QueueHealth;
|
|
77
80
|
depth: RuntimeQueueDepth;
|
|
78
81
|
oldestAgeMs: RuntimeQueueOldestAge;
|
|
@@ -117,6 +120,8 @@ export type RuntimeQueueHealthOptions = {
|
|
|
117
120
|
/** The caller could not enumerate every live lane within the bounded cap. */
|
|
118
121
|
absurdQueueInventoryTruncated?: boolean;
|
|
119
122
|
maxAbsurdQueues?: number;
|
|
123
|
+
/** Include bounded attention-row identities for an app-side disposition join. */
|
|
124
|
+
includeRetirementCandidates?: boolean;
|
|
120
125
|
};
|
|
121
126
|
|
|
122
127
|
export type RuntimeQueueHealthThresholds = Required<
|
|
@@ -151,6 +156,8 @@ type CountRow = {
|
|
|
151
156
|
dead_lettered_available?: boolean;
|
|
152
157
|
flow_available?: boolean;
|
|
153
158
|
flow_unavailable_reason?: string | null;
|
|
159
|
+
blocked_retirement_candidates?: unknown;
|
|
160
|
+
dead_lettered_retirement_candidates?: unknown;
|
|
154
161
|
};
|
|
155
162
|
|
|
156
163
|
function quoteIdentifier(value: string, label: string): string {
|
|
@@ -184,6 +191,59 @@ function ageValue(value: string | number | null | undefined): number | null {
|
|
|
184
191
|
return Math.floor(parsed);
|
|
185
192
|
}
|
|
186
193
|
|
|
194
|
+
function parseJsonArray(value: unknown): Record<string, unknown>[] {
|
|
195
|
+
let parsed = value;
|
|
196
|
+
if (typeof value === 'string') {
|
|
197
|
+
try {
|
|
198
|
+
parsed = JSON.parse(value) as unknown;
|
|
199
|
+
} catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return Array.isArray(parsed)
|
|
204
|
+
? parsed.filter(
|
|
205
|
+
(row): row is Record<string, unknown> =>
|
|
206
|
+
typeof row === 'object' && row !== null && !Array.isArray(row),
|
|
207
|
+
)
|
|
208
|
+
: [];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function operatorRetirementCandidates(
|
|
212
|
+
row: CountRow,
|
|
213
|
+
kind: RuntimeQueueHealthProjection['kind'],
|
|
214
|
+
): QueueRetirementCandidate[] {
|
|
215
|
+
const candidates: QueueRetirementCandidate[] = [];
|
|
216
|
+
const append = (
|
|
217
|
+
value: unknown,
|
|
218
|
+
bucket: QueueRetirementCandidate['bucket'],
|
|
219
|
+
) => {
|
|
220
|
+
for (const candidate of parseJsonArray(value)) {
|
|
221
|
+
let itemId: string | undefined;
|
|
222
|
+
if (kind === 'sandbox_cleanup') {
|
|
223
|
+
const provider = candidate.provider;
|
|
224
|
+
const sandboxId = candidate.sandboxId;
|
|
225
|
+
const routingDomainKey = candidate.routingDomainKey;
|
|
226
|
+
if (
|
|
227
|
+
typeof provider === 'string' &&
|
|
228
|
+
typeof sandboxId === 'string' &&
|
|
229
|
+
typeof routingDomainKey === 'string'
|
|
230
|
+
) {
|
|
231
|
+
itemId = encodeURIComponent(
|
|
232
|
+
JSON.stringify([provider, sandboxId, routingDomainKey]),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
} else if (typeof candidate.itemId === 'string') {
|
|
236
|
+
itemId = candidate.itemId;
|
|
237
|
+
}
|
|
238
|
+
const ageMs = numberValue(candidate.ageMs as string | number | undefined);
|
|
239
|
+
if (itemId !== undefined) candidates.push({ itemId, bucket, ageMs });
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
append(row.blocked_retirement_candidates, 'blocked');
|
|
243
|
+
append(row.dead_lettered_retirement_candidates, 'deadLettered');
|
|
244
|
+
return candidates;
|
|
245
|
+
}
|
|
246
|
+
|
|
187
247
|
function optionsWithDefaults(
|
|
188
248
|
input: RuntimeQueueHealthOptions = {},
|
|
189
249
|
): RuntimeQueueHealthThresholds {
|
|
@@ -265,6 +325,7 @@ function projectionFromRow(input: {
|
|
|
265
325
|
observedAt: Date;
|
|
266
326
|
environment?: string;
|
|
267
327
|
options: RuntimeQueueHealthThresholds;
|
|
328
|
+
includeRetirementCandidates?: boolean;
|
|
268
329
|
}): RuntimeQueueHealthProjection {
|
|
269
330
|
const depth: RuntimeQueueDepth = {
|
|
270
331
|
ready: numberValue(input.row.ready),
|
|
@@ -351,7 +412,9 @@ function projectionFromRow(input: {
|
|
|
351
412
|
blocked: measureDepth(depth.blocked, input.row.blocked_truncated),
|
|
352
413
|
deadLettered: deadLetteredAvailable
|
|
353
414
|
? measureDepth(depth.deadLettered, input.row.dead_lettered_truncated)
|
|
354
|
-
: notApplicable<number>(
|
|
415
|
+
: notApplicable<number>(
|
|
416
|
+
'This workset has blocked quarantine, not a DLQ.',
|
|
417
|
+
),
|
|
355
418
|
},
|
|
356
419
|
oldestAgeMs: {
|
|
357
420
|
ready: measureAge(oldestAgeMs.ready, input.row.ready_truncated),
|
|
@@ -363,7 +426,9 @@ function projectionFromRow(input: {
|
|
|
363
426
|
oldestAgeMs.deadLettered,
|
|
364
427
|
input.row.dead_lettered_truncated,
|
|
365
428
|
)
|
|
366
|
-
: notApplicable<number>(
|
|
429
|
+
: notApplicable<number>(
|
|
430
|
+
'This workset has blocked quarantine, not a DLQ.',
|
|
431
|
+
),
|
|
367
432
|
},
|
|
368
433
|
flow: {
|
|
369
434
|
admitted: flowAvailable
|
|
@@ -410,6 +475,14 @@ function projectionFromRow(input: {
|
|
|
410
475
|
kind: input.kind,
|
|
411
476
|
source: input.source,
|
|
412
477
|
observedAt: input.observedAt.toISOString(),
|
|
478
|
+
...(input.includeRetirementCandidates
|
|
479
|
+
? {
|
|
480
|
+
operatorRetirementCandidates: operatorRetirementCandidates(
|
|
481
|
+
input.row,
|
|
482
|
+
input.kind,
|
|
483
|
+
),
|
|
484
|
+
}
|
|
485
|
+
: {}),
|
|
413
486
|
status: derived.status,
|
|
414
487
|
statusReason: derived.statusReason,
|
|
415
488
|
observation,
|
|
@@ -438,36 +511,51 @@ function intervalExpr(column: string, nowParameter = '$1'): string {
|
|
|
438
511
|
return `GREATEST(0, floor(extract(epoch FROM (${nowParameter}::timestamptz - ${column})) * 1000))`;
|
|
439
512
|
}
|
|
440
513
|
|
|
441
|
-
function absurdProjectionQuery(
|
|
514
|
+
function absurdProjectionQuery(
|
|
515
|
+
queueTable: string,
|
|
516
|
+
includeRetirementCandidates: boolean,
|
|
517
|
+
candidateSampleLimit: number,
|
|
518
|
+
): string {
|
|
442
519
|
const table = `absurd.${quoteIdentifier(queueTable, 'queue table')}`;
|
|
520
|
+
const retirementColumns = includeRetirementCandidates
|
|
521
|
+
? `,
|
|
522
|
+
COALESCE((
|
|
523
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}))
|
|
524
|
+
FROM (SELECT item_id, age_at FROM sampled WHERE bucket = 'blocked' ORDER BY age_at ASC, item_id ASC LIMIT $4::int) candidates
|
|
525
|
+
), '[]'::json) AS blocked_retirement_candidates,
|
|
526
|
+
COALESCE((
|
|
527
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}))
|
|
528
|
+
FROM (SELECT item_id, age_at FROM sampled WHERE bucket = 'dead_lettered' ORDER BY age_at ASC, item_id ASC LIMIT $4::int) candidates
|
|
529
|
+
), '[]'::json) AS dead_lettered_retirement_candidates`
|
|
530
|
+
: '';
|
|
443
531
|
return `
|
|
444
532
|
WITH sampled AS (
|
|
445
|
-
(SELECT 'ready'::text AS bucket, available_at AS age_at
|
|
533
|
+
(SELECT 'ready'::text AS bucket, run_id::text AS item_id, available_at AS age_at
|
|
446
534
|
FROM ${table}
|
|
447
535
|
WHERE state = 'pending' AND available_at <= $1::timestamptz
|
|
448
536
|
ORDER BY available_at, run_id
|
|
449
537
|
LIMIT $2::int)
|
|
450
538
|
UNION ALL
|
|
451
|
-
(SELECT 'delayed'::text, available_at
|
|
539
|
+
(SELECT 'delayed'::text, run_id::text, available_at
|
|
452
540
|
FROM ${table}
|
|
453
541
|
WHERE state = 'sleeping'
|
|
454
542
|
ORDER BY available_at, run_id
|
|
455
543
|
LIMIT $2::int)
|
|
456
544
|
UNION ALL
|
|
457
|
-
(SELECT 'delayed'::text, available_at
|
|
545
|
+
(SELECT 'delayed'::text, run_id::text, available_at
|
|
458
546
|
FROM ${table}
|
|
459
547
|
WHERE state = 'pending' AND available_at > $1::timestamptz
|
|
460
548
|
ORDER BY available_at, run_id
|
|
461
549
|
LIMIT $2::int)
|
|
462
550
|
UNION ALL
|
|
463
|
-
(SELECT 'in_flight'::text, coalesce(started_at, created_at)
|
|
551
|
+
(SELECT 'in_flight'::text, run_id::text, coalesce(started_at, created_at)
|
|
464
552
|
FROM ${table}
|
|
465
553
|
WHERE state = 'running'
|
|
466
554
|
AND (claim_expires_at IS NULL OR claim_expires_at > $1::timestamptz)
|
|
467
555
|
ORDER BY available_at, run_id
|
|
468
556
|
LIMIT $2::int)
|
|
469
557
|
UNION ALL
|
|
470
|
-
(SELECT 'blocked'::text, coalesce(claim_expires_at, started_at, created_at)
|
|
558
|
+
(SELECT 'blocked'::text, run_id::text, coalesce(claim_expires_at, started_at, created_at)
|
|
471
559
|
FROM ${table}
|
|
472
560
|
WHERE state = 'running'
|
|
473
561
|
AND claim_expires_at IS NOT NULL
|
|
@@ -475,7 +563,7 @@ function absurdProjectionQuery(queueTable: string): string {
|
|
|
475
563
|
ORDER BY claim_expires_at, run_id
|
|
476
564
|
LIMIT $2::int)
|
|
477
565
|
UNION ALL
|
|
478
|
-
(SELECT 'dead_lettered'::text, coalesce(failed_at, created_at)
|
|
566
|
+
(SELECT 'dead_lettered'::text, run_id::text, coalesce(failed_at, created_at)
|
|
479
567
|
FROM ${table}
|
|
480
568
|
WHERE state = 'failed'
|
|
481
569
|
ORDER BY available_at, run_id
|
|
@@ -496,7 +584,7 @@ function absurdProjectionQuery(queueTable: string): string {
|
|
|
496
584
|
count(*) FILTER (WHERE bucket = 'delayed') > $3 AS delayed_truncated,
|
|
497
585
|
count(*) FILTER (WHERE bucket = 'in_flight') > $3 AS in_flight_truncated,
|
|
498
586
|
count(*) FILTER (WHERE bucket = 'blocked') > $3 AS blocked_truncated,
|
|
499
|
-
count(*) FILTER (WHERE bucket = 'dead_lettered') > $3 AS dead_lettered_truncated,
|
|
587
|
+
count(*) FILTER (WHERE bucket = 'dead_lettered') > $3 AS dead_lettered_truncated${retirementColumns},
|
|
500
588
|
0::bigint AS admitted,
|
|
501
589
|
0::bigint AS started,
|
|
502
590
|
0::bigint AS completed,
|
|
@@ -509,8 +597,22 @@ function absurdProjectionQuery(queueTable: string): string {
|
|
|
509
597
|
`;
|
|
510
598
|
}
|
|
511
599
|
|
|
512
|
-
function outboxProjectionQuery(
|
|
600
|
+
function outboxProjectionQuery(
|
|
601
|
+
schema: string,
|
|
602
|
+
includeRetirementCandidates: boolean,
|
|
603
|
+
): string {
|
|
513
604
|
const table = `${schema}."outbox"`;
|
|
605
|
+
const retirementColumns = includeRetirementCandidates
|
|
606
|
+
? `,
|
|
607
|
+
COALESCE((
|
|
608
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}))
|
|
609
|
+
FROM (SELECT item_id, age_at FROM blocked ORDER BY age_at ASC, item_id ASC LIMIT $3::int) candidates
|
|
610
|
+
), '[]'::json) AS blocked_retirement_candidates,
|
|
611
|
+
COALESCE((
|
|
612
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}))
|
|
613
|
+
FROM (SELECT item_id, age_at FROM dead_lettered ORDER BY age_at ASC, item_id ASC LIMIT $3::int) candidates
|
|
614
|
+
), '[]'::json) AS dead_lettered_retirement_candidates`
|
|
615
|
+
: '';
|
|
514
616
|
return `
|
|
515
617
|
WITH ready AS (
|
|
516
618
|
SELECT coalesce(next_attempt_at, created_at) AS age_at
|
|
@@ -540,7 +642,7 @@ function outboxProjectionQuery(schema: string): string {
|
|
|
540
642
|
ORDER BY lease_expires_at, created_at, event_id
|
|
541
643
|
LIMIT $2::int
|
|
542
644
|
), blocked AS (
|
|
543
|
-
SELECT coalesce(lease_expires_at, created_at) AS age_at
|
|
645
|
+
SELECT event_id::text AS item_id, coalesce(lease_expires_at, created_at) AS age_at
|
|
544
646
|
FROM ${table}
|
|
545
647
|
WHERE delivered_at IS NULL
|
|
546
648
|
AND failed_at IS NULL
|
|
@@ -549,7 +651,7 @@ function outboxProjectionQuery(schema: string): string {
|
|
|
549
651
|
ORDER BY coalesce(lease_expires_at, created_at), created_at, event_id
|
|
550
652
|
LIMIT $2::int
|
|
551
653
|
), dead_lettered AS (
|
|
552
|
-
SELECT coalesce(failed_at, created_at) AS age_at
|
|
654
|
+
SELECT event_id::text AS item_id, coalesce(failed_at, created_at) AS age_at
|
|
553
655
|
FROM ${table}
|
|
554
656
|
WHERE delivered_at IS NULL AND failed_at IS NOT NULL
|
|
555
657
|
ORDER BY failed_at, event_id
|
|
@@ -570,7 +672,7 @@ function outboxProjectionQuery(schema: string): string {
|
|
|
570
672
|
(SELECT count(*) FROM delayed) > $3 AS delayed_truncated,
|
|
571
673
|
(SELECT count(*) FROM in_flight) > $3 AS in_flight_truncated,
|
|
572
674
|
(SELECT count(*) FROM blocked) > $3 AS blocked_truncated,
|
|
573
|
-
(SELECT count(*) FROM dead_lettered) > $3 AS dead_lettered_truncated,
|
|
675
|
+
(SELECT count(*) FROM dead_lettered) > $3 AS dead_lettered_truncated${retirementColumns},
|
|
574
676
|
0::bigint AS admitted,
|
|
575
677
|
0::bigint AS started,
|
|
576
678
|
0::bigint AS completed,
|
|
@@ -582,8 +684,19 @@ function outboxProjectionQuery(schema: string): string {
|
|
|
582
684
|
`;
|
|
583
685
|
}
|
|
584
686
|
|
|
585
|
-
function cleanupProjectionQuery(
|
|
687
|
+
function cleanupProjectionQuery(
|
|
688
|
+
schema: string,
|
|
689
|
+
includeRetirementCandidates: boolean,
|
|
690
|
+
): string {
|
|
586
691
|
const table = `${schema}."sandbox_cleanup_jobs_v2"`;
|
|
692
|
+
const retirementColumns = includeRetirementCandidates
|
|
693
|
+
? `,
|
|
694
|
+
COALESCE((
|
|
695
|
+
SELECT json_agg(json_build_object('provider', provider, 'sandboxId', sandbox_id, 'routingDomainKey', routing_domain_key, 'ageMs', ${intervalExpr('age_at')}))
|
|
696
|
+
FROM (SELECT provider, sandbox_id, routing_domain_key, created_at, age_at FROM blocked ORDER BY age_at ASC, created_at ASC, provider ASC, sandbox_id ASC LIMIT $3::int) candidates
|
|
697
|
+
), '[]'::json) AS blocked_retirement_candidates,
|
|
698
|
+
'[]'::json AS dead_lettered_retirement_candidates`
|
|
699
|
+
: '';
|
|
587
700
|
return `
|
|
588
701
|
WITH ready AS (
|
|
589
702
|
SELECT created_at AS age_at
|
|
@@ -616,6 +729,10 @@ function cleanupProjectionQuery(schema: string): string {
|
|
|
616
729
|
LIMIT $2::int
|
|
617
730
|
), blocked AS (
|
|
618
731
|
SELECT
|
|
732
|
+
provider,
|
|
733
|
+
sandbox_id,
|
|
734
|
+
routing_domain_key,
|
|
735
|
+
created_at,
|
|
619
736
|
CASE
|
|
620
737
|
WHEN blocked_at IS NOT NULL THEN blocked_at
|
|
621
738
|
ELSE coalesce(lease_expires_at, created_at)
|
|
@@ -645,7 +762,7 @@ function cleanupProjectionQuery(schema: string): string {
|
|
|
645
762
|
(SELECT count(*) FROM delayed) > $3 AS delayed_truncated,
|
|
646
763
|
(SELECT count(*) FROM in_flight) > $3 AS in_flight_truncated,
|
|
647
764
|
(SELECT count(*) FROM blocked) > $3 AS blocked_truncated,
|
|
648
|
-
false AS dead_lettered_truncated,
|
|
765
|
+
false AS dead_lettered_truncated${retirementColumns},
|
|
649
766
|
0::bigint AS admitted,
|
|
650
767
|
0::bigint AS started,
|
|
651
768
|
0::bigint AS completed,
|
|
@@ -657,8 +774,19 @@ function cleanupProjectionQuery(schema: string): string {
|
|
|
657
774
|
`;
|
|
658
775
|
}
|
|
659
776
|
|
|
660
|
-
function computeBillingProjectionQuery(
|
|
777
|
+
function computeBillingProjectionQuery(
|
|
778
|
+
schema: string,
|
|
779
|
+
includeRetirementCandidates: boolean,
|
|
780
|
+
): string {
|
|
661
781
|
const table = `${schema}."compute_billing_jobs_v2"`;
|
|
782
|
+
const retirementColumns = includeRetirementCandidates
|
|
783
|
+
? `,
|
|
784
|
+
COALESCE((
|
|
785
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}))
|
|
786
|
+
FROM (SELECT item_id, age_at FROM blocked ORDER BY age_at ASC, item_id ASC LIMIT $3::int) candidates
|
|
787
|
+
), '[]'::json) AS blocked_retirement_candidates,
|
|
788
|
+
'[]'::json AS dead_lettered_retirement_candidates`
|
|
789
|
+
: '';
|
|
662
790
|
return `
|
|
663
791
|
WITH ready AS (
|
|
664
792
|
SELECT created_at AS age_at
|
|
@@ -688,7 +816,7 @@ function computeBillingProjectionQuery(schema: string): string {
|
|
|
688
816
|
ORDER BY lease_expires_at, created_at, run_id
|
|
689
817
|
LIMIT $2::int
|
|
690
818
|
), blocked AS (
|
|
691
|
-
SELECT coalesce(blocked_at, lease_expires_at, created_at) AS age_at
|
|
819
|
+
SELECT run_id::text AS item_id, coalesce(blocked_at, lease_expires_at, created_at) AS age_at
|
|
692
820
|
FROM ${table}
|
|
693
821
|
WHERE status <> 'settled'
|
|
694
822
|
AND (
|
|
@@ -713,7 +841,7 @@ function computeBillingProjectionQuery(schema: string): string {
|
|
|
713
841
|
(SELECT count(*) FROM delayed) > $3 AS delayed_truncated,
|
|
714
842
|
(SELECT count(*) FROM in_flight) > $3 AS in_flight_truncated,
|
|
715
843
|
(SELECT count(*) FROM blocked) > $3 AS blocked_truncated,
|
|
716
|
-
false AS dead_lettered_truncated,
|
|
844
|
+
false AS dead_lettered_truncated${retirementColumns},
|
|
717
845
|
0::bigint AS admitted,
|
|
718
846
|
0::bigint AS started,
|
|
719
847
|
0::bigint AS completed,
|
|
@@ -796,6 +924,10 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
796
924
|
const queueNames = registry.rows
|
|
797
925
|
.slice(0, maxQueues)
|
|
798
926
|
.map((row) => row.queue_name);
|
|
927
|
+
const candidateSampleLimit = Math.max(
|
|
928
|
+
1,
|
|
929
|
+
Math.floor(sampleLimit / Math.max(queueNames.length, 1)),
|
|
930
|
+
);
|
|
799
931
|
if (requestedNames) {
|
|
800
932
|
const found = new Set(queueNames);
|
|
801
933
|
const missing = requestedNames.filter((name) => !found.has(name));
|
|
@@ -807,9 +939,17 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
807
939
|
}
|
|
808
940
|
const projections: RuntimeQueueHealthProjection[] = [];
|
|
809
941
|
for (const queueName of queueNames) {
|
|
942
|
+
const includeRetirementCandidates =
|
|
943
|
+
input.includeRetirementCandidates === true;
|
|
810
944
|
const result = await client.query<CountRow>(
|
|
811
|
-
absurdProjectionQuery(
|
|
812
|
-
|
|
945
|
+
absurdProjectionQuery(
|
|
946
|
+
`r_${queueName}`,
|
|
947
|
+
includeRetirementCandidates,
|
|
948
|
+
candidateSampleLimit,
|
|
949
|
+
),
|
|
950
|
+
includeRetirementCandidates
|
|
951
|
+
? [now, sampleLimit + 1, sampleLimit, candidateSampleLimit]
|
|
952
|
+
: [now, sampleLimit + 1, sampleLimit],
|
|
813
953
|
);
|
|
814
954
|
projections.push(
|
|
815
955
|
projectionFromRow({
|
|
@@ -820,25 +960,29 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
820
960
|
observedAt: now,
|
|
821
961
|
environment: input.environment,
|
|
822
962
|
options,
|
|
963
|
+
includeRetirementCandidates: input.includeRetirementCandidates,
|
|
823
964
|
}),
|
|
824
965
|
);
|
|
825
966
|
}
|
|
826
967
|
const [outbox, computeBilling, cleanup] = await Promise.all([
|
|
827
|
-
client.query<CountRow>(
|
|
828
|
-
|
|
829
|
-
sampleLimit + 1,
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
968
|
+
client.query<CountRow>(
|
|
969
|
+
outboxProjectionQuery(schema, input.includeRetirementCandidates === true),
|
|
970
|
+
[now, sampleLimit + 1, sampleLimit],
|
|
971
|
+
),
|
|
972
|
+
client.query<CountRow>(
|
|
973
|
+
computeBillingProjectionQuery(
|
|
974
|
+
schema,
|
|
975
|
+
input.includeRetirementCandidates === true,
|
|
976
|
+
),
|
|
977
|
+
[now, sampleLimit + 1, sampleLimit],
|
|
978
|
+
),
|
|
979
|
+
client.query<CountRow>(
|
|
980
|
+
cleanupProjectionQuery(
|
|
981
|
+
schema,
|
|
982
|
+
input.includeRetirementCandidates === true,
|
|
983
|
+
),
|
|
984
|
+
[now, sampleLimit + 1, sampleLimit],
|
|
985
|
+
),
|
|
842
986
|
]);
|
|
843
987
|
projections.push(
|
|
844
988
|
projectionFromRow({
|
|
@@ -849,6 +993,7 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
849
993
|
observedAt: now,
|
|
850
994
|
environment: input.environment,
|
|
851
995
|
options,
|
|
996
|
+
includeRetirementCandidates: input.includeRetirementCandidates,
|
|
852
997
|
}),
|
|
853
998
|
projectionFromRow({
|
|
854
999
|
id: 'runtime:compute-billing-jobs-v2',
|
|
@@ -858,6 +1003,7 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
858
1003
|
observedAt: now,
|
|
859
1004
|
environment: input.environment,
|
|
860
1005
|
options,
|
|
1006
|
+
includeRetirementCandidates: input.includeRetirementCandidates,
|
|
861
1007
|
}),
|
|
862
1008
|
projectionFromRow({
|
|
863
1009
|
id: 'runtime:sandbox-cleanup-jobs-v2',
|
|
@@ -867,6 +1013,7 @@ export async function readRuntimeQueueHealthFromClient(
|
|
|
867
1013
|
observedAt: now,
|
|
868
1014
|
environment: input.environment,
|
|
869
1015
|
options,
|
|
1016
|
+
includeRetirementCandidates: input.includeRetirementCandidates,
|
|
870
1017
|
}),
|
|
871
1018
|
);
|
|
872
1019
|
const summary = {
|
|
@@ -11,6 +11,7 @@ export const PRODUCT_NOTIFICATION_EVENT_SCHEMA_VERSION = 1 as const;
|
|
|
11
11
|
export const PRODUCT_NOTIFICATION_MAX_EVENT_BYTES = 16_000;
|
|
12
12
|
export const PRODUCT_NOTIFICATION_MAX_EVENT_FACTS = 20;
|
|
13
13
|
export const PRODUCT_NOTIFICATION_MAX_EVENT_AGE_MS = 30 * 24 * 60 * 60 * 1_000;
|
|
14
|
+
export const PRODUCT_NOTIFICATION_MAX_IDENTIFIER_LENGTH = 512;
|
|
14
15
|
export const PRODUCT_NOTIFICATION_MAX_SOURCE_CLOCK_SKEW_MS = 5 * 60 * 1_000;
|
|
15
16
|
|
|
16
17
|
export const ProductNotificationEvent = {
|
|
@@ -255,7 +256,9 @@ function requiredString(value: unknown, field: string) {
|
|
|
255
256
|
if (typeof value !== 'string' || !value.trim()) {
|
|
256
257
|
throw new Error(`${field} must be a non-empty string.`);
|
|
257
258
|
}
|
|
258
|
-
if (value.length >
|
|
259
|
+
if (value.length > PRODUCT_NOTIFICATION_MAX_IDENTIFIER_LENGTH) {
|
|
260
|
+
throw new Error(`${field} is too long.`);
|
|
261
|
+
}
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
function requireInternalTargetPath(value: unknown) {
|