deepline 0.3.482 → 0.3.484
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/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +100 -9
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/ambiguous-modal-create-state.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/compute-billing-job-state.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement-state.ts +25 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +15 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/outbox-repair-operation-state.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +95 -33
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +9 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/sandbox-cleanup-job-state.ts +23 -0
- package/dist/bundling-sources/shared_libs/product-notifications/destination-state.ts +93 -0
- package/dist/cli/{auth-R5GFDAMI.mjs → auth-EBFOTF7X.mjs} +5 -5
- package/dist/cli/auth-L5CSV3UO.js +43 -0
- package/dist/cli/{billing-SO5SFQFC.mjs → billing-2ZOIMW5V.mjs} +6 -6
- package/dist/cli/{billing-DXKVQPTC.js → billing-G2G3EF3I.js} +79 -79
- package/dist/cli/{chunk-Y4Y2QUFL.js → chunk-2AWYJXBX.js} +5 -5
- package/dist/cli/{chunk-OVSSKTXG.js → chunk-3XQIVEAT.js} +3 -3
- package/dist/cli/{chunk-RLG2MNXH.mjs → chunk-4OEOM6GP.mjs} +4 -4
- package/dist/cli/{chunk-LVOEZVDW.mjs → chunk-4OVHIWE7.mjs} +2 -2
- package/dist/cli/{chunk-G7N2DYQL.mjs → chunk-4SFC2XUF.mjs} +5 -5
- package/dist/cli/chunk-5AEDYVB6.mjs +6 -0
- package/dist/cli/{chunk-2KC3MJP5.mjs → chunk-5GOG5ZGC.mjs} +2 -2
- package/dist/cli/chunk-5K5CAND4.mjs +200 -0
- package/dist/cli/{chunk-AEMPAR6L.js → chunk-5KNRQWHE.js} +3 -3
- package/dist/cli/chunk-6GRZEYSG.js +200 -0
- package/dist/cli/{chunk-FQFAWIRK.mjs → chunk-6NPWXE32.mjs} +1 -1
- package/dist/cli/{chunk-AZRG5NWV.mjs → chunk-7K5CWKKZ.mjs} +2 -2
- package/dist/cli/{chunk-LMLN7R7X.mjs → chunk-AF7MOUZP.mjs} +2 -2
- package/dist/cli/{chunk-UVU4OCYO.js → chunk-C62NI2X6.js} +5 -5
- package/dist/cli/{chunk-BHID34HA.js → chunk-CYWBAV5H.js} +16 -16
- package/dist/cli/chunk-D3BCSAN7.js +6 -0
- package/dist/cli/{chunk-M6B2OZGA.js → chunk-DBJ62RAO.js} +3 -3
- package/dist/cli/{chunk-JLI7SK55.mjs → chunk-DEXSQGXB.mjs} +5 -5
- package/dist/cli/{chunk-Y6OKWGAM.mjs → chunk-EACX7QXO.mjs} +2 -2
- package/dist/cli/{chunk-3EN2BLAS.js → chunk-FQK7BULS.js} +1 -1
- package/dist/cli/{chunk-6NF7S5WY.js → chunk-FRD5WFT4.js} +6 -6
- package/dist/cli/{chunk-KDJZXX7I.mjs → chunk-HEI3BWIV.mjs} +1 -1
- package/dist/cli/{chunk-RLYJ4O6Q.js → chunk-IXWQOVNW.js} +53 -53
- package/dist/cli/{chunk-4V34YMLU.js → chunk-KA2LNIUP.js} +6 -6
- package/dist/cli/{chunk-VU2PUAEH.js → chunk-KGOC6HGI.js} +53 -53
- package/dist/cli/{chunk-AJT436SK.mjs → chunk-LTZXLYKE.mjs} +1 -1
- package/dist/cli/{chunk-ETJSPSOQ.js → chunk-M4PG7FGC.js} +2 -2
- package/dist/cli/{chunk-YT6GV6RP.js → chunk-NW4QJE54.js} +2 -2
- package/dist/cli/{chunk-MFNEJOQG.js → chunk-OHKKDPA5.js} +10 -10
- package/dist/cli/{chunk-7SZUHFJ6.mjs → chunk-PU52JXKA.mjs} +2 -2
- package/dist/cli/{chunk-2ZIUCN5I.js → chunk-QIDDTIVZ.js} +93 -93
- package/dist/cli/{chunk-DZRHML3J.js → chunk-RLLEH2SP.js} +26 -26
- package/dist/cli/{chunk-RCWBWM5J.mjs → chunk-TIRSKEZH.mjs} +1 -1
- package/dist/cli/{chunk-QCIG6V26.mjs → chunk-TN2GW57M.mjs} +1 -1
- package/dist/cli/{chunk-5MHCTVMM.mjs → chunk-TRLDJNVE.mjs} +1 -1
- package/dist/cli/{chunk-OO6HA5OT.js → chunk-U7IJAGWB.js} +7 -7
- package/dist/cli/{chunk-AZUDSDZX.js → chunk-UKM74PAA.js} +37 -37
- package/dist/cli/{chunk-NXTZ4UNI.js → chunk-UT4YMA2N.js} +4 -4
- package/dist/cli/{chunk-LKFQERA6.js → chunk-UWOY74RU.js} +21 -21
- package/dist/cli/{chunk-EC2QPBSZ.mjs → chunk-V7JCMBLU.mjs} +4 -4
- package/dist/cli/{chunk-SFZCKYTD.js → chunk-VEVGM6B5.js} +9 -9
- package/dist/cli/{chunk-DYHBKFCW.mjs → chunk-WXRM3UC6.mjs} +3 -3
- package/dist/cli/{chunk-BK3XMHPF.mjs → chunk-WYUCDLVR.mjs} +1 -1
- package/dist/cli/{chunk-SKBXTN5F.mjs → chunk-XZXIWPT3.mjs} +1 -1
- package/dist/cli/{chunk-DM7APZWK.mjs → chunk-YGN77QA5.mjs} +2 -2
- package/dist/cli/{chunk-FKGA7VKL.mjs → chunk-YHA3EV3I.mjs} +4 -4
- package/dist/cli/{chunk-HYS35M6H.mjs → chunk-YWC5AN26.mjs} +10 -10
- package/dist/cli/{chunk-FUVJPW7O.js → chunk-ZOKM7DVA.js} +8 -8
- package/dist/cli/{csv-IC3XJ4PV.mjs → csv-NLWVWD3H.mjs} +4 -4
- package/dist/cli/{csv-UWFQLI3K.js → csv-S3CHUKI6.js} +13 -13
- package/dist/cli/{db-RIPJQYF3.mjs → db-OGRPAEEM.mjs} +6 -6
- package/dist/cli/{db-PT73YDEP.js → db-WBSKMMOH.js} +27 -27
- package/dist/cli/{describe-GHI2MTDW.mjs → describe-FAD3Z7LN.mjs} +18 -17
- package/dist/cli/describe-UIAYCHWW.js +49 -0
- package/dist/cli/{executions-XUJBZF5H.mjs → executions-LYCL344R.mjs} +6 -6
- package/dist/cli/{executions-QUOWVXCU.js → executions-SOAZHCY4.js} +8 -8
- package/dist/cli/{feedback-JOZ7QNGH.mjs → feedback-AVF6V7SV.mjs} +4 -4
- package/dist/cli/{feedback-UDZ7BFB3.js → feedback-OROUPUNJ.js} +8 -8
- package/dist/cli/git-ATMWC27D.js +25 -0
- package/dist/cli/{git-O67OED4L.mjs → git-J5N5M57H.mjs} +5 -5
- package/dist/cli/index.js +138 -138
- package/dist/cli/index.mjs +42 -42
- package/dist/cli/{legacy-noops-T2G2TKO2.mjs → legacy-noops-ENRRM7BL.mjs} +5 -5
- package/dist/cli/{legacy-noops-ZFD6Z2ID.js → legacy-noops-QAQMZMEG.js} +8 -8
- package/dist/cli/{monitors-WSH4I37O.mjs → monitors-CKQ65WRL.mjs} +7 -7
- package/dist/cli/{monitors-QPWEWJW7.js → monitors-SFMQ5LFR.js} +91 -91
- package/dist/cli/{org-PZOFC4P3.js → org-3UNTDLT2.js} +72 -72
- package/dist/cli/{org-FVM465Q4.mjs → org-K4UIEU5P.mjs} +6 -6
- package/dist/cli/{pages-A5SLKL6L.mjs → pages-6GPGBIAH.mjs} +6 -6
- package/dist/cli/{pages-UMDOU2WF.js → pages-NKZTGB4C.js} +17 -17
- package/dist/cli/{play-XKJLIZQE.mjs → play-62BEE7IX.mjs} +26 -22
- package/dist/cli/{play-AFGL5D7B.js → play-BDEADESI.js} +495 -491
- package/dist/cli/{providers-CQDRM62K.js → providers-SXGP44GH.js} +9 -9
- package/dist/cli/{providers-A2EIHS3P.mjs → providers-X3CUWMFP.mjs} +6 -6
- package/dist/cli/{quickstart-BJ2XCUVV.mjs → quickstart-7LN22BKX.mjs} +4 -4
- package/dist/cli/{quickstart-NMJLH7T6.js → quickstart-XQ4426M4.js} +6 -6
- package/dist/cli/{run-S4XTJADQ.js → run-PBJ4ZJ4W.js} +37 -36
- package/dist/cli/{run-63I5M3ID.mjs → run-XXSN3B5A.mjs} +26 -25
- package/dist/cli/search-IGHLRLBD.js +32 -0
- package/dist/cli/{search-DUIWWZ56.mjs → search-M75VMADD.mjs} +8 -8
- package/dist/cli/{chunk-UXQZLXHZ.js → search-output-F6HCFYEI.js} +14 -194
- package/dist/cli/{chunk-EDBQIPMG.mjs → search-output-NUQAKWDM.mjs} +14 -194
- package/dist/cli/{secrets-FK4PQJ6U.mjs → secrets-J2H6V33R.mjs} +6 -6
- package/dist/cli/{secrets-Q6QEWCAT.js → secrets-LRYY4QPE.js} +15 -15
- package/dist/cli/{sessions-QJSN6AUL.mjs → sessions-IMT336UV.mjs} +5 -5
- package/dist/cli/{sessions-KF6JPKFK.js → sessions-YPF62CZE.js} +6 -6
- package/dist/cli/{settings-PIKGA5PF.js → settings-35YYC5WA.js} +78 -78
- package/dist/cli/{settings-UIQP5ERC.mjs → settings-PDWVQIGH.mjs} +11 -11
- package/dist/cli/{setup-4CALCNAA.mjs → setup-DG5DISSE.mjs} +9 -9
- package/dist/cli/{setup-ROKA4M5X.js → setup-UR7GOR7H.js} +47 -47
- package/dist/cli/skills-4U5WC4MJ.js +33 -0
- package/dist/cli/{skills-SQKRC7W4.mjs → skills-U6NJD5J7.mjs} +7 -7
- package/dist/cli/{tools-B6J53QS2.mjs → tools-2HGEOELR.mjs} +17 -16
- package/dist/cli/tools-PYJTQVVS.js +56 -0
- package/dist/cli/update-344OTRFM.js +33 -0
- package/dist/cli/{update-MVKCAJJV.mjs → update-FRAYBAHK.mjs} +9 -9
- package/dist/cli/{workflow-3BO5WPZO.mjs → workflow-DDKVTF36.mjs} +7 -7
- package/dist/cli/{workflow-VH2H4VS3.js → workflow-JKKLOMBX.js} +17 -17
- package/dist/index.js +10 -10
- package/dist/index.mjs +10 -10
- package/dist/install-integrity.json +106 -96
- package/dist/plays/bundle-play-file.mjs +6 -6
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/dist/server.js +5 -5
- package/dist/server.mjs +3 -3
- package/package.json +1 -1
- package/dist/cli/auth-Q5VRDGL4.js +0 -43
- package/dist/cli/describe-72RFB7RS.js +0 -48
- package/dist/cli/git-A6SU7ITW.js +0 -25
- package/dist/cli/search-ACCI4VYH.js +0 -32
- package/dist/cli/skills-GZQSDPZX.js +0 -33
- package/dist/cli/tools-5C7ZVTDG.js +0 -55
- package/dist/cli/update-PR4QP7JN.js +0 -33
|
@@ -205,7 +205,7 @@ export const SDK_RELEASE = {
|
|
|
205
205
|
// getters keep their established compatibility behavior.
|
|
206
206
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
207
207
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
208
|
-
version: '0.3.
|
|
208
|
+
version: '0.3.484',
|
|
209
209
|
updateSummary: {
|
|
210
210
|
introducedIn: '0.3.90',
|
|
211
211
|
summary:
|
|
@@ -22,6 +22,72 @@ export type InboundPgClient = {
|
|
|
22
22
|
|
|
23
23
|
export type InboundFailureClass = 'transient' | 'permanent';
|
|
24
24
|
|
|
25
|
+
/** Persisted states for `dl_meta.signal_radar_inbound_dead_letter`. */
|
|
26
|
+
export const SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS = {
|
|
27
|
+
open: 'open',
|
|
28
|
+
replaying: 'replaying',
|
|
29
|
+
replayed: 'replayed',
|
|
30
|
+
discarded: 'discarded',
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export type SignalRadarInboundDeadLetterStatus =
|
|
34
|
+
(typeof SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS)[keyof typeof SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS];
|
|
35
|
+
|
|
36
|
+
export type ClaimableSignalRadarInboundDeadLetterStatus = Extract<
|
|
37
|
+
SignalRadarInboundDeadLetterStatus,
|
|
38
|
+
'open' | 'replaying'
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
/** Durable transition edges. Database predicates remain the atomic fences. */
|
|
42
|
+
export type SignalRadarInboundDeadLetterTransition =
|
|
43
|
+
| { event: 'claim'; from: 'open'; to: 'replaying' }
|
|
44
|
+
| { event: 'stale_claim_reclaimed'; from: 'replaying'; to: 'replaying' }
|
|
45
|
+
| { event: 'enqueue_failed'; from: 'replaying'; to: 'open' }
|
|
46
|
+
| { event: 'worker_unprocessed'; from: 'replaying'; to: 'open' }
|
|
47
|
+
| {
|
|
48
|
+
event: 'worker_processed';
|
|
49
|
+
from: 'open' | 'replaying';
|
|
50
|
+
to: 'replayed';
|
|
51
|
+
}
|
|
52
|
+
| { event: 'malformed_payload'; from: 'replaying'; to: 'discarded' }
|
|
53
|
+
| {
|
|
54
|
+
event: 'dead_letter_recorded';
|
|
55
|
+
from: 'open' | 'replaying' | 'replayed';
|
|
56
|
+
to: 'open';
|
|
57
|
+
}
|
|
58
|
+
| { event: 'dead_letter_recorded'; from: 'discarded'; to: 'discarded' };
|
|
59
|
+
|
|
60
|
+
/** Parse a value read from persisted storage; unknown future values fail closed. */
|
|
61
|
+
export function parseSignalRadarInboundDeadLetterStatus(
|
|
62
|
+
value: unknown,
|
|
63
|
+
): SignalRadarInboundDeadLetterStatus | null {
|
|
64
|
+
switch (value) {
|
|
65
|
+
case SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.open:
|
|
66
|
+
case SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.replaying:
|
|
67
|
+
case SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.replayed:
|
|
68
|
+
case SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.discarded:
|
|
69
|
+
return value;
|
|
70
|
+
default:
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A failed redrive returns an in-flight/completed delivery to review. */
|
|
76
|
+
export function transitionSignalRadarInboundDeadLetterStatus(
|
|
77
|
+
value: unknown,
|
|
78
|
+
event: 'dead_letter_recorded',
|
|
79
|
+
): SignalRadarInboundDeadLetterStatus {
|
|
80
|
+
const current = parseSignalRadarInboundDeadLetterStatus(value);
|
|
81
|
+
if (!current) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Unknown signal radar dead-letter status '${String(value)}'.`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return current === SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.discarded
|
|
87
|
+
? SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.discarded
|
|
88
|
+
: SIGNAL_RADAR_INBOUND_DEAD_LETTER_STATUS.open;
|
|
89
|
+
}
|
|
90
|
+
|
|
25
91
|
function errorMessage(error: unknown): string {
|
|
26
92
|
if (error instanceof Error && error.message.trim()) return error.message;
|
|
27
93
|
if (typeof error === 'string' && error.trim()) return error;
|
|
@@ -211,17 +277,42 @@ export async function recordSignalRadarDeadLetter(input: {
|
|
|
211
277
|
};
|
|
212
278
|
|
|
213
279
|
if (message.deadLetterId) {
|
|
214
|
-
const
|
|
215
|
-
`
|
|
216
|
-
|
|
217
|
-
attempts = attempts + $2,
|
|
218
|
-
last_error = $3,
|
|
219
|
-
updated_at = now()
|
|
280
|
+
const prior = await client.query(
|
|
281
|
+
`SELECT status
|
|
282
|
+
FROM dl_meta.signal_radar_inbound_dead_letter
|
|
220
283
|
WHERE dead_letter_id = $1
|
|
221
|
-
|
|
222
|
-
[message.deadLetterId
|
|
284
|
+
FOR UPDATE`,
|
|
285
|
+
[message.deadLetterId],
|
|
223
286
|
);
|
|
224
|
-
|
|
287
|
+
const existing = (prior.rows ?? [])[0] as
|
|
288
|
+
| { status?: unknown }
|
|
289
|
+
| undefined;
|
|
290
|
+
if (existing) {
|
|
291
|
+
const nextStatus = transitionSignalRadarInboundDeadLetterStatus(
|
|
292
|
+
existing.status,
|
|
293
|
+
'dead_letter_recorded',
|
|
294
|
+
);
|
|
295
|
+
const reopened = await client.query(
|
|
296
|
+
`UPDATE dl_meta.signal_radar_inbound_dead_letter
|
|
297
|
+
SET status = $2,
|
|
298
|
+
attempts = attempts + $3,
|
|
299
|
+
last_error = $4,
|
|
300
|
+
updated_at = now()
|
|
301
|
+
WHERE dead_letter_id = $1 AND status = $5
|
|
302
|
+
RETURNING dead_letter_id`,
|
|
303
|
+
[
|
|
304
|
+
message.deadLetterId,
|
|
305
|
+
nextStatus,
|
|
306
|
+
input.attempts,
|
|
307
|
+
lastError.slice(0, 8000),
|
|
308
|
+
existing.status,
|
|
309
|
+
],
|
|
310
|
+
);
|
|
311
|
+
if ((reopened.rows ?? []).length === 0) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
'Signal radar dead-letter status changed while recording a failure.',
|
|
314
|
+
);
|
|
315
|
+
}
|
|
225
316
|
if (eventIdempotencyKey) {
|
|
226
317
|
await markCapturedRawWriteFailed(
|
|
227
318
|
client,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Persisted in ambiguous_modal_creates.status. */
|
|
2
|
+
export const AmbiguousModalCreateStatus = {
|
|
3
|
+
Pending: 'pending',
|
|
4
|
+
Resolved: 'resolved',
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
export type AmbiguousModalCreateStatus =
|
|
8
|
+
(typeof AmbiguousModalCreateStatus)[keyof typeof AmbiguousModalCreateStatus];
|
|
9
|
+
|
|
10
|
+
/** Decode database text at the scheduler boundary; unknown states fail closed. */
|
|
11
|
+
export function readAmbiguousModalCreateStatus(
|
|
12
|
+
value: unknown,
|
|
13
|
+
): AmbiguousModalCreateStatus {
|
|
14
|
+
if (
|
|
15
|
+
value === AmbiguousModalCreateStatus.Pending ||
|
|
16
|
+
value === AmbiguousModalCreateStatus.Resolved
|
|
17
|
+
) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Unknown ambiguous Modal create status: ${String(value)}.`);
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Persisted in compute_billing_jobs_v2.status. */
|
|
2
|
+
export const ComputeBillingJobStatus = {
|
|
3
|
+
Pending: 'pending',
|
|
4
|
+
Settling: 'settling',
|
|
5
|
+
Settled: 'settled',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type ComputeBillingJobStatus =
|
|
9
|
+
(typeof ComputeBillingJobStatus)[keyof typeof ComputeBillingJobStatus];
|
|
10
|
+
|
|
11
|
+
/** Decode database text at the scheduler boundary; unknown states fail closed. */
|
|
12
|
+
export function readComputeBillingJobStatus(
|
|
13
|
+
value: unknown,
|
|
14
|
+
): ComputeBillingJobStatus {
|
|
15
|
+
if (
|
|
16
|
+
value === ComputeBillingJobStatus.Pending ||
|
|
17
|
+
value === ComputeBillingJobStatus.Settling ||
|
|
18
|
+
value === ComputeBillingJobStatus.Settled
|
|
19
|
+
) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unknown compute billing job status: ${String(value)}.`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Persisted in runtime_integration_operation_value_retirements.state. */
|
|
2
|
+
export const IntegrationOperationValueRetirementState = {
|
|
3
|
+
Pending: 'pending',
|
|
4
|
+
Deleting: 'deleting',
|
|
5
|
+
Deleted: 'deleted',
|
|
6
|
+
Cancelled: 'cancelled',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type IntegrationOperationValueRetirementState =
|
|
10
|
+
(typeof IntegrationOperationValueRetirementState)[keyof typeof IntegrationOperationValueRetirementState];
|
|
11
|
+
|
|
12
|
+
/** Decode database text at the scheduler boundary; unknown states fail closed. */
|
|
13
|
+
export function readIntegrationOperationValueRetirementState(
|
|
14
|
+
value: unknown,
|
|
15
|
+
): IntegrationOperationValueRetirementState {
|
|
16
|
+
if (
|
|
17
|
+
value === IntegrationOperationValueRetirementState.Pending ||
|
|
18
|
+
value === IntegrationOperationValueRetirementState.Deleting ||
|
|
19
|
+
value === IntegrationOperationValueRetirementState.Deleted ||
|
|
20
|
+
value === IntegrationOperationValueRetirementState.Cancelled
|
|
21
|
+
) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Unknown Integration Operation value retirement state: ${String(value)}.`);
|
|
25
|
+
}
|
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
isIntegrationOperationValueReference,
|
|
5
5
|
type IntegrationOperationValueReference,
|
|
6
6
|
} from './integration-operation-value-reference';
|
|
7
|
+
import {
|
|
8
|
+
IntegrationOperationValueRetirementState,
|
|
9
|
+
readIntegrationOperationValueRetirementState,
|
|
10
|
+
} from './integration-operation-value-retirement-state';
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* The value-retirement journal needs only the scheduler client's query
|
|
@@ -50,6 +54,7 @@ type RetirementRow = {
|
|
|
50
54
|
object_digest: string;
|
|
51
55
|
reference_json: unknown;
|
|
52
56
|
claim_token: string;
|
|
57
|
+
state: unknown;
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
export type ClaimedIntegrationOperationValueRetirement = {
|
|
@@ -59,6 +64,7 @@ export type ClaimedIntegrationOperationValueRetirement = {
|
|
|
59
64
|
objectDigest: string;
|
|
60
65
|
reference: IntegrationOperationValueReference;
|
|
61
66
|
claimToken: string;
|
|
67
|
+
state: typeof IntegrationOperationValueRetirementState.Deleting;
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -102,7 +108,8 @@ export async function claimPostgresIntegrationOperationValueRetirements(input: {
|
|
|
102
108
|
WHERE retirements.ctid = candidates.ctid
|
|
103
109
|
RETURNING retirements.org_id, retirements.operation_key,
|
|
104
110
|
retirements.object_key, retirements.object_digest,
|
|
105
|
-
retirements.reference_json, retirements.claim_token
|
|
111
|
+
retirements.reference_json, retirements.claim_token,
|
|
112
|
+
retirements.state`,
|
|
106
113
|
[limit(input.limit), claimToken],
|
|
107
114
|
);
|
|
108
115
|
return claimed.rows.map((row) => {
|
|
@@ -119,6 +126,12 @@ export async function claimPostgresIntegrationOperationValueRetirements(input: {
|
|
|
119
126
|
`Integration Operation value retirement ${row.object_key} does not match its immutable reference.`,
|
|
120
127
|
);
|
|
121
128
|
}
|
|
129
|
+
const state = readIntegrationOperationValueRetirementState(row.state);
|
|
130
|
+
if (state !== IntegrationOperationValueRetirementState.Deleting) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Integration Operation value retirement ${row.object_key} was claimed in ${state} state.`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
122
135
|
return {
|
|
123
136
|
orgId: row.org_id,
|
|
124
137
|
operationKey: row.operation_key,
|
|
@@ -126,6 +139,7 @@ export async function claimPostgresIntegrationOperationValueRetirements(input: {
|
|
|
126
139
|
objectDigest: row.object_digest,
|
|
127
140
|
reference: row.reference_json,
|
|
128
141
|
claimToken: row.claim_token,
|
|
142
|
+
state,
|
|
129
143
|
};
|
|
130
144
|
});
|
|
131
145
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Persisted in outbox_repair_operations.state. */
|
|
2
|
+
export const OutboxRepairOperationState = {
|
|
3
|
+
Planned: 'planned',
|
|
4
|
+
Applied: 'applied',
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
export type OutboxRepairOperationState =
|
|
8
|
+
(typeof OutboxRepairOperationState)[keyof typeof OutboxRepairOperationState];
|
|
9
|
+
|
|
10
|
+
/** Decode database text at the scheduler boundary; unknown states fail closed. */
|
|
11
|
+
export function readOutboxRepairOperationState(
|
|
12
|
+
value: unknown,
|
|
13
|
+
): OutboxRepairOperationState {
|
|
14
|
+
if (
|
|
15
|
+
value === OutboxRepairOperationState.Planned ||
|
|
16
|
+
value === OutboxRepairOperationState.Applied
|
|
17
|
+
) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Unknown outbox repair operation state: ${String(value)}.`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A repair is planned once, then durably applied; applied is terminal. */
|
|
24
|
+
export function canTransitionOutboxRepairOperation(
|
|
25
|
+
from: OutboxRepairOperationState,
|
|
26
|
+
to: OutboxRepairOperationState,
|
|
27
|
+
): boolean {
|
|
28
|
+
return from === OutboxRepairOperationState.Planned &&
|
|
29
|
+
to === OutboxRepairOperationState.Applied;
|
|
30
|
+
}
|
|
@@ -59,6 +59,24 @@ import {
|
|
|
59
59
|
type RuntimeSandboxProvider,
|
|
60
60
|
runtimeSandboxProviderForResourceKind,
|
|
61
61
|
} from './runtime-sandbox-operation';
|
|
62
|
+
import {
|
|
63
|
+
AmbiguousModalCreateStatus,
|
|
64
|
+
readAmbiguousModalCreateStatus,
|
|
65
|
+
} from './ambiguous-modal-create-state';
|
|
66
|
+
import {
|
|
67
|
+
ComputeBillingJobStatus,
|
|
68
|
+
readComputeBillingJobStatus,
|
|
69
|
+
} from './compute-billing-job-state';
|
|
70
|
+
import {
|
|
71
|
+
SandboxCleanupJobStatus,
|
|
72
|
+
readSandboxCleanupJobStatus,
|
|
73
|
+
} from './sandbox-cleanup-job-state';
|
|
74
|
+
export {
|
|
75
|
+
OutboxRepairOperationState,
|
|
76
|
+
canTransitionOutboxRepairOperation,
|
|
77
|
+
readOutboxRepairOperationState,
|
|
78
|
+
} from './outbox-repair-operation-state';
|
|
79
|
+
export type { OutboxRepairOperationState as OutboxRepairOperationStateType } from './outbox-repair-operation-state';
|
|
62
80
|
import {
|
|
63
81
|
TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
|
|
64
82
|
type ToolExecutionErrorSchemaVersion,
|
|
@@ -10245,7 +10263,7 @@ export async function recordPostgresSchedulerAmbiguousModalCreateIntent(
|
|
|
10245
10263
|
},
|
|
10246
10264
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
10247
10265
|
): Promise<boolean> {
|
|
10248
|
-
const recorded = await client.query<{ run_id: string }>(
|
|
10266
|
+
const recorded = await client.query<{ run_id: string; status: unknown }>(
|
|
10249
10267
|
`
|
|
10250
10268
|
INSERT INTO ${tableName('ambiguous_modal_creates', options)} (
|
|
10251
10269
|
run_id, attempt, modal_app_id, environment, tags_json, sandbox_capacity_lease_id
|
|
@@ -10263,7 +10281,7 @@ export async function recordPostgresSchedulerAmbiguousModalCreateIntent(
|
|
|
10263
10281
|
sandbox_capacity_lease_id = EXCLUDED.sandbox_capacity_lease_id,
|
|
10264
10282
|
status = 'pending',
|
|
10265
10283
|
resolved_at = NULL
|
|
10266
|
-
RETURNING run_id
|
|
10284
|
+
RETURNING run_id, status
|
|
10267
10285
|
`,
|
|
10268
10286
|
[
|
|
10269
10287
|
input.runId,
|
|
@@ -10275,6 +10293,14 @@ export async function recordPostgresSchedulerAmbiguousModalCreateIntent(
|
|
|
10275
10293
|
input.sandboxCapacityLeaseId?.trim() || null,
|
|
10276
10294
|
],
|
|
10277
10295
|
);
|
|
10296
|
+
for (const row of recorded.rows) {
|
|
10297
|
+
if (
|
|
10298
|
+
readAmbiguousModalCreateStatus(row.status) !==
|
|
10299
|
+
AmbiguousModalCreateStatus.Pending
|
|
10300
|
+
) {
|
|
10301
|
+
throw new Error('Recorded ambiguous Modal create intent was not pending.');
|
|
10302
|
+
}
|
|
10303
|
+
}
|
|
10278
10304
|
return recorded.rows.length > 0;
|
|
10279
10305
|
}
|
|
10280
10306
|
|
|
@@ -10302,6 +10328,7 @@ export async function resolvePostgresSchedulerAmbiguousModalCreateIntent(
|
|
|
10302
10328
|
}
|
|
10303
10329
|
|
|
10304
10330
|
export type PostgresSchedulerAmbiguousModalCreateIntent = {
|
|
10331
|
+
status: typeof AmbiguousModalCreateStatus.Pending;
|
|
10305
10332
|
runId: string;
|
|
10306
10333
|
attempt: number;
|
|
10307
10334
|
modalAppId: string;
|
|
@@ -10329,6 +10356,7 @@ export async function listPostgresSchedulerAmbiguousModalCreateIntents(
|
|
|
10329
10356
|
const minimumAgeMs = Math.max(0, Math.floor(input.minimumAgeMs));
|
|
10330
10357
|
const rows = await client.query<{
|
|
10331
10358
|
run_id: string;
|
|
10359
|
+
status: unknown;
|
|
10332
10360
|
attempt: number;
|
|
10333
10361
|
modal_app_id: string;
|
|
10334
10362
|
environment: 'preview' | 'production';
|
|
@@ -10337,7 +10365,7 @@ export async function listPostgresSchedulerAmbiguousModalCreateIntents(
|
|
|
10337
10365
|
created_at_ms: number;
|
|
10338
10366
|
}>(
|
|
10339
10367
|
`
|
|
10340
|
-
SELECT run_id, attempt, modal_app_id, environment, tags_json, sandbox_capacity_lease_id,
|
|
10368
|
+
SELECT run_id, status, attempt, modal_app_id, environment, tags_json, sandbox_capacity_lease_id,
|
|
10341
10369
|
floor(extract(epoch FROM created_at) * 1000)::bigint AS created_at_ms
|
|
10342
10370
|
FROM ${tableName('ambiguous_modal_creates', options)}
|
|
10343
10371
|
WHERE status = 'pending'
|
|
@@ -10348,15 +10376,24 @@ export async function listPostgresSchedulerAmbiguousModalCreateIntents(
|
|
|
10348
10376
|
`,
|
|
10349
10377
|
[input.environment, minimumAgeMs, limit],
|
|
10350
10378
|
);
|
|
10351
|
-
return rows.rows.map((row) =>
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10379
|
+
return rows.rows.map((row) => {
|
|
10380
|
+
const status = readAmbiguousModalCreateStatus(row.status);
|
|
10381
|
+
if (status !== AmbiguousModalCreateStatus.Pending) {
|
|
10382
|
+
throw new Error(
|
|
10383
|
+
`Ambiguous Modal create intent ${row.run_id} was listed in ${status} state.`,
|
|
10384
|
+
);
|
|
10385
|
+
}
|
|
10386
|
+
return {
|
|
10387
|
+
status,
|
|
10388
|
+
runId: row.run_id,
|
|
10389
|
+
attempt: Number(row.attempt),
|
|
10390
|
+
modalAppId: row.modal_app_id,
|
|
10391
|
+
environment: row.environment,
|
|
10392
|
+
tags: row.tags_json,
|
|
10393
|
+
sandboxCapacityLeaseId: row.sandbox_capacity_lease_id,
|
|
10394
|
+
createdAtMs: Number(row.created_at_ms),
|
|
10395
|
+
};
|
|
10396
|
+
});
|
|
10360
10397
|
}
|
|
10361
10398
|
|
|
10362
10399
|
/** Provider-confirmed absence is terminal for one pre-create selector. */
|
|
@@ -11743,6 +11780,7 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
11743
11780
|
}
|
|
11744
11781
|
|
|
11745
11782
|
export type PostgresSchedulerComputeBillingJob = {
|
|
11783
|
+
status: typeof ComputeBillingJobStatus.Settling;
|
|
11746
11784
|
runId: string;
|
|
11747
11785
|
attemptCount: number;
|
|
11748
11786
|
failureCount: number;
|
|
@@ -11994,6 +12032,7 @@ export async function claimPostgresSchedulerComputeBillingJobs(
|
|
|
11994
12032
|
);
|
|
11995
12033
|
const claimed = await client.query<{
|
|
11996
12034
|
run_id: string;
|
|
12035
|
+
status: unknown;
|
|
11997
12036
|
attempt_count: number;
|
|
11998
12037
|
failure_count: number;
|
|
11999
12038
|
run_status: string;
|
|
@@ -12045,6 +12084,7 @@ export async function claimPostgresSchedulerComputeBillingJobs(
|
|
|
12045
12084
|
WHERE job.run_id = candidates.run_id
|
|
12046
12085
|
RETURNING
|
|
12047
12086
|
job.run_id,
|
|
12087
|
+
job.status,
|
|
12048
12088
|
job.attempt_count,
|
|
12049
12089
|
job.failure_count,
|
|
12050
12090
|
job.session_bootstrap_approved_at,
|
|
@@ -12052,6 +12092,7 @@ export async function claimPostgresSchedulerComputeBillingJobs(
|
|
|
12052
12092
|
)
|
|
12053
12093
|
SELECT
|
|
12054
12094
|
claimed.run_id,
|
|
12095
|
+
claimed.status,
|
|
12055
12096
|
claimed.attempt_count,
|
|
12056
12097
|
claimed.failure_count,
|
|
12057
12098
|
claimed.session_bootstrap_approved_at,
|
|
@@ -12063,15 +12104,24 @@ export async function claimPostgresSchedulerComputeBillingJobs(
|
|
|
12063
12104
|
`,
|
|
12064
12105
|
[input.leaseOwner, limit, leaseSeconds],
|
|
12065
12106
|
);
|
|
12066
|
-
return claimed.rows.map((row) =>
|
|
12067
|
-
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
|
|
12107
|
+
return claimed.rows.map((row) => {
|
|
12108
|
+
const status = readComputeBillingJobStatus(row.status);
|
|
12109
|
+
if (status !== ComputeBillingJobStatus.Settling) {
|
|
12110
|
+
throw new Error(
|
|
12111
|
+
`Compute billing job ${row.run_id} was claimed in ${status} state.`,
|
|
12112
|
+
);
|
|
12113
|
+
}
|
|
12114
|
+
return {
|
|
12115
|
+
status,
|
|
12116
|
+
runId: row.run_id,
|
|
12117
|
+
attemptCount: row.attempt_count,
|
|
12118
|
+
failureCount: row.failure_count,
|
|
12119
|
+
success: row.run_status === 'completed',
|
|
12120
|
+
bootstrapSessionPending:
|
|
12121
|
+
row.session_bootstrap_approved_at !== null &&
|
|
12122
|
+
row.session_bootstrap_completed_at === null,
|
|
12123
|
+
};
|
|
12124
|
+
});
|
|
12075
12125
|
}
|
|
12076
12126
|
|
|
12077
12127
|
/**
|
|
@@ -12476,6 +12526,7 @@ function protectedDetachedResourceSql(
|
|
|
12476
12526
|
}
|
|
12477
12527
|
|
|
12478
12528
|
export type PostgresSchedulerSandboxCleanupJob = {
|
|
12529
|
+
status: typeof SandboxCleanupJobStatus.Deleting;
|
|
12479
12530
|
provider: 'daytona' | 'modal';
|
|
12480
12531
|
sandboxId: string;
|
|
12481
12532
|
runId: string;
|
|
@@ -12951,6 +13002,7 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
|
|
|
12951
13002
|
if (!adoptionWork.rows[0].pending && !adoptionWork.rows[0].claimable)
|
|
12952
13003
|
return [];
|
|
12953
13004
|
const claimed = await client.query<{
|
|
13005
|
+
status: unknown;
|
|
12954
13006
|
provider: 'daytona' | 'modal';
|
|
12955
13007
|
sandbox_id: string;
|
|
12956
13008
|
run_id: string;
|
|
@@ -13031,6 +13083,7 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
|
|
|
13031
13083
|
target.run_id,
|
|
13032
13084
|
target.attempt,
|
|
13033
13085
|
target.environment,
|
|
13086
|
+
target.status,
|
|
13034
13087
|
target.routing_domain,
|
|
13035
13088
|
target.attempt_count,
|
|
13036
13089
|
target.failure_count,
|
|
@@ -13108,18 +13161,27 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
|
|
|
13108
13161
|
input.throughAttempt ?? null,
|
|
13109
13162
|
],
|
|
13110
13163
|
);
|
|
13111
|
-
return claimed.rows.map((row) =>
|
|
13112
|
-
|
|
13113
|
-
|
|
13114
|
-
|
|
13115
|
-
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13164
|
+
return claimed.rows.map((row) => {
|
|
13165
|
+
const status = readSandboxCleanupJobStatus(row.status);
|
|
13166
|
+
if (status !== SandboxCleanupJobStatus.Deleting) {
|
|
13167
|
+
throw new Error(
|
|
13168
|
+
`Sandbox cleanup job ${row.sandbox_id} was claimed in ${status} state.`,
|
|
13169
|
+
);
|
|
13170
|
+
}
|
|
13171
|
+
return {
|
|
13172
|
+
status,
|
|
13173
|
+
provider: row.provider,
|
|
13174
|
+
sandboxId: row.sandbox_id,
|
|
13175
|
+
runId: row.run_id,
|
|
13176
|
+
attempt: row.attempt,
|
|
13177
|
+
environment: row.environment,
|
|
13178
|
+
routingDomain: row.routing_domain,
|
|
13179
|
+
attemptCount: row.attempt_count,
|
|
13180
|
+
failureCount: row.failure_count,
|
|
13181
|
+
runStatusAtClaim: row.terminal_status,
|
|
13182
|
+
diagnostic: row.diagnostic_json,
|
|
13183
|
+
};
|
|
13184
|
+
});
|
|
13123
13185
|
}
|
|
13124
13186
|
|
|
13125
13187
|
/**
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
RuntimeSandboxOperationState,
|
|
14
14
|
RuntimeSandboxProvider,
|
|
15
15
|
} from './runtime-sandbox-operation';
|
|
16
|
+
import type { ComputeBillingJobStatus } from './compute-billing-job-state';
|
|
17
|
+
import { readComputeBillingJobStatus } from './compute-billing-job-state';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* The baseline when an organization has no current override. It sits above
|
|
@@ -1437,7 +1439,7 @@ export type RuntimeSandboxCapacityTestEvidence = {
|
|
|
1437
1439
|
cleanupJobs: number;
|
|
1438
1440
|
confirmedCleanupJobs: number;
|
|
1439
1441
|
pendingCleanup: number;
|
|
1440
|
-
computeJobStatus:
|
|
1442
|
+
computeJobStatus: ComputeBillingJobStatus | null;
|
|
1441
1443
|
hasComputeIntent: boolean;
|
|
1442
1444
|
recordedResources: number;
|
|
1443
1445
|
firstAcquiredAtMs: number | null;
|
|
@@ -1544,5 +1546,11 @@ export async function readRuntimeSandboxCapacityTestEvidence(
|
|
|
1544
1546
|
const evidence = result.rows[0]?.evidence;
|
|
1545
1547
|
if (!evidence)
|
|
1546
1548
|
throw new Error('Sandbox capacity evidence query returned no snapshot.');
|
|
1549
|
+
if (evidence.run) {
|
|
1550
|
+
evidence.run.computeJobStatus =
|
|
1551
|
+
evidence.run.computeJobStatus === null
|
|
1552
|
+
? null
|
|
1553
|
+
: readComputeBillingJobStatus(evidence.run.computeJobStatus);
|
|
1554
|
+
}
|
|
1547
1555
|
return input.runId && evidence.run === null ? null : evidence;
|
|
1548
1556
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Persisted in sandbox_cleanup_jobs_v2.status. */
|
|
2
|
+
export const SandboxCleanupJobStatus = {
|
|
3
|
+
Pending: 'pending',
|
|
4
|
+
Deleting: 'deleting',
|
|
5
|
+
Deleted: 'deleted',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type SandboxCleanupJobStatus =
|
|
9
|
+
(typeof SandboxCleanupJobStatus)[keyof typeof SandboxCleanupJobStatus];
|
|
10
|
+
|
|
11
|
+
/** Decode database text at the scheduler boundary; unknown states fail closed. */
|
|
12
|
+
export function readSandboxCleanupJobStatus(
|
|
13
|
+
value: unknown,
|
|
14
|
+
): SandboxCleanupJobStatus {
|
|
15
|
+
if (
|
|
16
|
+
value === SandboxCleanupJobStatus.Pending ||
|
|
17
|
+
value === SandboxCleanupJobStatus.Deleting ||
|
|
18
|
+
value === SandboxCleanupJobStatus.Deleted
|
|
19
|
+
) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unknown sandbox cleanup job status: ${String(value)}.`);
|
|
23
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Persisted product notification destination lifecycle. */
|
|
2
|
+
export const PRODUCT_NOTIFICATION_DESTINATION_STATUS = {
|
|
3
|
+
pendingVerification: 'pending_verification',
|
|
4
|
+
active: 'active',
|
|
5
|
+
disabled: 'disabled',
|
|
6
|
+
needsAttention: 'needs_attention',
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export type ProductNotificationDestinationStatus =
|
|
10
|
+
(typeof PRODUCT_NOTIFICATION_DESTINATION_STATUS)[keyof typeof PRODUCT_NOTIFICATION_DESTINATION_STATUS];
|
|
11
|
+
|
|
12
|
+
export const PRODUCT_NOTIFICATION_DESTINATION_TRANSITIONS = {
|
|
13
|
+
pending_verification: ['active', 'disabled', 'needs_attention'],
|
|
14
|
+
active: ['pending_verification', 'disabled', 'needs_attention'],
|
|
15
|
+
disabled: ['pending_verification', 'active'],
|
|
16
|
+
needs_attention: ['pending_verification', 'active', 'disabled'],
|
|
17
|
+
} as const satisfies Record<
|
|
18
|
+
ProductNotificationDestinationStatus,
|
|
19
|
+
readonly ProductNotificationDestinationStatus[]
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export type StoredProductNotificationDestinationStatus =
|
|
23
|
+
| { kind: 'known'; status: ProductNotificationDestinationStatus }
|
|
24
|
+
| { kind: 'unknown'; value: string };
|
|
25
|
+
|
|
26
|
+
export function readProductNotificationDestinationStatus(
|
|
27
|
+
value: string,
|
|
28
|
+
): StoredProductNotificationDestinationStatus {
|
|
29
|
+
switch (value) {
|
|
30
|
+
case PRODUCT_NOTIFICATION_DESTINATION_STATUS.pendingVerification:
|
|
31
|
+
case PRODUCT_NOTIFICATION_DESTINATION_STATUS.active:
|
|
32
|
+
case PRODUCT_NOTIFICATION_DESTINATION_STATUS.disabled:
|
|
33
|
+
case PRODUCT_NOTIFICATION_DESTINATION_STATUS.needsAttention:
|
|
34
|
+
return { kind: 'known', status: value };
|
|
35
|
+
default:
|
|
36
|
+
return { kind: 'unknown', value };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function requireProductNotificationDestinationStatus(
|
|
41
|
+
value: string,
|
|
42
|
+
): ProductNotificationDestinationStatus {
|
|
43
|
+
const stored = readProductNotificationDestinationStatus(value);
|
|
44
|
+
if (stored.kind === 'unknown') {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Unsupported product notification destination status: ${stored.value}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return stored.status;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isProductNotificationDestinationDeliverable(
|
|
53
|
+
value: string,
|
|
54
|
+
): boolean {
|
|
55
|
+
return (
|
|
56
|
+
readProductNotificationDestinationStatus(value).kind === 'known' &&
|
|
57
|
+
value === PRODUCT_NOTIFICATION_DESTINATION_STATUS.active
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function canTransitionProductNotificationDestination(
|
|
62
|
+
current: string,
|
|
63
|
+
next: ProductNotificationDestinationStatus,
|
|
64
|
+
): boolean {
|
|
65
|
+
const stored = readProductNotificationDestinationStatus(current);
|
|
66
|
+
return (
|
|
67
|
+
stored.kind === 'known' &&
|
|
68
|
+
(
|
|
69
|
+
PRODUCT_NOTIFICATION_DESTINATION_TRANSITIONS[
|
|
70
|
+
stored.status
|
|
71
|
+
] as readonly string[]
|
|
72
|
+
).includes(next)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Validate a destination lifecycle write without narrowing persisted schema. */
|
|
77
|
+
export function nextProductNotificationDestinationStatus(
|
|
78
|
+
current: string,
|
|
79
|
+
next: ProductNotificationDestinationStatus,
|
|
80
|
+
): ProductNotificationDestinationStatus {
|
|
81
|
+
const stored = readProductNotificationDestinationStatus(current);
|
|
82
|
+
if (stored.kind === 'unknown') {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Cannot transition product notification destination from unknown status: ${stored.value}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (!canTransitionProductNotificationDestination(current, next)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Invalid product notification destination transition: ${current} -> ${next}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return next;
|
|
93
|
+
}
|