@greatwait/contracts 0.3.1 → 0.3.2
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/attempt-diagnostics.d.ts +1 -1
- package/dist/attempt-diagnostics.js +13 -1
- package/dist/attempt-search.d.ts +2 -0
- package/dist/billing.d.ts +46 -0
- package/dist/billing.js +29 -0
- package/dist/callback-safety.js +5 -1
- package/dist/configuration.d.ts +2 -2
- package/dist/configuration.js +6 -3
- package/dist/delivery.d.ts +12 -1
- package/dist/delivery.js +1 -0
- package/dist/effective-limits.d.ts +4 -4
- package/dist/endpoints.d.ts +29 -0
- package/dist/endpoints.js +32 -1
- package/dist/forecast.d.ts +2 -2
- package/dist/http-request.d.ts +12 -2
- package/dist/http-request.js +31 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/mcp.js +1 -0
- package/dist/operational-config.d.ts +174 -22
- package/dist/operational-config.js +207 -74
- package/dist/operations.d.ts +334 -52
- package/dist/operations.js +195 -34
- package/dist/project-notifications.d.ts +35 -9
- package/dist/project-notifications.js +43 -12
- package/dist/protocol.d.ts +28 -0
- package/dist/protocol.js +23 -0
- package/dist/scheduling-records.d.ts +106 -16
- package/dist/scheduling-records.js +36 -12
- package/dist/scheduling.d.ts +155 -43
- package/dist/scheduling.js +188 -50
- package/dist/service-mode.d.ts +88 -0
- package/dist/service-mode.js +130 -0
- package/dist/tenancy.d.ts +54 -30
- package/dist/tenancy.js +35 -17
- package/dist/timing.d.ts +39 -0
- package/dist/timing.js +51 -0
- package/dist/zod-values.d.ts +1 -1
- package/dist/zod-values.js +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +1 -1
- package/generated/v1/examples/billing.noticesdismiss.request.json +3 -0
- package/generated/v1/examples/billing.noticesdismiss.response.json +6 -0
- package/generated/v1/examples/billing.reconcile.response.json +12 -0
- package/generated/v1/examples/billing.summary.response.json +12 -0
- package/generated/v1/examples/executions.list.response.json +1 -0
- package/generated/v1/examples/limits.read.response.json +1 -1
- package/generated/v1/examples/projectnotifications.rotateSigningKey.response.json +1 -0
- package/generated/v1/examples/projects.list.response.json +0 -6
- package/generated/v1/examples/projects.read.response.json +14 -0
- package/generated/v1/examples/schedules.list.response.json +1 -3
- package/generated/v1/examples/schedules.summary.response.json +174 -0
- package/generated/v1/examples/timing.summary.response.json +33 -1
- package/generated/v1/manifest.json +79 -2
- package/generated/v1/openapi.json +4084 -2023
- package/generated/v1/policy-matrix.json +22 -1
- package/generated/v1/schemas/attempts.list.response.json +10 -0
- package/generated/v1/schemas/attempts.read.response.json +10 -0
- package/generated/v1/schemas/attempts.search.response.json +4 -0
- package/generated/v1/schemas/billing.notices.query.json +4 -0
- package/generated/v1/schemas/billing.noticesdismiss.request.json +12 -0
- package/generated/v1/schemas/billing.noticesdismiss.response.json +22 -0
- package/generated/v1/schemas/billing.reconcile.response.json +43 -0
- package/generated/v1/schemas/billing.summary.response.json +43 -0
- package/generated/v1/schemas/endpoints.list.query.json +15 -0
- package/generated/v1/schemas/executions.list.response.json +4 -0
- package/generated/v1/schemas/occurrences.list.response.json +4 -0
- package/generated/v1/schemas/occurrences.read.response.json +4 -0
- package/generated/v1/schemas/occurrences.redeliver.response.json +10 -0
- package/generated/v1/schemas/projectnotifications.read.response.json +4 -0
- package/generated/v1/schemas/projectnotifications.rotateSigningKey.response.json +4 -0
- package/generated/v1/schemas/projectnotifications.update.request.json +41 -17
- package/generated/v1/schemas/projectnotifications.update.response.json +4 -0
- package/generated/v1/schemas/projects.list.response.json +0 -27
- package/generated/v1/schemas/projects.read.response-headers.json +11 -0
- package/generated/v1/schemas/projects.read.response.json +79 -0
- package/generated/v1/schemas/schedules.archive.response.json +8 -0
- package/generated/v1/schemas/schedules.cancel.response.json +8 -0
- package/generated/v1/schemas/schedules.create.request.json +1 -1
- package/generated/v1/schemas/schedules.create.response.json +8 -0
- package/generated/v1/schemas/schedules.list.query.json +6 -0
- package/generated/v1/schemas/schedules.list.response.json +4 -0
- package/generated/v1/schemas/schedules.pause.response.json +8 -0
- package/generated/v1/schemas/schedules.read.response.json +8 -0
- package/generated/v1/schemas/schedules.replace.request.json +1 -1
- package/generated/v1/schemas/schedules.replace.response.json +8 -0
- package/generated/v1/schemas/schedules.resume.response.json +8 -0
- package/generated/v1/schemas/schedules.summary.query.json +11 -0
- package/generated/v1/schemas/schedules.summary.response.json +144 -0
- package/generated/v1/schemas/timing.summary.response.json +69 -0
- package/generated/v1/snippets/billing.noticesdismiss.json +6 -0
- package/generated/v1/snippets/projectnotifications.rotateSigningKey.json +1 -1
- package/generated/v1/snippets/projects.read.json +6 -0
- package/generated/v1/snippets/schedules.summary.json +6 -0
- package/package.json +1 -1
package/dist/scheduling.js
CHANGED
|
@@ -3,11 +3,13 @@ import { executionSelectionSchema, normalizeExecutionSelection, } from "./execut
|
|
|
3
3
|
import { httpRequestSchema, publicHttpRequestSchema } from "./http-request.js";
|
|
4
4
|
import { FIXED_INTERVAL_MIN_SECONDS, FIXED_INTERVAL_MAX_SECONDS, MAX_INLINE_PAYLOAD_BYTES, } from "./configuration-bounds.js";
|
|
5
5
|
import { z } from "./zod.js";
|
|
6
|
+
import { SERVICE_MODE_POLICY } from "./operational-config.js";
|
|
7
|
+
import { REQUESTED_LATENCY_CLASSES, oneOffSpacingMs, resolveService, } from "./service-mode.js";
|
|
6
8
|
import { externalIdentifierSchema, scheduleTagsSchema, } from "./host-correlation.js";
|
|
7
9
|
import { attemptIdSchema, deliveryIdSchema, endpointIdSchema, occurrenceIdSchema, scheduleIdSchema, } from "./identifiers.js";
|
|
8
10
|
import { CALLBACK_ATTEMPT_DIAGNOSTICS } from "./delivery.js";
|
|
9
11
|
import { callbackUrlSchema, signingSecretNoticeSchema } from "./endpoints.js";
|
|
10
|
-
import { pageRequestSchema, pageSchema, versionSchema, } from "./protocol.js";
|
|
12
|
+
import { pageRequestSchema, pageSchema, projectedFreshnessSchema, versionSchema, } from "./protocol.js";
|
|
11
13
|
import { callbackTriggerSchema, } from "./signatures.js";
|
|
12
14
|
import { durationSecondsSchema, timeZoneSchema, timestampSchema } from "./time.js";
|
|
13
15
|
/**
|
|
@@ -104,6 +106,13 @@ export function isTerminalScheduleStatus(status) {
|
|
|
104
106
|
*/
|
|
105
107
|
export const LATENCY_CLASSES = ["realtime", "bulk"];
|
|
106
108
|
export const latencyClassSchema = /* @__PURE__ */ (() => z.enum(LATENCY_CLASSES))();
|
|
109
|
+
/**
|
|
110
|
+
* The service mode a request asks for (W12-02, FXO-3171): `auto`, or one of
|
|
111
|
+
* the concrete classes. Omission means `auto`. A read reports the concrete
|
|
112
|
+
* class an occurrence resolved to as `latencyClass`, and what was asked for as
|
|
113
|
+
* `requestedLatencyClass`; see `service-mode.ts` for the resolution.
|
|
114
|
+
*/
|
|
115
|
+
export const requestedLatencyClassSchema = /* @__PURE__ */ (() => z.enum(REQUESTED_LATENCY_CLASSES))();
|
|
107
116
|
/**
|
|
108
117
|
* What happens when an occurrence reaches its first attempt after its planned
|
|
109
118
|
* instant.
|
|
@@ -612,13 +621,11 @@ export const SCHEDULING_BOUNDS = {
|
|
|
612
621
|
* was the copied scheduler's `MAX_INTERVAL_SECONDS`, carried over on the
|
|
613
622
|
* belief that "a Durable Object alarm is not arbitrarily far in the future".
|
|
614
623
|
* Cloudflare documents no maximum distance for `setAlarm()`, and the belief
|
|
615
|
-
* predates the PostgreSQL authority besides: the durable fact is a row
|
|
616
|
-
* the alarm is only what wakes a bucket to read it.
|
|
624
|
+
* predates the PostgreSQL authority besides: the durable fact is a row.
|
|
617
625
|
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
* — so an alarm that is somehow lost costs lateness rather than the run.
|
|
626
|
+
* Since W13-03 there is no alarm at all. The Node runtime polls
|
|
627
|
+
* `core.occurrence` for rows whose `due_at` has passed, so a year-distant
|
|
628
|
+
* occurrence is the same row read on the same poll as a minute-distant one.
|
|
622
629
|
*
|
|
623
630
|
* Widening a bound is additive by `classifyChange` and narrowing it is
|
|
624
631
|
* breaking, so this direction is the safe one to move in.
|
|
@@ -689,17 +696,15 @@ export const SCHEDULING_BOUNDS = {
|
|
|
689
696
|
*/
|
|
690
697
|
payloadBytes: MAX_INLINE_PAYLOAD_BYTES,
|
|
691
698
|
/**
|
|
692
|
-
* How soon a one-shot has to be for `
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
* who disagrees says so — `latencyClass` on the request is what makes this a
|
|
700
|
-
* default rather than a rule.
|
|
699
|
+
* How soon a one-shot has to be for `auto` to resolve it to `realtime`, in
|
|
700
|
+
* seconds, exclusive: a one-shot planned less than a minute after it was
|
|
701
|
+
* accepted is realtime, and one planned exactly a minute out is bulk
|
|
702
|
+
* (W12-02, FXO-3171; owner decision 2026-10-14). The single owner is
|
|
703
|
+
* `SERVICE_MODE_POLICY.autoRealtimeBelowMs`. A caller who disagrees says so
|
|
704
|
+
* — `latencyClass` on the request is what makes this a default rather than a
|
|
705
|
+
* rule.
|
|
701
706
|
*/
|
|
702
|
-
realtimeOneShotSeconds:
|
|
707
|
+
realtimeOneShotSeconds: SERVICE_MODE_POLICY.autoRealtimeBelowMs / 1000,
|
|
703
708
|
};
|
|
704
709
|
/* -------------------------------------------------------------------------- */
|
|
705
710
|
/* Recurrence */
|
|
@@ -881,7 +886,18 @@ export const scheduleSchema = /* @__PURE__ */ (() => z.object({
|
|
|
881
886
|
endpointUrl: z.string().url().optional(),
|
|
882
887
|
generation: generationSchema,
|
|
883
888
|
id: scheduleIdSchema,
|
|
889
|
+
/**
|
|
890
|
+
* The concrete class this schedule's occurrences are served and charged
|
|
891
|
+
* as: what `auto` resolved to, or the class the caller named.
|
|
892
|
+
*/
|
|
884
893
|
latencyClass: latencyClassSchema,
|
|
894
|
+
/**
|
|
895
|
+
* The mode the caller asked for (W12-02): `auto` when the request named
|
|
896
|
+
* none. Absent on a schedule accepted before the mode was recorded; its
|
|
897
|
+
* `latencyClass` is what it was accepted as, and a replacement that leaves
|
|
898
|
+
* its timing alone keeps it.
|
|
899
|
+
*/
|
|
900
|
+
requestedLatencyClass: requestedLatencyClassSchema.optional(),
|
|
885
901
|
/**
|
|
886
902
|
* What this schedule does with an occurrence that reaches its first attempt
|
|
887
903
|
* late. Present on every schedule so a caller never has to duplicate the
|
|
@@ -946,6 +962,76 @@ export const scheduleSchema = /* @__PURE__ */ (() => z.object({
|
|
|
946
962
|
* is authoritative, and detail is one call away.
|
|
947
963
|
*/
|
|
948
964
|
export const schedulePageSchema = /* @__PURE__ */ (() => pageSchema(scheduleSchema))();
|
|
965
|
+
/** Local days a schedule summary covers, today included (W9-80, FXO-3446). */
|
|
966
|
+
export const SCHEDULE_SUMMARY_DAYS = 30;
|
|
967
|
+
/** Auto-paused schedules a summary names beside their count. */
|
|
968
|
+
export const SCHEDULE_SUMMARY_PAUSED_SAMPLE = 5;
|
|
969
|
+
/** The oldest a served schedule summary may be, in seconds. */
|
|
970
|
+
export const SCHEDULE_SUMMARY_MAX_AGE_SECONDS = 300;
|
|
971
|
+
const localDateSchema = /* @__PURE__ */ (() => z
|
|
972
|
+
.string()
|
|
973
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/u, "must be a calendar date, YYYY-MM-DD"))();
|
|
974
|
+
/** A schedule summary request: the reader's time zone for the day buckets. */
|
|
975
|
+
export const scheduleSummaryRequestSchema = /* @__PURE__ */ (() => z.object({
|
|
976
|
+
timeZone: timeZoneSchema
|
|
977
|
+
.default("UTC")
|
|
978
|
+
.describe("IANA time zone the per-day buckets are counted in; defaults to UTC."),
|
|
979
|
+
}))();
|
|
980
|
+
/**
|
|
981
|
+
* Whole-project schedule counts for the caller's project and environment
|
|
982
|
+
* (W9-80, FXO-3446).
|
|
983
|
+
*
|
|
984
|
+
* **Exact as of `freshness.asOf`, and at most five minutes old.** The counts
|
|
985
|
+
* read every retained, unarchived schedule in one statement, which is too
|
|
986
|
+
* costly to repeat on every view of a large project, so an answer is computed
|
|
987
|
+
* on read and reused for up to {@link SCHEDULE_SUMMARY_MAX_AGE_SECONDS}
|
|
988
|
+
* seconds per project, environment and time zone. `freshness` is always
|
|
989
|
+
* `projected`: `asOf` is when the counts were taken and `lagSeconds` how long
|
|
990
|
+
* ago that was, so a client can say "as of 2 minutes ago" rather than present
|
|
991
|
+
* the numbers as live. Counts cover retained schedules only: retention removes
|
|
992
|
+
* cancelled schedules and one-offs that ran once their history horizon passes.
|
|
993
|
+
*/
|
|
994
|
+
export const scheduleSummarySchema = /* @__PURE__ */ (() => z.object({
|
|
995
|
+
counts: z.object({
|
|
996
|
+
active: z.number().int().nonnegative(),
|
|
997
|
+
paused: z.number().int().nonnegative(),
|
|
998
|
+
completed: z.number().int().nonnegative(),
|
|
999
|
+
cancelled: z.number().int().nonnegative(),
|
|
1000
|
+
}),
|
|
1001
|
+
/**
|
|
1002
|
+
* Paused schedules whose newest run paused them after repeated failures,
|
|
1003
|
+
* and up to five of them (id, name and why), most recently paused first.
|
|
1004
|
+
* Read a schedule, or list `status=paused`, for the rest.
|
|
1005
|
+
*/
|
|
1006
|
+
pausedAfterFailures: z.object({
|
|
1007
|
+
count: z.number().int().nonnegative(),
|
|
1008
|
+
schedules: z
|
|
1009
|
+
.array(z.object({
|
|
1010
|
+
id: scheduleIdSchema,
|
|
1011
|
+
name: z.string().optional(),
|
|
1012
|
+
autoPauseReason: autoPauseReasonSchema,
|
|
1013
|
+
}))
|
|
1014
|
+
.max(SCHEDULE_SUMMARY_PAUSED_SAMPLE),
|
|
1015
|
+
}),
|
|
1016
|
+
/**
|
|
1017
|
+
* One entry per local day of the window, oldest first, zero days
|
|
1018
|
+
* included: schedules created that day, and schedules that finished that
|
|
1019
|
+
* day (completed by their final run, or cancelled).
|
|
1020
|
+
*/
|
|
1021
|
+
days: z
|
|
1022
|
+
.array(z.object({
|
|
1023
|
+
date: localDateSchema,
|
|
1024
|
+
created: z.number().int().nonnegative(),
|
|
1025
|
+
finished: z.number().int().nonnegative(),
|
|
1026
|
+
}))
|
|
1027
|
+
.length(SCHEDULE_SUMMARY_DAYS),
|
|
1028
|
+
window: z.object({
|
|
1029
|
+
from: localDateSchema,
|
|
1030
|
+
to: localDateSchema,
|
|
1031
|
+
timeZone: timeZoneSchema,
|
|
1032
|
+
}),
|
|
1033
|
+
freshness: projectedFreshnessSchema,
|
|
1034
|
+
}))();
|
|
949
1035
|
function hasCompleteTimingEvidence(value) {
|
|
950
1036
|
return ((value.firstAttemptStartedAt === undefined) ===
|
|
951
1037
|
(value.firstAttemptLatenessMs === undefined) &&
|
|
@@ -970,7 +1056,12 @@ const timingEvidenceShape = {
|
|
|
970
1056
|
.optional(),
|
|
971
1057
|
timingEligibilityReason: z.string().min(1).max(128).optional(),
|
|
972
1058
|
/** Policy pinned when trusted first-attempt timing evidence is recorded. */
|
|
973
|
-
timingPolicyVersion: z
|
|
1059
|
+
timingPolicyVersion: z
|
|
1060
|
+
.string()
|
|
1061
|
+
.min(1)
|
|
1062
|
+
.max(64)
|
|
1063
|
+
.optional()
|
|
1064
|
+
.describe("Timing policy pinned with trusted first-outbound evidence. Banded occurrences use timing-v2-service-bands (realtime 1000ms, short bulk 10000ms, bulk 60000ms from plannedFor); legacy occurrences without a band retain their recorded policy, including payg-v1, and 5000ms/15000ms thresholds. Absent timing evidence means unknown."),
|
|
974
1065
|
};
|
|
975
1066
|
/**
|
|
976
1067
|
* One concrete occurrence of a schedule.
|
|
@@ -989,6 +1080,13 @@ export const occurrenceSchema = /* @__PURE__ */ (() => z
|
|
|
989
1080
|
/** When the occurrence was planned to fire. Never earlier than this. */
|
|
990
1081
|
plannedFor: timestampSchema,
|
|
991
1082
|
schedule: scheduleIdSchema,
|
|
1083
|
+
/**
|
|
1084
|
+
* The schedule's name, on the project executions listing
|
|
1085
|
+
* (`executions.list`) only, read from the same schedule rows that page
|
|
1086
|
+
* already joins — so a list of runs can say whose they are without a
|
|
1087
|
+
* schedule read per row (W9-80). Absent elsewhere.
|
|
1088
|
+
*/
|
|
1089
|
+
scheduleName: z.string().optional(),
|
|
992
1090
|
sequence: z.number().int().positive(),
|
|
993
1091
|
/** Present exactly when the occurrence is in a terminal state. */
|
|
994
1092
|
settledAt: timestampSchema.optional(),
|
|
@@ -1151,6 +1249,22 @@ export const attemptSchema = /* @__PURE__ */ (() => z
|
|
|
1151
1249
|
response: attemptResponseSchema.optional(),
|
|
1152
1250
|
request: attemptRequestSchema.optional(),
|
|
1153
1251
|
requestState: z.enum(["captured", "expired", "not_captured"]).optional(),
|
|
1252
|
+
/**
|
|
1253
|
+
* Why a successful attempt's request cannot be shown, when
|
|
1254
|
+
* `requestState` is `not_captured` for that reason. A successful
|
|
1255
|
+
* attempt's request is rebuilt from the schedule's payload and its
|
|
1256
|
+
* endpoint, so it is shown only while neither has changed since the run
|
|
1257
|
+
* and the payload still exists; it is never shown from a later version.
|
|
1258
|
+
* Absent otherwise.
|
|
1259
|
+
*/
|
|
1260
|
+
requestUnavailable: z
|
|
1261
|
+
.enum([
|
|
1262
|
+
"schedule_changed",
|
|
1263
|
+
"endpoint_changed",
|
|
1264
|
+
"endpoint_deleted",
|
|
1265
|
+
"payload_deleted",
|
|
1266
|
+
])
|
|
1267
|
+
.optional(),
|
|
1154
1268
|
/** The fixed diagnostic-header allowlist, when a response carried any. */
|
|
1155
1269
|
responseHeaders: z.array(attemptResponseHeaderSchema).max(8).optional(),
|
|
1156
1270
|
/** Absent when no response was received at all. */
|
|
@@ -1372,20 +1486,27 @@ export const scheduleRequestSchema = /* @__PURE__ */ (() => z
|
|
|
1372
1486
|
*/
|
|
1373
1487
|
url: callbackUrlSchema().optional(),
|
|
1374
1488
|
/**
|
|
1375
|
-
* How promptly this schedule's occurrences expect to be served
|
|
1489
|
+
* How promptly this schedule's occurrences expect to be served: `auto`,
|
|
1490
|
+
* `bulk` or `realtime` (W12-02, FXO-3171).
|
|
1376
1491
|
*
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1492
|
+
* Omitting it means `auto`, the ordinary case. `auto` makes a one-shot
|
|
1493
|
+
* planned less than a minute after acceptance `realtime` and everything
|
|
1494
|
+
* else `bulk`; a recurrence is classified by the gap between its runs, so
|
|
1495
|
+
* it is `bulk`. Naming `realtime` or `bulk` always gets that class — for
|
|
1379
1496
|
* the caller whose nightly cron really is a human waiting, or whose
|
|
1380
|
-
* thirty-second one-shot really is a campaign
|
|
1381
|
-
* guess about intent and cannot be a correct one.
|
|
1497
|
+
* thirty-second one-shot really is a campaign.
|
|
1382
1498
|
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1499
|
+
* Bulk work becomes eligible on a 5-second boundary when its runs are
|
|
1500
|
+
* under five minutes apart (or it is due within five minutes) and on a
|
|
1501
|
+
* 50-second boundary otherwise; realtime on a 250-millisecond boundary.
|
|
1502
|
+
* The occurrence's `plannedFor` is unchanged. Each occurrence is charged
|
|
1503
|
+
* as the class it resolved to.
|
|
1504
|
+
*
|
|
1505
|
+
* On a replacement, omission keeps the schedule's mode. Changing the
|
|
1506
|
+
* timing resolves again under that mode; changing anything else keeps
|
|
1507
|
+
* the class and price the schedule was accepted under.
|
|
1387
1508
|
*/
|
|
1388
|
-
latencyClass:
|
|
1509
|
+
latencyClass: requestedLatencyClassSchema.optional(),
|
|
1389
1510
|
/**
|
|
1390
1511
|
* What to do when an occurrence reaches its first attempt late.
|
|
1391
1512
|
*
|
|
@@ -1540,29 +1661,46 @@ export function deriveDeliverySettings(request) {
|
|
|
1540
1661
|
onOverlap: request.delivery?.onOverlap ?? DEFAULT_DELIVERY_OVERLAP_POLICY,
|
|
1541
1662
|
};
|
|
1542
1663
|
}
|
|
1664
|
+
/**
|
|
1665
|
+
* The create request as its idempotency fingerprint reads it (W12-02).
|
|
1666
|
+
*
|
|
1667
|
+
* An omitted `latencyClass` means `auto`, so the two spellings are one request
|
|
1668
|
+
* and must fingerprint alike. They are made alike by writing the default the
|
|
1669
|
+
* way the request has always been able to write it — by omission — so a
|
|
1670
|
+
* receipt issued before `auto` existed still recognizes its own retry.
|
|
1671
|
+
*/
|
|
1672
|
+
export function fingerprintedScheduleRequest(request) {
|
|
1673
|
+
if (request.latencyClass !== "auto")
|
|
1674
|
+
return request;
|
|
1675
|
+
const { latencyClass: _auto, ...rest } = request;
|
|
1676
|
+
void _auto;
|
|
1677
|
+
return rest;
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* The concrete class a create request resolves to at `nowMs`, its acceptance
|
|
1681
|
+
* time. Here so REST, MCP and the dashboard's preview reach the same answer
|
|
1682
|
+
* for the same body; `service-mode.ts` owns the rule.
|
|
1683
|
+
*
|
|
1684
|
+
* A recurrence is classified by the gap between runs, at least a minute for
|
|
1685
|
+
* anything this surface accepts, so `auto` makes it `bulk` without evaluating
|
|
1686
|
+
* a cron expression here. `nowMs` is a parameter so the boundary can be asked
|
|
1687
|
+
* about one millisecond either side of it.
|
|
1688
|
+
*/
|
|
1543
1689
|
export function deriveLatencyClass(request, nowMs) {
|
|
1544
|
-
|
|
1545
|
-
return request.latencyClass;
|
|
1546
|
-
}
|
|
1690
|
+
const requested = request.latencyClass ?? "auto";
|
|
1547
1691
|
if (request.cron !== undefined || request.everySeconds !== undefined) {
|
|
1548
|
-
return
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
if (request.delaySeconds !== undefined) {
|
|
1552
|
-
return request.delaySeconds * 1000 <= threshold ? "realtime" : "bulk";
|
|
1553
|
-
}
|
|
1554
|
-
if (request.at === undefined) {
|
|
1555
|
-
// No recurrence at all, which the schema refuses before this is reached.
|
|
1556
|
-
// Answered rather than thrown so a caller of this function on an unvalidated
|
|
1557
|
-
// body gets the conservative class instead of an exception.
|
|
1558
|
-
return "bulk";
|
|
1559
|
-
}
|
|
1560
|
-
const atMs = Date.parse(request.at);
|
|
1561
|
-
if (Number.isNaN(atMs)) {
|
|
1562
|
-
return "bulk";
|
|
1692
|
+
return resolveService(requested, request.everySeconds === undefined
|
|
1693
|
+
? SCHEDULING_BOUNDS.everySecondsMin * 1000
|
|
1694
|
+
: request.everySeconds * 1000).latencyClass;
|
|
1563
1695
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1696
|
+
const atMs = request.delaySeconds !== undefined
|
|
1697
|
+
? nowMs + request.delaySeconds * 1000
|
|
1698
|
+
: request.at === undefined
|
|
1699
|
+
? Number.NaN
|
|
1700
|
+
: Date.parse(request.at);
|
|
1701
|
+
// No instant at all, which the schema refuses before this is reached, is
|
|
1702
|
+
// answered conservatively rather than thrown.
|
|
1703
|
+
return Number.isNaN(atMs)
|
|
1704
|
+
? resolveService(requested, Infinity).latencyClass
|
|
1705
|
+
: resolveService(requested, oneOffSpacingMs(nowMs, atMs)).latencyClass;
|
|
1568
1706
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** The modes a request may name. `auto` is what omission means. */
|
|
2
|
+
export declare const REQUESTED_LATENCY_CLASSES: readonly ["auto", "bulk", "realtime"];
|
|
3
|
+
export type RequestedLatencyClass = (typeof REQUESTED_LATENCY_CLASSES)[number];
|
|
4
|
+
/** The concrete services an occurrence resolves to. */
|
|
5
|
+
export type ResolvedLatencyClass = "bulk" | "realtime";
|
|
6
|
+
/** The internal bands. Not on the wire: only the concrete service is. */
|
|
7
|
+
export declare const SERVICE_BANDS: readonly ["realtime", "short_bulk", "bulk"];
|
|
8
|
+
export type ServiceBand = (typeof SERVICE_BANDS)[number];
|
|
9
|
+
export interface ServiceResolution {
|
|
10
|
+
readonly band: ServiceBand;
|
|
11
|
+
readonly latencyClass: ResolvedLatencyClass;
|
|
12
|
+
readonly quantumMs: number;
|
|
13
|
+
}
|
|
14
|
+
/** The concrete service a band is charged and reported as. */
|
|
15
|
+
export declare function bandLatencyClass(band: ServiceBand): ResolvedLatencyClass;
|
|
16
|
+
/**
|
|
17
|
+
* The service, band and quantum for one occurrence.
|
|
18
|
+
*
|
|
19
|
+
* `spacingMs` is the one-off lead or the recurrence's shorter adjacent gap
|
|
20
|
+
* (see the module comment). A non-finite spacing — a recurrence with no
|
|
21
|
+
* adjacent instant — is treated as long.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveService(requested: RequestedLatencyClass, spacingMs: number): ServiceResolution;
|
|
24
|
+
/** A one-off's lead: planned instant minus the captured acceptance time. */
|
|
25
|
+
export declare function oneOffSpacingMs(acceptedAtMs: number, plannedForMs: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* A recurrence occurrence's shorter adjacent planned gap. Either neighbour may
|
|
28
|
+
* be unknown — a series' first occurrence has no predecessor — and the gap to
|
|
29
|
+
* a known neighbour decides alone.
|
|
30
|
+
*/
|
|
31
|
+
export declare function recurrenceSpacingMs(previousMs: number | undefined, plannedForMs: number, nextMs: number | undefined): number;
|
|
32
|
+
/**
|
|
33
|
+
* An instant's first eligibility in its band: `ceil(instant / quantum) *
|
|
34
|
+
* quantum`, so a boundary stays where it is.
|
|
35
|
+
*
|
|
36
|
+
* Applied once, to the planned instant or to a retry's backed-off instant.
|
|
37
|
+
* Work that becomes claimable late is claimable now, never at another
|
|
38
|
+
* boundary: nothing rounds a rounded instant, and nothing rounds `now`. The
|
|
39
|
+
* last quantum of year 9999 has no boundary the contract can name, so it is
|
|
40
|
+
* eligible at the end of that year instead.
|
|
41
|
+
*/
|
|
42
|
+
export declare function roundEligibilityMs(instantMs: number, band: ServiceBand): number;
|
|
43
|
+
export interface OccurrenceResolution extends ServiceResolution {
|
|
44
|
+
/** `plannedForMs` rounded up to the band's quantum. */
|
|
45
|
+
readonly eligibleAtMs: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* One occurrence's service, band and rounded first eligibility: the entry
|
|
49
|
+
* point create, replacement and a recurrence's successor all use.
|
|
50
|
+
*
|
|
51
|
+
* A one-off is measured from its captured acceptance time; a recurrence by its
|
|
52
|
+
* neighbours, which the caller took from the existing recurrence evaluator.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveOccurrence(input: {
|
|
55
|
+
readonly kind: "one-off";
|
|
56
|
+
readonly requested: RequestedLatencyClass;
|
|
57
|
+
readonly acceptedAtMs: number;
|
|
58
|
+
readonly plannedForMs: number;
|
|
59
|
+
} | {
|
|
60
|
+
readonly kind: "recurring";
|
|
61
|
+
readonly requested: RequestedLatencyClass;
|
|
62
|
+
readonly plannedForMs: number;
|
|
63
|
+
readonly previousMs?: number;
|
|
64
|
+
readonly nextMs?: number;
|
|
65
|
+
}): OccurrenceResolution;
|
|
66
|
+
/** Stored requested intent; `legacy` predates recording it (W12-02). */
|
|
67
|
+
export type StoredRequestedLatencyClass = RequestedLatencyClass | "legacy";
|
|
68
|
+
/**
|
|
69
|
+
* What a replacement runs under (implementation-guide § W02, step 4).
|
|
70
|
+
*
|
|
71
|
+
* - A request naming a mode other than the stored one is a mode edit.
|
|
72
|
+
* - A timing or cadence edit resolves again. An explicit stored mode survives
|
|
73
|
+
* it; a legacy schedule's enters `auto`.
|
|
74
|
+
* - Anything else — payload, headers, name, tags, a mode named as it already
|
|
75
|
+
* was — preserves the stored service and tariff, and a legacy schedule stays
|
|
76
|
+
* legacy.
|
|
77
|
+
*/
|
|
78
|
+
export declare function replacementMode(input: {
|
|
79
|
+
readonly stored: StoredRequestedLatencyClass;
|
|
80
|
+
readonly requested: RequestedLatencyClass | undefined;
|
|
81
|
+
readonly timingChanged: boolean;
|
|
82
|
+
}): {
|
|
83
|
+
readonly kind: "preserve";
|
|
84
|
+
readonly requested: StoredRequestedLatencyClass;
|
|
85
|
+
} | {
|
|
86
|
+
readonly kind: "resolve";
|
|
87
|
+
readonly requested: RequestedLatencyClass;
|
|
88
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W12-02 (FXO-3171): the one resolver for service modes.
|
|
3
|
+
*
|
|
4
|
+
* Design: docs/archive/warm-worker-scheduling/project-spec.md § Customer
|
|
5
|
+
* contract, and implementation-guide.md § W02. A request names a mode —
|
|
6
|
+
* `auto`, `bulk` or `realtime` — and every occurrence resolves to a concrete
|
|
7
|
+
* service (`bulk` or `realtime`, what is charged and what a read reports), an
|
|
8
|
+
* internal band (`realtime`, `short_bulk` or `bulk`) and that band's quantum.
|
|
9
|
+
*
|
|
10
|
+
* Pure: every input is a parameter, nothing here reads a clock, and the
|
|
11
|
+
* spacing it classifies by is measured by the caller from instants the
|
|
12
|
+
* existing recurrence evaluator produced. There is deliberately no cron
|
|
13
|
+
* arithmetic in this module: a second evaluator is a second opinion about when
|
|
14
|
+
* a schedule fires, and two opinions eventually disagree.
|
|
15
|
+
*
|
|
16
|
+
* The spacing is:
|
|
17
|
+
*
|
|
18
|
+
* - for a one-off, the lead from the captured acceptance time to the original
|
|
19
|
+
* planned instant (an instant already past has no lead), measured once at
|
|
20
|
+
* acceptance — a replayed request reads the stored answer and never measures
|
|
21
|
+
* again; and
|
|
22
|
+
* - for a recurrence, the shorter adjacent planned gap. A one-minute heartbeat
|
|
23
|
+
* is short bulk however soon its next run happens to be due.
|
|
24
|
+
*
|
|
25
|
+
* Every recurrence the product accepts is at least a minute apart — `every`
|
|
26
|
+
* starts at sixty seconds and cron names whole minutes — so `auto` never
|
|
27
|
+
* resolves a recurrence to `realtime`, and an occurrence's service is the
|
|
28
|
+
* schedule's for its whole series. Only the band moves with irregular gaps.
|
|
29
|
+
*/
|
|
30
|
+
import { SERVICE_MODE_POLICY } from "./operational-config.js";
|
|
31
|
+
/** The modes a request may name. `auto` is what omission means. */
|
|
32
|
+
export const REQUESTED_LATENCY_CLASSES = ["auto", "bulk", "realtime"];
|
|
33
|
+
/** The internal bands. Not on the wire: only the concrete service is. */
|
|
34
|
+
export const SERVICE_BANDS = ["realtime", "short_bulk", "bulk"];
|
|
35
|
+
/** The concrete service a band is charged and reported as. */
|
|
36
|
+
export function bandLatencyClass(band) {
|
|
37
|
+
return band === "realtime" ? "realtime" : "bulk";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The service, band and quantum for one occurrence.
|
|
41
|
+
*
|
|
42
|
+
* `spacingMs` is the one-off lead or the recurrence's shorter adjacent gap
|
|
43
|
+
* (see the module comment). A non-finite spacing — a recurrence with no
|
|
44
|
+
* adjacent instant — is treated as long.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveService(requested, spacingMs) {
|
|
47
|
+
const spacing = Number.isNaN(spacingMs) ? Infinity : Math.max(0, spacingMs);
|
|
48
|
+
const band = requested === "realtime" ||
|
|
49
|
+
(requested === "auto" && spacing < SERVICE_MODE_POLICY.autoRealtimeBelowMs)
|
|
50
|
+
? "realtime"
|
|
51
|
+
: spacing < SERVICE_MODE_POLICY.shortBulkBelowMs
|
|
52
|
+
? "short_bulk"
|
|
53
|
+
: "bulk";
|
|
54
|
+
return {
|
|
55
|
+
band,
|
|
56
|
+
latencyClass: bandLatencyClass(band),
|
|
57
|
+
quantumMs: SERVICE_MODE_POLICY.quantumMs[band],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** A one-off's lead: planned instant minus the captured acceptance time. */
|
|
61
|
+
export function oneOffSpacingMs(acceptedAtMs, plannedForMs) {
|
|
62
|
+
return plannedForMs - acceptedAtMs;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A recurrence occurrence's shorter adjacent planned gap. Either neighbour may
|
|
66
|
+
* be unknown — a series' first occurrence has no predecessor — and the gap to
|
|
67
|
+
* a known neighbour decides alone.
|
|
68
|
+
*/
|
|
69
|
+
export function recurrenceSpacingMs(previousMs, plannedForMs, nextMs) {
|
|
70
|
+
const gaps = [
|
|
71
|
+
previousMs === undefined ? Infinity : plannedForMs - previousMs,
|
|
72
|
+
nextMs === undefined ? Infinity : nextMs - plannedForMs,
|
|
73
|
+
].filter((gap) => gap > 0);
|
|
74
|
+
return gaps.length === 0 ? Infinity : Math.min(...gaps);
|
|
75
|
+
}
|
|
76
|
+
/** The last instant the contract's four-digit-year timestamps express. */
|
|
77
|
+
const LATEST_EXPRESSIBLE_MS = Date.parse("9999-12-31T23:59:59.999Z");
|
|
78
|
+
/**
|
|
79
|
+
* An instant's first eligibility in its band: `ceil(instant / quantum) *
|
|
80
|
+
* quantum`, so a boundary stays where it is.
|
|
81
|
+
*
|
|
82
|
+
* Applied once, to the planned instant or to a retry's backed-off instant.
|
|
83
|
+
* Work that becomes claimable late is claimable now, never at another
|
|
84
|
+
* boundary: nothing rounds a rounded instant, and nothing rounds `now`. The
|
|
85
|
+
* last quantum of year 9999 has no boundary the contract can name, so it is
|
|
86
|
+
* eligible at the end of that year instead.
|
|
87
|
+
*/
|
|
88
|
+
export function roundEligibilityMs(instantMs, band) {
|
|
89
|
+
const quantum = SERVICE_MODE_POLICY.quantumMs[band];
|
|
90
|
+
return Math.min(Math.ceil(instantMs / quantum) * quantum, Math.max(instantMs, LATEST_EXPRESSIBLE_MS));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* One occurrence's service, band and rounded first eligibility: the entry
|
|
94
|
+
* point create, replacement and a recurrence's successor all use.
|
|
95
|
+
*
|
|
96
|
+
* A one-off is measured from its captured acceptance time; a recurrence by its
|
|
97
|
+
* neighbours, which the caller took from the existing recurrence evaluator.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveOccurrence(input) {
|
|
100
|
+
const spacing = input.kind === "one-off"
|
|
101
|
+
? oneOffSpacingMs(input.acceptedAtMs, input.plannedForMs)
|
|
102
|
+
: recurrenceSpacingMs(input.previousMs, input.plannedForMs, input.nextMs);
|
|
103
|
+
const resolution = resolveService(input.requested, spacing);
|
|
104
|
+
return {
|
|
105
|
+
...resolution,
|
|
106
|
+
eligibleAtMs: roundEligibilityMs(input.plannedForMs, resolution.band),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* What a replacement runs under (implementation-guide § W02, step 4).
|
|
111
|
+
*
|
|
112
|
+
* - A request naming a mode other than the stored one is a mode edit.
|
|
113
|
+
* - A timing or cadence edit resolves again. An explicit stored mode survives
|
|
114
|
+
* it; a legacy schedule's enters `auto`.
|
|
115
|
+
* - Anything else — payload, headers, name, tags, a mode named as it already
|
|
116
|
+
* was — preserves the stored service and tariff, and a legacy schedule stays
|
|
117
|
+
* legacy.
|
|
118
|
+
*/
|
|
119
|
+
export function replacementMode(input) {
|
|
120
|
+
if (input.requested !== undefined && input.requested !== input.stored) {
|
|
121
|
+
return { kind: "resolve", requested: input.requested };
|
|
122
|
+
}
|
|
123
|
+
if (input.timingChanged) {
|
|
124
|
+
return {
|
|
125
|
+
kind: "resolve",
|
|
126
|
+
requested: input.stored === "legacy" ? "auto" : input.stored,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { kind: "preserve", requested: input.stored };
|
|
130
|
+
}
|
package/dist/tenancy.d.ts
CHANGED
|
@@ -133,20 +133,19 @@ export declare const provisionedProjectSchema: import("zod").ZodObject<{
|
|
|
133
133
|
}, import("zod/v4/core").$strip>;
|
|
134
134
|
}, import("zod/v4/core").$strip>;
|
|
135
135
|
export type ProvisionedProject = z.infer<typeof provisionedProjectSchema>;
|
|
136
|
+
/** Exactly what a permanent deletion of the project would destroy now. */
|
|
137
|
+
export declare const projectDeletionCountsSchema: import("zod").ZodObject<{
|
|
138
|
+
endpoints: import("zod").ZodNumber;
|
|
139
|
+
keys: import("zod").ZodNumber;
|
|
140
|
+
runs: import("zod").ZodNumber;
|
|
141
|
+
schedules: import("zod").ZodNumber;
|
|
142
|
+
}, import("zod/v4/core").$strip>;
|
|
136
143
|
/**
|
|
137
|
-
*
|
|
144
|
+
* One project, with the exact counts a permanent deletion would destroy now.
|
|
138
145
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* `/v1` takes one: every operation that names an environment names its
|
|
143
|
-
* `kind`. Publishing an identifier no operation accepts invites a client to
|
|
144
|
-
* build on it and then constrains us to keep it.
|
|
145
|
-
*
|
|
146
|
-
* This remains a display aid rather than an authorization control. Whatever the
|
|
147
|
-
* list shows, the authority for a request is re-resolved from the member's
|
|
148
|
-
* organization when the request arrives. Lifecycle state and version make an
|
|
149
|
-
* archived row manageable without making it a valid work target.
|
|
146
|
+
* Returned by the single-project read (`projects.read`) and by the writes that
|
|
147
|
+
* answer with the project they changed. Never by the list: see
|
|
148
|
+
* `projectListItemSchema`.
|
|
150
149
|
*/
|
|
151
150
|
export declare const projectSummarySchema: import("zod").ZodObject<{
|
|
152
151
|
createdAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
@@ -169,12 +168,43 @@ export declare const projectSummarySchema: import("zod").ZodObject<{
|
|
|
169
168
|
version: import("zod").ZodNumber;
|
|
170
169
|
}, import("zod/v4/core").$strip>;
|
|
171
170
|
export type ProjectSummary = z.infer<typeof projectSummarySchema>;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
/**
|
|
172
|
+
* What a signed-in member is told about a project they may act in.
|
|
173
|
+
*
|
|
174
|
+
* **Not `provisionedProjectSchema`, deliberately.** That shape carries
|
|
175
|
+
* `organization`, `status`, `version` and both environment *identifiers*, and
|
|
176
|
+
* publishing environment ids would make them public names — while nothing on
|
|
177
|
+
* `/v1` takes one: every operation that names an environment names its
|
|
178
|
+
* `kind`. Publishing an identifier no operation accepts invites a client to
|
|
179
|
+
* build on it and then constrains us to keep it.
|
|
180
|
+
*
|
|
181
|
+
* This remains a display aid rather than an authorization control. Whatever the
|
|
182
|
+
* list shows, the authority for a request is re-resolved from the member's
|
|
183
|
+
* organization when the request arrives. Lifecycle state and version make an
|
|
184
|
+
* archived row manageable without making it a valid work target.
|
|
185
|
+
*
|
|
186
|
+
* **No deletion counts (W9-88).** Counting a project's schedules and runs
|
|
187
|
+
* reads every one of them, so a list that counted every project cost the sum
|
|
188
|
+
* of the organization's whole history — 2.2 s for an organization with
|
|
189
|
+
* 290,000 schedules, longer than the API's pool would wait, and the landing
|
|
190
|
+
* page failed every time. Exact counts are a deletion preview; they belong to
|
|
191
|
+
* the one project somebody is about to delete: `projectSummarySchema`.
|
|
192
|
+
*/
|
|
193
|
+
export declare const projectListItemSchema: import("zod").ZodObject<{
|
|
194
|
+
status: import("zod").ZodEnum<{
|
|
195
|
+
active: "active";
|
|
196
|
+
archived: "archived";
|
|
197
|
+
}>;
|
|
198
|
+
name: import("zod").ZodString;
|
|
199
|
+
version: import("zod").ZodNumber;
|
|
200
|
+
id: import("zod").ZodString;
|
|
201
|
+
createdAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
202
|
+
environmentKinds: import("zod").ZodArray<import("zod").ZodEnum<{
|
|
203
|
+
test: "test";
|
|
204
|
+
live: "live";
|
|
205
|
+
}>>;
|
|
177
206
|
}, import("zod/v4/core").$strip>;
|
|
207
|
+
export type ProjectListItem = z.infer<typeof projectListItemSchema>;
|
|
178
208
|
export type ProjectDeletionCounts = z.infer<typeof projectDeletionCountsSchema>;
|
|
179
209
|
/**
|
|
180
210
|
* Every active or archived project the member can access. Administrators see all.
|
|
@@ -190,24 +220,18 @@ export type ProjectDeletionCounts = z.infer<typeof projectDeletionCountsSchema>;
|
|
|
190
220
|
export declare const PROJECT_LIST_LIMIT = 200;
|
|
191
221
|
export declare const projectListSchema: import("zod").ZodObject<{
|
|
192
222
|
data: import("zod").ZodArray<import("zod").ZodObject<{
|
|
193
|
-
createdAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
194
|
-
deletionCounts: import("zod").ZodObject<{
|
|
195
|
-
endpoints: import("zod").ZodNumber;
|
|
196
|
-
keys: import("zod").ZodNumber;
|
|
197
|
-
runs: import("zod").ZodNumber;
|
|
198
|
-
schedules: import("zod").ZodNumber;
|
|
199
|
-
}, import("zod/v4/core").$strip>;
|
|
200
|
-
environmentKinds: import("zod").ZodArray<import("zod").ZodEnum<{
|
|
201
|
-
test: "test";
|
|
202
|
-
live: "live";
|
|
203
|
-
}>>;
|
|
204
|
-
id: import("zod").ZodString;
|
|
205
|
-
name: import("zod").ZodString;
|
|
206
223
|
status: import("zod").ZodEnum<{
|
|
207
224
|
active: "active";
|
|
208
225
|
archived: "archived";
|
|
209
226
|
}>;
|
|
227
|
+
name: import("zod").ZodString;
|
|
210
228
|
version: import("zod").ZodNumber;
|
|
229
|
+
id: import("zod").ZodString;
|
|
230
|
+
createdAt: import("zod").ZodNullable<import("zod").ZodString>;
|
|
231
|
+
environmentKinds: import("zod").ZodArray<import("zod").ZodEnum<{
|
|
232
|
+
test: "test";
|
|
233
|
+
live: "live";
|
|
234
|
+
}>>;
|
|
211
235
|
}, import("zod/v4/core").$strip>>;
|
|
212
236
|
total: import("zod").ZodNumber;
|
|
213
237
|
}, import("zod/v4/core").$strip>;
|