deepline 0.3.148 → 0.3.150
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/client.ts +347 -12
- package/dist/bundling-sources/sdk/src/http.ts +28 -2
- package/dist/bundling-sources/sdk/src/index.ts +4 -0
- package/dist/bundling-sources/sdk/src/play.ts +55 -5
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/observability/dlq.ts +1 -0
- package/dist/bundling-sources/shared_libs/observability/queue-health.ts +31 -0
- package/dist/bundling-sources/shared_libs/observability/queue-item-connectors.ts +13 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +94 -23
- package/dist/cli/index.js +393 -17
- package/dist/cli/index.mjs +399 -20
- package/dist/index.d.mts +61 -2
- package/dist/index.d.ts +61 -2
- package/dist/index.js +236 -13
- package/dist/index.mjs +236 -13
- 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
|
@@ -561,6 +561,24 @@
|
|
|
561
561
|
"failureAlert": "vercel_route_wrapper",
|
|
562
562
|
"domainReportRequired": true
|
|
563
563
|
},
|
|
564
|
+
{
|
|
565
|
+
"id": "vercel.public-tool-execution-blob-cleanup",
|
|
566
|
+
"name": "Public tool execution blob cleanup",
|
|
567
|
+
"scheduler": "vercel_cron",
|
|
568
|
+
"cadence": "*/15 * * * *",
|
|
569
|
+
"timezone": "UTC",
|
|
570
|
+
"source": "apps/deepline-api/vercel.json",
|
|
571
|
+
"locator": "/api/v2/cron/public-tool-execution-blob-cleanup",
|
|
572
|
+
"owner": "integrations",
|
|
573
|
+
"criticality": "maintenance",
|
|
574
|
+
"purpose": "Delete expired public execution response and launch blobs while retaining idempotency key and fingerprint tombstones.",
|
|
575
|
+
"successDefinition": "Every claimed expired receipt's recorded blobs are confirmed absent before their references are cleared; no eligible receipts is a valid no-op.",
|
|
576
|
+
"failureDefinition": "An R2 deletion/absence check fails or the fenced tombstone cleanup acknowledgement cannot complete.",
|
|
577
|
+
"startGraceMs": 300000,
|
|
578
|
+
"runTimeoutMs": 1800000,
|
|
579
|
+
"failureAlert": "vercel_route_wrapper",
|
|
580
|
+
"domainReportRequired": true
|
|
581
|
+
},
|
|
564
582
|
{
|
|
565
583
|
"id": "vercel.scheduled-work-health-snapshot",
|
|
566
584
|
"name": "Scheduled-work health snapshot",
|
|
@@ -144,6 +144,39 @@ export async function r2ObjectExists(
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Confirm an R2 object is absent without confusing a HEAD/auth/network failure
|
|
149
|
+
* with a successful deletion.
|
|
150
|
+
*/
|
|
151
|
+
export async function confirmR2ObjectAbsent(
|
|
152
|
+
key: string,
|
|
153
|
+
kind: R2BucketKind = 'default',
|
|
154
|
+
): Promise<boolean> {
|
|
155
|
+
const config = getR2Config(kind);
|
|
156
|
+
const client = getR2Client(config);
|
|
157
|
+
try {
|
|
158
|
+
await client.send(
|
|
159
|
+
new HeadObjectCommand({ Bucket: config.bucket, Key: key }),
|
|
160
|
+
);
|
|
161
|
+
return false;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const details = error as {
|
|
164
|
+
name?: string;
|
|
165
|
+
Code?: string;
|
|
166
|
+
$metadata?: { httpStatusCode?: number };
|
|
167
|
+
};
|
|
168
|
+
if (
|
|
169
|
+
details.$metadata?.httpStatusCode === 404 ||
|
|
170
|
+
details.name === 'NotFound' ||
|
|
171
|
+
details.name === 'NoSuchKey' ||
|
|
172
|
+
details.Code === 'NotFound' ||
|
|
173
|
+
details.Code === 'NoSuchKey'
|
|
174
|
+
)
|
|
175
|
+
return true;
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
147
180
|
export async function uploadBufferToR2(input: {
|
|
148
181
|
key: string;
|
|
149
182
|
body: Buffer | Uint8Array;
|
|
@@ -757,6 +757,9 @@ function isRetryableAppRuntimeResponse(input: {
|
|
|
757
757
|
// than reducing every 4xx to permanent at the worker boundary.
|
|
758
758
|
const explicitRetryable = appRuntimeExplicitRetryable(input.body);
|
|
759
759
|
if (explicitRetryable !== null) return explicitRetryable;
|
|
760
|
+
if (isComputeBillingTooManyWritesResponse(input)) {
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
760
763
|
if (
|
|
761
764
|
input.action === 'append_run_events' &&
|
|
762
765
|
input.status === 500 &&
|
|
@@ -809,6 +812,43 @@ function appRuntimeExplicitRetryable(body: string): boolean | null {
|
|
|
809
812
|
}
|
|
810
813
|
}
|
|
811
814
|
|
|
815
|
+
function isComputeBillingTooManyWritesResponse(input: {
|
|
816
|
+
action: RuntimeApiRequest['action'];
|
|
817
|
+
status: number;
|
|
818
|
+
body: string;
|
|
819
|
+
}): boolean {
|
|
820
|
+
if (
|
|
821
|
+
input.status !== 500 ||
|
|
822
|
+
(input.action !== 'compute_billing_upsert' &&
|
|
823
|
+
input.action !== 'compute_billing_record_item' &&
|
|
824
|
+
input.action !== 'compute_billing_finalize')
|
|
825
|
+
) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
// The runtime route's unexpected-error envelope exposes the Convex error
|
|
829
|
+
// name and redacted message but has no retryable flag. A TooManyWrites
|
|
830
|
+
// conflict is transient; treating this 500 as permanent blocks a durable
|
|
831
|
+
// billing job. Only the replay-safe compute actions accept this signature.
|
|
832
|
+
try {
|
|
833
|
+
const parsed = JSON.parse(input.body) as unknown;
|
|
834
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
const error = parsed as Record<string, unknown>;
|
|
838
|
+
if (error.action != null && error.action !== input.action) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
return (
|
|
842
|
+
error.errorName === 'TooManyWrites' ||
|
|
843
|
+
error.code === 'TooManyWrites' ||
|
|
844
|
+
(typeof error.debug_error === 'string' &&
|
|
845
|
+
/\bTooManyWrites\b/.test(error.debug_error))
|
|
846
|
+
);
|
|
847
|
+
} catch {
|
|
848
|
+
return false;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
812
852
|
function isRetryableAppRuntimeResponseStatus(status: number): boolean {
|
|
813
853
|
return (
|
|
814
854
|
status === 429 ||
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
lowerBound,
|
|
6
6
|
notApplicable,
|
|
7
7
|
unavailable,
|
|
8
|
+
runtimeQueueItemRevision,
|
|
8
9
|
type QueueDefinition,
|
|
9
10
|
type QueueHealth,
|
|
10
11
|
type QueueObservation,
|
|
@@ -236,7 +237,42 @@ function operatorRetirementCandidates(
|
|
|
236
237
|
itemId = candidate.itemId;
|
|
237
238
|
}
|
|
238
239
|
const ageMs = numberValue(candidate.ageMs as string | number | undefined);
|
|
239
|
-
|
|
240
|
+
const revisionFields = candidate.revisionFields;
|
|
241
|
+
let sourceRevision: string | undefined;
|
|
242
|
+
if (
|
|
243
|
+
typeof revisionFields === 'object' &&
|
|
244
|
+
revisionFields !== null &&
|
|
245
|
+
!Array.isArray(revisionFields)
|
|
246
|
+
) {
|
|
247
|
+
const fields = revisionFields as Record<string, unknown>;
|
|
248
|
+
const sourceDate = fields.updatedAt ?? fields.createdAt;
|
|
249
|
+
const requiresState = kind === 'absurd';
|
|
250
|
+
const requiresStatus =
|
|
251
|
+
kind === 'compute_billing' || kind === 'sandbox_cleanup';
|
|
252
|
+
if (
|
|
253
|
+
(typeof sourceDate === 'string' || typeof sourceDate === 'number') &&
|
|
254
|
+
Number.isFinite(new Date(sourceDate).getTime()) &&
|
|
255
|
+
(!requiresState || typeof fields.state === 'string') &&
|
|
256
|
+
(!requiresStatus || typeof fields.status === 'string')
|
|
257
|
+
) {
|
|
258
|
+
sourceRevision = runtimeQueueItemRevision({
|
|
259
|
+
updatedAt: fields.updatedAt,
|
|
260
|
+
createdAt: fields.createdAt,
|
|
261
|
+
state: fields.state,
|
|
262
|
+
status: fields.status,
|
|
263
|
+
leaseExpiresAt: fields.leaseExpiresAt,
|
|
264
|
+
failedAt: fields.failedAt,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (itemId !== undefined) {
|
|
269
|
+
candidates.push({
|
|
270
|
+
itemId,
|
|
271
|
+
bucket,
|
|
272
|
+
ageMs,
|
|
273
|
+
...(sourceRevision ? { sourceRevision } : {}),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
240
276
|
}
|
|
241
277
|
};
|
|
242
278
|
append(row.blocked_retirement_candidates, 'blocked');
|
|
@@ -517,45 +553,53 @@ function absurdProjectionQuery(
|
|
|
517
553
|
candidateSampleLimit: number,
|
|
518
554
|
): string {
|
|
519
555
|
const table = `absurd.${quoteIdentifier(queueTable, 'queue table')}`;
|
|
556
|
+
const revisionFields = includeRetirementCandidates
|
|
557
|
+
? `json_build_object(
|
|
558
|
+
'createdAt', created_at,
|
|
559
|
+
'state', state,
|
|
560
|
+
'leaseExpiresAt', claim_expires_at,
|
|
561
|
+
'failedAt', failed_at
|
|
562
|
+
)`
|
|
563
|
+
: 'NULL::json';
|
|
520
564
|
const retirementColumns = includeRetirementCandidates
|
|
521
565
|
? `,
|
|
522
566
|
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
|
|
567
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}, 'revisionFields', revision_fields))
|
|
568
|
+
FROM (SELECT item_id, age_at, revision_fields FROM sampled WHERE bucket = 'blocked' ORDER BY age_at ASC, item_id ASC LIMIT $4::int) candidates
|
|
525
569
|
), '[]'::json) AS blocked_retirement_candidates,
|
|
526
570
|
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
|
|
571
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}, 'revisionFields', revision_fields))
|
|
572
|
+
FROM (SELECT item_id, age_at, revision_fields FROM sampled WHERE bucket = 'dead_lettered' ORDER BY age_at ASC, item_id ASC LIMIT $4::int) candidates
|
|
529
573
|
), '[]'::json) AS dead_lettered_retirement_candidates`
|
|
530
574
|
: '';
|
|
531
575
|
return `
|
|
532
576
|
WITH sampled AS (
|
|
533
|
-
(SELECT 'ready'::text AS bucket, run_id::text AS item_id, available_at AS age_at
|
|
577
|
+
(SELECT 'ready'::text AS bucket, run_id::text AS item_id, available_at AS age_at, NULL::json AS revision_fields
|
|
534
578
|
FROM ${table}
|
|
535
579
|
WHERE state = 'pending' AND available_at <= $1::timestamptz
|
|
536
580
|
ORDER BY available_at, run_id
|
|
537
581
|
LIMIT $2::int)
|
|
538
582
|
UNION ALL
|
|
539
|
-
(SELECT 'delayed'::text, run_id::text, available_at
|
|
583
|
+
(SELECT 'delayed'::text, run_id::text, available_at, NULL::json
|
|
540
584
|
FROM ${table}
|
|
541
585
|
WHERE state = 'sleeping'
|
|
542
586
|
ORDER BY available_at, run_id
|
|
543
587
|
LIMIT $2::int)
|
|
544
588
|
UNION ALL
|
|
545
|
-
(SELECT 'delayed'::text, run_id::text, available_at
|
|
589
|
+
(SELECT 'delayed'::text, run_id::text, available_at, NULL::json
|
|
546
590
|
FROM ${table}
|
|
547
591
|
WHERE state = 'pending' AND available_at > $1::timestamptz
|
|
548
592
|
ORDER BY available_at, run_id
|
|
549
593
|
LIMIT $2::int)
|
|
550
594
|
UNION ALL
|
|
551
|
-
(SELECT 'in_flight'::text, run_id::text, coalesce(started_at, created_at)
|
|
595
|
+
(SELECT 'in_flight'::text, run_id::text, coalesce(started_at, created_at), NULL::json
|
|
552
596
|
FROM ${table}
|
|
553
597
|
WHERE state = 'running'
|
|
554
598
|
AND (claim_expires_at IS NULL OR claim_expires_at > $1::timestamptz)
|
|
555
599
|
ORDER BY available_at, run_id
|
|
556
600
|
LIMIT $2::int)
|
|
557
601
|
UNION ALL
|
|
558
|
-
(SELECT 'blocked'::text, run_id::text, coalesce(claim_expires_at, started_at, created_at)
|
|
602
|
+
(SELECT 'blocked'::text, run_id::text, coalesce(claim_expires_at, started_at, created_at), ${revisionFields}
|
|
559
603
|
FROM ${table}
|
|
560
604
|
WHERE state = 'running'
|
|
561
605
|
AND claim_expires_at IS NOT NULL
|
|
@@ -563,7 +607,7 @@ function absurdProjectionQuery(
|
|
|
563
607
|
ORDER BY claim_expires_at, run_id
|
|
564
608
|
LIMIT $2::int)
|
|
565
609
|
UNION ALL
|
|
566
|
-
(SELECT 'dead_lettered'::text, run_id::text, coalesce(failed_at, created_at)
|
|
610
|
+
(SELECT 'dead_lettered'::text, run_id::text, coalesce(failed_at, created_at), ${revisionFields}
|
|
567
611
|
FROM ${table}
|
|
568
612
|
WHERE state = 'failed'
|
|
569
613
|
ORDER BY available_at, run_id
|
|
@@ -602,15 +646,22 @@ function outboxProjectionQuery(
|
|
|
602
646
|
includeRetirementCandidates: boolean,
|
|
603
647
|
): string {
|
|
604
648
|
const table = `${schema}."outbox"`;
|
|
649
|
+
const revisionFields = includeRetirementCandidates
|
|
650
|
+
? `json_build_object(
|
|
651
|
+
'createdAt', created_at,
|
|
652
|
+
'leaseExpiresAt', lease_expires_at,
|
|
653
|
+
'failedAt', failed_at
|
|
654
|
+
)`
|
|
655
|
+
: 'NULL::json';
|
|
605
656
|
const retirementColumns = includeRetirementCandidates
|
|
606
657
|
? `,
|
|
607
658
|
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
|
|
659
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}, 'revisionFields', revision_fields))
|
|
660
|
+
FROM (SELECT item_id, age_at, revision_fields FROM blocked ORDER BY age_at ASC, item_id ASC LIMIT $3::int) candidates
|
|
610
661
|
), '[]'::json) AS blocked_retirement_candidates,
|
|
611
662
|
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
|
|
663
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}, 'revisionFields', revision_fields))
|
|
664
|
+
FROM (SELECT item_id, age_at, revision_fields FROM dead_lettered ORDER BY age_at ASC, item_id ASC LIMIT $3::int) candidates
|
|
614
665
|
), '[]'::json) AS dead_lettered_retirement_candidates`
|
|
615
666
|
: '';
|
|
616
667
|
return `
|
|
@@ -642,7 +693,8 @@ function outboxProjectionQuery(
|
|
|
642
693
|
ORDER BY lease_expires_at, created_at, event_id
|
|
643
694
|
LIMIT $2::int
|
|
644
695
|
), blocked AS (
|
|
645
|
-
SELECT event_id::text AS item_id, coalesce(lease_expires_at, created_at) AS age_at
|
|
696
|
+
SELECT event_id::text AS item_id, coalesce(lease_expires_at, created_at) AS age_at,
|
|
697
|
+
${revisionFields} AS revision_fields
|
|
646
698
|
FROM ${table}
|
|
647
699
|
WHERE delivered_at IS NULL
|
|
648
700
|
AND failed_at IS NULL
|
|
@@ -651,7 +703,8 @@ function outboxProjectionQuery(
|
|
|
651
703
|
ORDER BY coalesce(lease_expires_at, created_at), created_at, event_id
|
|
652
704
|
LIMIT $2::int
|
|
653
705
|
), dead_lettered AS (
|
|
654
|
-
SELECT event_id::text AS item_id, coalesce(failed_at, created_at) AS age_at
|
|
706
|
+
SELECT event_id::text AS item_id, coalesce(failed_at, created_at) AS age_at,
|
|
707
|
+
${revisionFields} AS revision_fields
|
|
655
708
|
FROM ${table}
|
|
656
709
|
WHERE delivered_at IS NULL AND failed_at IS NOT NULL
|
|
657
710
|
ORDER BY failed_at, event_id
|
|
@@ -689,11 +742,19 @@ function cleanupProjectionQuery(
|
|
|
689
742
|
includeRetirementCandidates: boolean,
|
|
690
743
|
): string {
|
|
691
744
|
const table = `${schema}."sandbox_cleanup_jobs_v2"`;
|
|
745
|
+
const revisionFields = includeRetirementCandidates
|
|
746
|
+
? `json_build_object(
|
|
747
|
+
'updatedAt', updated_at,
|
|
748
|
+
'createdAt', created_at,
|
|
749
|
+
'status', status,
|
|
750
|
+
'leaseExpiresAt', lease_expires_at
|
|
751
|
+
)`
|
|
752
|
+
: 'NULL::json';
|
|
692
753
|
const retirementColumns = includeRetirementCandidates
|
|
693
754
|
? `,
|
|
694
755
|
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
|
|
756
|
+
SELECT json_agg(json_build_object('provider', provider, 'sandboxId', sandbox_id, 'routingDomainKey', routing_domain_key, 'ageMs', ${intervalExpr('age_at')}, 'revisionFields', revision_fields))
|
|
757
|
+
FROM (SELECT provider, sandbox_id, routing_domain_key, created_at, age_at, revision_fields FROM blocked ORDER BY age_at ASC, created_at ASC, provider ASC, sandbox_id ASC LIMIT $3::int) candidates
|
|
697
758
|
), '[]'::json) AS blocked_retirement_candidates,
|
|
698
759
|
'[]'::json AS dead_lettered_retirement_candidates`
|
|
699
760
|
: '';
|
|
@@ -736,7 +797,8 @@ function cleanupProjectionQuery(
|
|
|
736
797
|
CASE
|
|
737
798
|
WHEN blocked_at IS NOT NULL THEN blocked_at
|
|
738
799
|
ELSE coalesce(lease_expires_at, created_at)
|
|
739
|
-
END AS age_at
|
|
800
|
+
END AS age_at,
|
|
801
|
+
${revisionFields} AS revision_fields
|
|
740
802
|
FROM ${table}
|
|
741
803
|
WHERE status <> 'deleted'
|
|
742
804
|
AND (
|
|
@@ -779,11 +841,19 @@ function computeBillingProjectionQuery(
|
|
|
779
841
|
includeRetirementCandidates: boolean,
|
|
780
842
|
): string {
|
|
781
843
|
const table = `${schema}."compute_billing_jobs_v2"`;
|
|
844
|
+
const revisionFields = includeRetirementCandidates
|
|
845
|
+
? `json_build_object(
|
|
846
|
+
'updatedAt', updated_at,
|
|
847
|
+
'createdAt', created_at,
|
|
848
|
+
'status', status,
|
|
849
|
+
'leaseExpiresAt', lease_expires_at
|
|
850
|
+
)`
|
|
851
|
+
: 'NULL::json';
|
|
782
852
|
const retirementColumns = includeRetirementCandidates
|
|
783
853
|
? `,
|
|
784
854
|
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
|
|
855
|
+
SELECT json_agg(json_build_object('itemId', item_id, 'ageMs', ${intervalExpr('age_at')}, 'revisionFields', revision_fields))
|
|
856
|
+
FROM (SELECT item_id, age_at, revision_fields FROM blocked ORDER BY age_at ASC, item_id ASC LIMIT $3::int) candidates
|
|
787
857
|
), '[]'::json) AS blocked_retirement_candidates,
|
|
788
858
|
'[]'::json AS dead_lettered_retirement_candidates`
|
|
789
859
|
: '';
|
|
@@ -816,7 +886,8 @@ function computeBillingProjectionQuery(
|
|
|
816
886
|
ORDER BY lease_expires_at, created_at, run_id
|
|
817
887
|
LIMIT $2::int
|
|
818
888
|
), blocked AS (
|
|
819
|
-
SELECT run_id::text AS item_id, coalesce(blocked_at, lease_expires_at, created_at) AS age_at
|
|
889
|
+
SELECT run_id::text AS item_id, coalesce(blocked_at, lease_expires_at, created_at) AS age_at,
|
|
890
|
+
${revisionFields} AS revision_fields
|
|
820
891
|
FROM ${table}
|
|
821
892
|
WHERE status <> 'settled'
|
|
822
893
|
AND (
|