@greatwait/contracts 0.3.0 → 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/attempt-search.js +22 -19
- package/dist/authority.js +9 -9
- 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 +6 -2
- package/dist/configuration.js +93 -52
- package/dist/delivery.d.ts +13 -1
- package/dist/delivery.js +2 -0
- package/dist/effective-limits.d.ts +4 -4
- package/dist/endpoints.d.ts +29 -0
- package/dist/endpoints.js +57 -26
- package/dist/forecast.d.ts +2 -2
- package/dist/http-request.d.ts +12 -2
- package/dist/http-request.js +56 -24
- package/dist/identifiers.js +20 -20
- 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 +213 -79
- 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 +56 -33
- package/dist/schedule-selection.js +2 -2
- package/dist/scheduling-records.d.ts +106 -16
- package/dist/scheduling-records.js +36 -12
- package/dist/scheduling.d.ts +200 -43
- package/dist/scheduling.js +280 -131
- package/dist/service-mode.d.ts +88 -0
- package/dist/service-mode.js +130 -0
- package/dist/signatures.d.ts +6 -6
- package/dist/signatures.js +24 -24
- package/dist/stored-http.d.ts +18 -0
- package/dist/stored-http.js +106 -0
- package/dist/tenancy.d.ts +54 -30
- package/dist/tenancy.js +35 -17
- package/dist/time.js +7 -7
- 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
|
/**
|
|
@@ -51,18 +53,18 @@ export const SCHEDULE_STATUSES = [
|
|
|
51
53
|
"cancelled",
|
|
52
54
|
"completed",
|
|
53
55
|
];
|
|
54
|
-
export const scheduleStatusSchema = z.enum(SCHEDULE_STATUSES);
|
|
56
|
+
export const scheduleStatusSchema = /* @__PURE__ */ (() => z.enum(SCHEDULE_STATUSES))();
|
|
55
57
|
/** A recurring schedule pauses after this many failed runs; "never" disables it. */
|
|
56
58
|
export const DEFAULT_AUTO_PAUSE_AFTER_FAILURES = 5;
|
|
57
|
-
export const autoPauseAfterFailuresSchema = z
|
|
59
|
+
export const autoPauseAfterFailuresSchema = /* @__PURE__ */ (() => z
|
|
58
60
|
.union([z.number().int().min(1).max(100), z.literal("never")])
|
|
59
|
-
.describe('Consecutive failed runs before automatic pause: 1–100 or "never". Defaults to 5 on create; omission on replace preserves the setting. One-shot schedules are unaffected.');
|
|
60
|
-
export const autoPauseReasonSchema = z.object({
|
|
61
|
+
.describe('Consecutive failed runs before automatic pause: 1–100 or "never". Defaults to 5 on create; omission on replace preserves the setting. One-shot schedules are unaffected.'))();
|
|
62
|
+
export const autoPauseReasonSchema = /* @__PURE__ */ (() => z.object({
|
|
61
63
|
failedRuns: z.number().int().positive(),
|
|
62
64
|
status: z.number().int().min(100).max(599).optional(),
|
|
63
65
|
diagnostic: z.string().max(500),
|
|
64
66
|
pausedAt: timestampSchema,
|
|
65
|
-
});
|
|
67
|
+
}))();
|
|
66
68
|
/**
|
|
67
69
|
* The statuses a schedule never leaves, and from which it never fires again on
|
|
68
70
|
* its own.
|
|
@@ -103,7 +105,14 @@ export function isTerminalScheduleStatus(status) {
|
|
|
103
105
|
* anything to anybody. `realtime` gets preference without starving `bulk`.
|
|
104
106
|
*/
|
|
105
107
|
export const LATENCY_CLASSES = ["realtime", "bulk"];
|
|
106
|
-
export const latencyClassSchema = z.enum(LATENCY_CLASSES);
|
|
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.
|
|
@@ -118,7 +127,7 @@ export const latencyClassSchema = z.enum(LATENCY_CLASSES);
|
|
|
118
127
|
* it started under.
|
|
119
128
|
*/
|
|
120
129
|
export const LATENESS_POLICIES = ["skipAhead", "catchUp"];
|
|
121
|
-
export const latenessPolicySchema = z.enum(LATENESS_POLICIES);
|
|
130
|
+
export const latenessPolicySchema = /* @__PURE__ */ (() => z.enum(LATENESS_POLICIES))();
|
|
122
131
|
/** The inherited one-shot staleness window: twenty-four hours. */
|
|
123
132
|
export const DEFAULT_ONE_SHOT_MAX_STALENESS_SECONDS = 86_400;
|
|
124
133
|
/**
|
|
@@ -129,7 +138,7 @@ export const DEFAULT_ONE_SHOT_MAX_STALENESS_SECONDS = 86_400;
|
|
|
129
138
|
* staleness bound uses the recurrence's own default: twenty-four hours for a
|
|
130
139
|
* one-shot, and one recurrence interval for a recurring schedule.
|
|
131
140
|
*/
|
|
132
|
-
export const scheduleLatenessRequestSchema = z
|
|
141
|
+
export const scheduleLatenessRequestSchema = /* @__PURE__ */ (() => z
|
|
133
142
|
.object({
|
|
134
143
|
maxStalenessSeconds: durationSecondsSchema
|
|
135
144
|
.positive("must be at least 1 second")
|
|
@@ -137,7 +146,7 @@ export const scheduleLatenessRequestSchema = z
|
|
|
137
146
|
.optional(),
|
|
138
147
|
policy: latenessPolicySchema.optional(),
|
|
139
148
|
})
|
|
140
|
-
.strict();
|
|
149
|
+
.strict())();
|
|
141
150
|
/**
|
|
142
151
|
* Lateness settings as a schedule reads back.
|
|
143
152
|
*
|
|
@@ -145,9 +154,9 @@ export const scheduleLatenessRequestSchema = z
|
|
|
145
154
|
* remains optional because it records a per-schedule override, not a duplicated
|
|
146
155
|
* copy of a default that can be derived from the recurrence.
|
|
147
156
|
*/
|
|
148
|
-
export const scheduleLatenessSchema = scheduleLatenessRequestSchema.extend({
|
|
157
|
+
export const scheduleLatenessSchema = /* @__PURE__ */ (() => scheduleLatenessRequestSchema.extend({
|
|
149
158
|
policy: latenessPolicySchema,
|
|
150
|
-
});
|
|
159
|
+
}))();
|
|
151
160
|
/* -------------------------------------------------------------------------- */
|
|
152
161
|
/* Delivery duration */
|
|
153
162
|
/* -------------------------------------------------------------------------- */
|
|
@@ -256,12 +265,12 @@ export const RESPONSE_DELIVERY_MAX_DURATION_SECONDS = 1_800;
|
|
|
256
265
|
* a relation between two fields), and above
|
|
257
266
|
* {@link FREE_DELIVERY_DURATION_SECONDS} it needs a plan that allows it.
|
|
258
267
|
*/
|
|
259
|
-
export const deliveryDurationSecondsSchema = z
|
|
268
|
+
export const deliveryDurationSecondsSchema = /* @__PURE__ */ (() => z
|
|
260
269
|
.number()
|
|
261
270
|
.int("must be a whole number of seconds")
|
|
262
271
|
.positive("must be at least 1 second")
|
|
263
272
|
.max(MAX_DELIVERY_DURATION_SECONDS, `must be at most ${String(MAX_DELIVERY_DURATION_SECONDS)} seconds`)
|
|
264
|
-
.describe("How long the receiver may take to answer, in seconds");
|
|
273
|
+
.describe("How long the receiver may take to answer, in seconds"))();
|
|
265
274
|
/** Whether a duration is past the point where it needs a plan that allows it. */
|
|
266
275
|
export function isLongRunningDuration(seconds) {
|
|
267
276
|
return seconds > FREE_DELIVERY_DURATION_SECONDS;
|
|
@@ -291,7 +300,7 @@ export function isLongRunningDuration(seconds) {
|
|
|
291
300
|
* untouched, and so is what a rung costs.
|
|
292
301
|
*/
|
|
293
302
|
export const DELIVERY_COMPLETION_MODES = ["response", "callback"];
|
|
294
|
-
export const deliveryCompletionModeSchema = z.enum(DELIVERY_COMPLETION_MODES);
|
|
303
|
+
export const deliveryCompletionModeSchema = /* @__PURE__ */ (() => z.enum(DELIVERY_COMPLETION_MODES))();
|
|
295
304
|
/**
|
|
296
305
|
* What to do when a recurring schedule comes due and its previous run has not
|
|
297
306
|
* finished.
|
|
@@ -313,7 +322,7 @@ export const deliveryCompletionModeSchema = z.enum(DELIVERY_COMPLETION_MODES);
|
|
|
313
322
|
* - `queue` — run it when the previous one finishes.
|
|
314
323
|
*/
|
|
315
324
|
export const DELIVERY_OVERLAP_POLICIES = ["skip", "queue"];
|
|
316
|
-
export const deliveryOverlapPolicySchema = z.enum(DELIVERY_OVERLAP_POLICIES);
|
|
325
|
+
export const deliveryOverlapPolicySchema = /* @__PURE__ */ (() => z.enum(DELIVERY_OVERLAP_POLICIES))();
|
|
317
326
|
export const DEFAULT_DELIVERY_OVERLAP_POLICY = "skip";
|
|
318
327
|
/**
|
|
319
328
|
* A definition generation. Starts at 1 and increases on every replacement.
|
|
@@ -324,10 +333,7 @@ export const DEFAULT_DELIVERY_OVERLAP_POLICY = "skip";
|
|
|
324
333
|
* would make pausing and resuming a schedule invalidate occurrences that are
|
|
325
334
|
* still perfectly valid.
|
|
326
335
|
*/
|
|
327
|
-
export const generationSchema = z
|
|
328
|
-
.number()
|
|
329
|
-
.int("must be a whole number")
|
|
330
|
-
.positive("must be at least 1");
|
|
336
|
+
export const generationSchema = /* @__PURE__ */ (() => z.number().int("must be a whole number").positive("must be at least 1"))();
|
|
331
337
|
/**
|
|
332
338
|
* A customer-supplied fairness key.
|
|
333
339
|
*
|
|
@@ -339,11 +345,11 @@ export const generationSchema = z
|
|
|
339
345
|
* another Durable Object namespace.
|
|
340
346
|
*/
|
|
341
347
|
export const PARTITION_KEY_MAX_LENGTH = 64;
|
|
342
|
-
export const partitionKeySchema = z
|
|
348
|
+
export const partitionKeySchema = /* @__PURE__ */ (() => z
|
|
343
349
|
.string()
|
|
344
350
|
.min(1, "must not be empty")
|
|
345
351
|
.max(PARTITION_KEY_MAX_LENGTH, `must be at most ${String(PARTITION_KEY_MAX_LENGTH)} characters`)
|
|
346
|
-
.regex(/^[A-Za-z0-9._:-]+$/u, "must use only letters, digits, dot, underscore, colon, or hyphen");
|
|
352
|
+
.regex(/^[A-Za-z0-9._:-]+$/u, "must use only letters, digits, dot, underscore, colon, or hyphen"))();
|
|
347
353
|
/* -------------------------------------------------------------------------- */
|
|
348
354
|
/* Occurrence state */
|
|
349
355
|
/* -------------------------------------------------------------------------- */
|
|
@@ -369,11 +375,11 @@ export { OCCURRENCE_STATES, occurrenceStateSchema, } from "./occurrence-states.j
|
|
|
369
375
|
* the same thing. The date range is half-open, like every other time window in
|
|
370
376
|
* this contract, so adjacent requests neither overlap nor leave a gap.
|
|
371
377
|
*/
|
|
372
|
-
export const projectOccurrenceListRequestSchema = pageRequestSchema
|
|
378
|
+
export const projectOccurrenceListRequestSchema = /* @__PURE__ */ (() => pageRequestSchema
|
|
373
379
|
.extend(executionSelectionSchema.shape)
|
|
374
380
|
.refine((request) => request.from === undefined ||
|
|
375
381
|
request.to === undefined ||
|
|
376
|
-
Date.parse(request.from) < Date.parse(request.to), { message: "must be later than from", path: ["to"] });
|
|
382
|
+
Date.parse(request.from) < Date.parse(request.to), { message: "must be later than from", path: ["to"] }))();
|
|
377
383
|
const PROJECT_OCCURRENCE_LIST_PARAMETERS = [
|
|
378
384
|
"cursor",
|
|
379
385
|
"limit",
|
|
@@ -468,9 +474,9 @@ export const ATTEMPT_STATES = [
|
|
|
468
474
|
"terminal_failure",
|
|
469
475
|
"ambiguous",
|
|
470
476
|
];
|
|
471
|
-
export const attemptStateSchema = z.enum(ATTEMPT_STATES);
|
|
477
|
+
export const attemptStateSchema = /* @__PURE__ */ (() => z.enum(ATTEMPT_STATES))();
|
|
472
478
|
/** Safe diagnostic codes produced by the W3-03 delivery classifier. */
|
|
473
|
-
export const attemptDiagnosticSchema = z.enum(CALLBACK_ATTEMPT_DIAGNOSTICS);
|
|
479
|
+
export const attemptDiagnosticSchema = /* @__PURE__ */ (() => z.enum(CALLBACK_ATTEMPT_DIAGNOSTICS))();
|
|
474
480
|
/**
|
|
475
481
|
* Bounded next-action hints for an attempt timeline.
|
|
476
482
|
*
|
|
@@ -499,7 +505,7 @@ export const ATTEMPT_REMEDIATIONS = [
|
|
|
499
505
|
export const ATTEMPT_RESPONSE_HEADER_MAX_LENGTH = 256;
|
|
500
506
|
/** How much of a failed receiver's body is published, in characters. */
|
|
501
507
|
export const ATTEMPT_FAILURE_SNIPPET_MAX_LENGTH = 4_096;
|
|
502
|
-
export const attemptRemediationSchema = z.enum(ATTEMPT_REMEDIATIONS);
|
|
508
|
+
export const attemptRemediationSchema = /* @__PURE__ */ (() => z.enum(ATTEMPT_REMEDIATIONS))();
|
|
503
509
|
/** Derive the one safe next-action code a public attempt may expose. */
|
|
504
510
|
export function remediationForAttempt(diagnostic) {
|
|
505
511
|
switch (diagnostic) {
|
|
@@ -615,13 +621,11 @@ export const SCHEDULING_BOUNDS = {
|
|
|
615
621
|
* was the copied scheduler's `MAX_INTERVAL_SECONDS`, carried over on the
|
|
616
622
|
* belief that "a Durable Object alarm is not arbitrarily far in the future".
|
|
617
623
|
* Cloudflare documents no maximum distance for `setAlarm()`, and the belief
|
|
618
|
-
* predates the PostgreSQL authority besides: the durable fact is a row
|
|
619
|
-
* the alarm is only what wakes a bucket to read it.
|
|
624
|
+
* predates the PostgreSQL authority besides: the durable fact is a row.
|
|
620
625
|
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
* — 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.
|
|
625
629
|
*
|
|
626
630
|
* Widening a bound is additive by `classifyChange` and narrowing it is
|
|
627
631
|
* breaking, so this direction is the safe one to move in.
|
|
@@ -692,17 +696,15 @@ export const SCHEDULING_BOUNDS = {
|
|
|
692
696
|
*/
|
|
693
697
|
payloadBytes: MAX_INLINE_PAYLOAD_BYTES,
|
|
694
698
|
/**
|
|
695
|
-
* How soon a one-shot has to be for `
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
* who disagrees says so — `latencyClass` on the request is what makes this a
|
|
703
|
-
* 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.
|
|
704
706
|
*/
|
|
705
|
-
realtimeOneShotSeconds:
|
|
707
|
+
realtimeOneShotSeconds: SERVICE_MODE_POLICY.autoRealtimeBelowMs / 1000,
|
|
706
708
|
};
|
|
707
709
|
/* -------------------------------------------------------------------------- */
|
|
708
710
|
/* Recurrence */
|
|
@@ -735,12 +737,12 @@ export const CRON_EXPRESSION_MAX_LENGTH = 120;
|
|
|
735
737
|
* fire on a second boundary is refused by the partition, because a minute is
|
|
736
738
|
* the granularity both arms of recurrence promise.
|
|
737
739
|
*/
|
|
738
|
-
export const cronExpressionSchema = z
|
|
740
|
+
export const cronExpressionSchema = /* @__PURE__ */ (() => z
|
|
739
741
|
.string()
|
|
740
742
|
.min(1, "must not be empty")
|
|
741
743
|
.max(CRON_EXPRESSION_MAX_LENGTH, `must be at most ${String(CRON_EXPRESSION_MAX_LENGTH)} characters`)
|
|
742
744
|
.refine((expression) => expression.startsWith("@") ||
|
|
743
|
-
[5, 6].includes(expression.trim().split(/\s+/u).length), { message: "must be five cron fields, six fields, or an @-nickname" });
|
|
745
|
+
[5, 6].includes(expression.trim().split(/\s+/u).length), { message: "must be five cron fields, six fields, or an @-nickname" }))();
|
|
744
746
|
/**
|
|
745
747
|
* How a schedule recurs, as a caller reads it back.
|
|
746
748
|
*
|
|
@@ -757,7 +759,7 @@ export const cronExpressionSchema = z
|
|
|
757
759
|
* on that arm rather than optional, so a read always says which wall clock a
|
|
758
760
|
* cron schedule is on instead of leaving the caller to assume UTC.
|
|
759
761
|
*/
|
|
760
|
-
export const recurrenceSchema = z.discriminatedUnion("kind", [
|
|
762
|
+
export const recurrenceSchema = /* @__PURE__ */ (() => z.discriminatedUnion("kind", [
|
|
761
763
|
/**
|
|
762
764
|
* A one-shot, which carries no instant of its own.
|
|
763
765
|
*
|
|
@@ -779,7 +781,7 @@ export const recurrenceSchema = z.discriminatedUnion("kind", [
|
|
|
779
781
|
kind: z.literal("cron"),
|
|
780
782
|
timeZone: timeZoneSchema,
|
|
781
783
|
}),
|
|
782
|
-
]);
|
|
784
|
+
]))();
|
|
783
785
|
/** The customer-safe default for a schedule's recurrence kind. */
|
|
784
786
|
export function defaultLatenessPolicyForRecurrence(kind) {
|
|
785
787
|
return kind === "at" ? "catchUp" : "skipAhead";
|
|
@@ -807,12 +809,12 @@ export function defaultLatenessPolicyForRecurrence(kind) {
|
|
|
807
809
|
*/
|
|
808
810
|
/** The most a schedule's name may hold; the generated one is cut to fit it. */
|
|
809
811
|
export const SCHEDULE_NAME_MAX_LENGTH = 120;
|
|
810
|
-
export const scheduleNameSchema = z
|
|
812
|
+
export const scheduleNameSchema = /* @__PURE__ */ (() => z
|
|
811
813
|
.string()
|
|
812
814
|
.trim()
|
|
813
815
|
.min(1, "must not be blank")
|
|
814
|
-
.max(SCHEDULE_NAME_MAX_LENGTH, `must be at most ${String(SCHEDULE_NAME_MAX_LENGTH)} characters`);
|
|
815
|
-
export const scheduleSchema = z.object({
|
|
816
|
+
.max(SCHEDULE_NAME_MAX_LENGTH, `must be at most ${String(SCHEDULE_NAME_MAX_LENGTH)} characters`))();
|
|
817
|
+
export const scheduleSchema = /* @__PURE__ */ (() => z.object({
|
|
816
818
|
autoPauseAfterFailures: autoPauseAfterFailuresSchema.optional(),
|
|
817
819
|
consecutiveFailedRuns: z.number().int().nonnegative().optional(),
|
|
818
820
|
autoPauseReason: autoPauseReasonSchema.nullable().optional(),
|
|
@@ -884,7 +886,18 @@ export const scheduleSchema = z.object({
|
|
|
884
886
|
endpointUrl: z.string().url().optional(),
|
|
885
887
|
generation: generationSchema,
|
|
886
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
|
+
*/
|
|
887
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(),
|
|
888
901
|
/**
|
|
889
902
|
* What this schedule does with an occurrence that reaches its first attempt
|
|
890
903
|
* late. Present on every schedule so a caller never has to duplicate the
|
|
@@ -937,7 +950,7 @@ export const scheduleSchema = z.object({
|
|
|
937
950
|
status: scheduleStatusSchema,
|
|
938
951
|
updatedAt: timestampSchema,
|
|
939
952
|
version: versionSchema,
|
|
940
|
-
});
|
|
953
|
+
}))();
|
|
941
954
|
/**
|
|
942
955
|
* A page of schedules.
|
|
943
956
|
*
|
|
@@ -948,7 +961,77 @@ export const scheduleSchema = z.object({
|
|
|
948
961
|
* listing back through every partition it spans. Detail is where the occurrence
|
|
949
962
|
* is authoritative, and detail is one call away.
|
|
950
963
|
*/
|
|
951
|
-
export const schedulePageSchema = pageSchema(scheduleSchema);
|
|
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
|
+
}))();
|
|
952
1035
|
function hasCompleteTimingEvidence(value) {
|
|
953
1036
|
return ((value.firstAttemptStartedAt === undefined) ===
|
|
954
1037
|
(value.firstAttemptLatenessMs === undefined) &&
|
|
@@ -973,7 +1056,12 @@ const timingEvidenceShape = {
|
|
|
973
1056
|
.optional(),
|
|
974
1057
|
timingEligibilityReason: z.string().min(1).max(128).optional(),
|
|
975
1058
|
/** Policy pinned when trusted first-attempt timing evidence is recorded. */
|
|
976
|
-
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."),
|
|
977
1065
|
};
|
|
978
1066
|
/**
|
|
979
1067
|
* One concrete occurrence of a schedule.
|
|
@@ -983,7 +1071,7 @@ const timingEvidenceShape = {
|
|
|
983
1071
|
* numbering starts again. Together `(schedule, generation, sequence)` is what
|
|
984
1072
|
* makes an occurrence identifiable without depending on when it was written.
|
|
985
1073
|
*/
|
|
986
|
-
export const occurrenceSchema = z
|
|
1074
|
+
export const occurrenceSchema = /* @__PURE__ */ (() => z
|
|
987
1075
|
.object({
|
|
988
1076
|
attemptCount: z.number().int().nonnegative(),
|
|
989
1077
|
...timingEvidenceShape,
|
|
@@ -992,6 +1080,13 @@ export const occurrenceSchema = z
|
|
|
992
1080
|
/** When the occurrence was planned to fire. Never earlier than this. */
|
|
993
1081
|
plannedFor: timestampSchema,
|
|
994
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(),
|
|
995
1090
|
sequence: z.number().int().positive(),
|
|
996
1091
|
/** Present exactly when the occurrence is in a terminal state. */
|
|
997
1092
|
settledAt: timestampSchema.optional(),
|
|
@@ -1014,7 +1109,7 @@ export const occurrenceSchema = z
|
|
|
1014
1109
|
.refine(hasConsistentTimingEligibility, {
|
|
1015
1110
|
message: "timingEligibilityReason is required for a blocked population and absent otherwise",
|
|
1016
1111
|
path: ["timingEligibilityReason"],
|
|
1017
|
-
});
|
|
1112
|
+
}))();
|
|
1018
1113
|
/**
|
|
1019
1114
|
* One delivery attempt against one occurrence.
|
|
1020
1115
|
*
|
|
@@ -1039,11 +1134,11 @@ export const occurrenceSchema = z
|
|
|
1039
1134
|
* can look up in their own logs. Cookies, `Authorization` and
|
|
1040
1135
|
* `WWW-Authenticate` have no representation here at all.
|
|
1041
1136
|
*/
|
|
1042
|
-
export const attemptResponseHeaderSchema = z.object({
|
|
1137
|
+
export const attemptResponseHeaderSchema = /* @__PURE__ */ (() => z.object({
|
|
1043
1138
|
/** Lowercase, from the published allowlist. */
|
|
1044
1139
|
name: z.string().min(1).max(64),
|
|
1045
1140
|
value: z.string().max(ATTEMPT_RESPONSE_HEADER_MAX_LENGTH),
|
|
1046
|
-
});
|
|
1141
|
+
}))();
|
|
1047
1142
|
/**
|
|
1048
1143
|
* What a failing receiver said, bounded and access-controlled.
|
|
1049
1144
|
*
|
|
@@ -1057,14 +1152,14 @@ export const attemptResponseHeaderSchema = z.object({
|
|
|
1057
1152
|
* that is exactly the bound and a body that was cut at the bound are different
|
|
1058
1153
|
* facts and only one of them should show a customer an ellipsis.
|
|
1059
1154
|
*/
|
|
1060
|
-
export const attemptFailureSnippetSchema = z.object({
|
|
1155
|
+
export const attemptFailureSnippetSchema = /* @__PURE__ */ (() => z.object({
|
|
1061
1156
|
/** The media type it was captured from. Not customer content. */
|
|
1062
1157
|
contentType: z.string().min(1).max(128),
|
|
1063
1158
|
text: z.string().max(ATTEMPT_FAILURE_SNIPPET_MAX_LENGTH),
|
|
1064
1159
|
truncated: z.boolean(),
|
|
1065
|
-
});
|
|
1160
|
+
}))();
|
|
1066
1161
|
/** Stored diagnostic content, available only under the attempt's verified authority. */
|
|
1067
|
-
export const attemptCapturedBodySchema = z.object({
|
|
1162
|
+
export const attemptCapturedBodySchema = /* @__PURE__ */ (() => z.object({
|
|
1068
1163
|
contentType: z.string(),
|
|
1069
1164
|
encoding: z.enum(["utf8", "base64"]),
|
|
1070
1165
|
// Up to 10 KiB received bytes; credential masking can expand the representation.
|
|
@@ -1074,27 +1169,46 @@ export const attemptCapturedBodySchema = z.object({
|
|
|
1074
1169
|
truncated: z.boolean(),
|
|
1075
1170
|
redacted: z.boolean().optional(),
|
|
1076
1171
|
incomplete: z.boolean().optional(),
|
|
1077
|
-
});
|
|
1078
|
-
export const attemptResponseSchema = z.object({
|
|
1172
|
+
}))();
|
|
1173
|
+
export const attemptResponseSchema = /* @__PURE__ */ (() => z.object({
|
|
1079
1174
|
statusText: z.string(),
|
|
1080
1175
|
headers: z.array(z.object({ name: z.string(), value: z.string() })),
|
|
1081
1176
|
bodyState: z.enum(["captured", "empty", "expired", "not_captured"]),
|
|
1082
1177
|
body: attemptCapturedBodySchema.optional(),
|
|
1083
|
-
});
|
|
1084
|
-
export const attemptRequestSchema = z.object({
|
|
1178
|
+
}))();
|
|
1179
|
+
export const attemptRequestSchema = /* @__PURE__ */ (() => z.object({
|
|
1085
1180
|
method: z.enum(["GET", "POST", "PUT"]),
|
|
1086
1181
|
url: z.string(),
|
|
1087
1182
|
headers: z.array(z.object({ name: z.string(), value: z.string() })),
|
|
1088
1183
|
body: z.string(),
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
|
|
1184
|
+
}))();
|
|
1185
|
+
/** Shared validators for attempt search summaries and full attempt details. */
|
|
1186
|
+
export const attemptSearchSummaryShape = /* @__PURE__ */ (() => ({
|
|
1092
1187
|
attemptNumber: z.number().int().positive(),
|
|
1093
|
-
/** Present exactly when the attempt has been classified. */
|
|
1094
1188
|
classifiedAt: timestampSchema.optional(),
|
|
1189
|
+
diagnostic: attemptDiagnosticSchema.optional(),
|
|
1190
|
+
durationMs: z
|
|
1191
|
+
.number()
|
|
1192
|
+
.int()
|
|
1193
|
+
.nonnegative()
|
|
1194
|
+
.max(ATTEMPT_DURATION_MAX_MS)
|
|
1195
|
+
.optional(),
|
|
1196
|
+
id: attemptIdSchema,
|
|
1197
|
+
occurrence: occurrenceIdSchema,
|
|
1198
|
+
remediation: attemptRemediationSchema.optional(),
|
|
1199
|
+
responseStatus: z.number().int().min(100).max(599).optional(),
|
|
1200
|
+
startedAt: timestampSchema,
|
|
1201
|
+
state: attemptStateSchema.optional(),
|
|
1202
|
+
trigger: attemptTriggerSchema,
|
|
1203
|
+
}))();
|
|
1204
|
+
export const attemptSchema = /* @__PURE__ */ (() => z
|
|
1205
|
+
.object({
|
|
1206
|
+
attemptNumber: attemptSearchSummaryShape.attemptNumber,
|
|
1207
|
+
/** Present exactly when the attempt has been classified. */
|
|
1208
|
+
classifiedAt: attemptSearchSummaryShape.classifiedAt,
|
|
1095
1209
|
delivery: deliveryIdSchema,
|
|
1096
1210
|
/** Safe classifier code; absent when durable evidence predates diagnostics. */
|
|
1097
|
-
diagnostic:
|
|
1211
|
+
diagnostic: attemptSearchSummaryShape.diagnostic,
|
|
1098
1212
|
/**
|
|
1099
1213
|
* Instrumented re-drives of the same dispatching occurrence under a new
|
|
1100
1214
|
* dispatch epoch. Includes deliberate admission deferrals; this is not a
|
|
@@ -1102,14 +1216,9 @@ export const attemptSchema = z
|
|
|
1102
1216
|
*/
|
|
1103
1217
|
dispatchRecoveries: z.int().nonnegative().optional(),
|
|
1104
1218
|
/** Executor-observed elapsed time; absent when no durable timing survived. */
|
|
1105
|
-
durationMs:
|
|
1106
|
-
.number()
|
|
1107
|
-
.int()
|
|
1108
|
-
.nonnegative()
|
|
1109
|
-
.max(ATTEMPT_DURATION_MAX_MS)
|
|
1110
|
-
.optional(),
|
|
1219
|
+
durationMs: attemptSearchSummaryShape.durationMs,
|
|
1111
1220
|
...timingEvidenceShape,
|
|
1112
|
-
id:
|
|
1221
|
+
id: attemptSearchSummaryShape.id,
|
|
1113
1222
|
/**
|
|
1114
1223
|
* Instrumented send authorizations granted after a later local alarm
|
|
1115
1224
|
* replayed the original wake receipt. Preserves the attempt and delivery
|
|
@@ -1118,7 +1227,7 @@ export const attemptSchema = z
|
|
|
1118
1227
|
* older responses.
|
|
1119
1228
|
*/
|
|
1120
1229
|
localReceiptRecoveries: z.int().nonnegative().optional(),
|
|
1121
|
-
occurrence:
|
|
1230
|
+
occurrence: attemptSearchSummaryShape.occurrence,
|
|
1122
1231
|
/**
|
|
1123
1232
|
* How many times the watchdog took this attempt's send back and sent it
|
|
1124
1233
|
* again: the send was authorized, and its outcome was never recorded
|
|
@@ -1129,7 +1238,7 @@ export const attemptSchema = z
|
|
|
1129
1238
|
*/
|
|
1130
1239
|
rescues: z.int().positive().optional(),
|
|
1131
1240
|
/** Stable next-action code paired with a diagnostic when one is known. */
|
|
1132
|
-
remediation:
|
|
1241
|
+
remediation: attemptSearchSummaryShape.remediation,
|
|
1133
1242
|
/**
|
|
1134
1243
|
* Compatibility text prefix, up to 4 KiB, derived from the retained
|
|
1135
1244
|
* response when its media type is supported by legacy clients. Use
|
|
@@ -1140,13 +1249,29 @@ export const attemptSchema = z
|
|
|
1140
1249
|
response: attemptResponseSchema.optional(),
|
|
1141
1250
|
request: attemptRequestSchema.optional(),
|
|
1142
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(),
|
|
1143
1268
|
/** The fixed diagnostic-header allowlist, when a response carried any. */
|
|
1144
1269
|
responseHeaders: z.array(attemptResponseHeaderSchema).max(8).optional(),
|
|
1145
1270
|
/** Absent when no response was received at all. */
|
|
1146
|
-
responseStatus:
|
|
1147
|
-
startedAt:
|
|
1148
|
-
state:
|
|
1149
|
-
trigger:
|
|
1271
|
+
responseStatus: attemptSearchSummaryShape.responseStatus,
|
|
1272
|
+
startedAt: attemptSearchSummaryShape.startedAt,
|
|
1273
|
+
state: attemptSearchSummaryShape.state,
|
|
1274
|
+
trigger: attemptSearchSummaryShape.trigger,
|
|
1150
1275
|
})
|
|
1151
1276
|
// Classification and its timestamp arrive together or not at all — the same
|
|
1152
1277
|
// pairing the partition's CHECK constraint enforces. Independently optional
|
|
@@ -1185,30 +1310,30 @@ export const attemptSchema = z
|
|
|
1185
1310
|
.refine(hasConsistentTimingEligibility, {
|
|
1186
1311
|
message: "timingEligibilityReason is required for a blocked population and absent otherwise",
|
|
1187
1312
|
path: ["timingEligibilityReason"],
|
|
1188
|
-
});
|
|
1313
|
+
}))();
|
|
1189
1314
|
/** An authoritative page over one schedule's retained occurrences. */
|
|
1190
|
-
export const occurrencePageSchema = pageSchema(occurrenceSchema).extend({
|
|
1315
|
+
export const occurrencePageSchema = /* @__PURE__ */ (() => pageSchema(occurrenceSchema).extend({
|
|
1191
1316
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
1192
|
-
});
|
|
1317
|
+
}))();
|
|
1193
1318
|
/** A project-wide occurrence page and its plan's normal history boundary. */
|
|
1194
|
-
export const projectOccurrencePageSchema = pageSchema(occurrenceSchema).extend({
|
|
1319
|
+
export const projectOccurrencePageSchema = /* @__PURE__ */ (() => pageSchema(occurrenceSchema).extend({
|
|
1195
1320
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
1196
1321
|
retention: z.object({ from: timestampSchema }),
|
|
1197
|
-
});
|
|
1322
|
+
}))();
|
|
1198
1323
|
/** An authoritative occurrence detail response. */
|
|
1199
|
-
export const occurrenceViewSchema = z.object({
|
|
1324
|
+
export const occurrenceViewSchema = /* @__PURE__ */ (() => z.object({
|
|
1200
1325
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
1201
1326
|
occurrence: occurrenceSchema,
|
|
1202
|
-
});
|
|
1327
|
+
}))();
|
|
1203
1328
|
/** An authoritative page over one retained occurrence's immutable attempts. */
|
|
1204
|
-
export const attemptPageSchema = pageSchema(attemptSchema).extend({
|
|
1329
|
+
export const attemptPageSchema = /* @__PURE__ */ (() => pageSchema(attemptSchema).extend({
|
|
1205
1330
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
1206
|
-
});
|
|
1331
|
+
}))();
|
|
1207
1332
|
/** An authoritative attempt detail or redelivery response. */
|
|
1208
|
-
export const attemptViewSchema = z.object({
|
|
1333
|
+
export const attemptViewSchema = /* @__PURE__ */ (() => z.object({
|
|
1209
1334
|
attempt: attemptSchema,
|
|
1210
1335
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
1211
|
-
});
|
|
1336
|
+
}))();
|
|
1212
1337
|
/**
|
|
1213
1338
|
* A schedule together with the occurrence that is next to happen.
|
|
1214
1339
|
*
|
|
@@ -1217,19 +1342,19 @@ export const attemptViewSchema = z.object({
|
|
|
1217
1342
|
* that has to make a second request to answer it will cache the first answer and
|
|
1218
1343
|
* be wrong.
|
|
1219
1344
|
*/
|
|
1220
|
-
export const scheduleViewSchema = z.object({
|
|
1345
|
+
export const scheduleViewSchema = /* @__PURE__ */ (() => z.object({
|
|
1221
1346
|
occurrence: occurrenceSchema,
|
|
1222
1347
|
schedule: scheduleSchema,
|
|
1223
|
-
});
|
|
1348
|
+
}))();
|
|
1224
1349
|
/**
|
|
1225
1350
|
* What a schedule create or replace answers: the schedule view, and — when the
|
|
1226
1351
|
* request named its destination by `url` — where that endpoint's signing
|
|
1227
1352
|
* secret is (W9-44). Present only then, because only then can the caller have
|
|
1228
1353
|
* an endpoint whose secret it never held.
|
|
1229
1354
|
*/
|
|
1230
|
-
export const scheduleWrittenViewSchema = scheduleViewSchema.extend({
|
|
1355
|
+
export const scheduleWrittenViewSchema = /* @__PURE__ */ (() => scheduleViewSchema.extend({
|
|
1231
1356
|
endpointSigningSecret: signingSecretNoticeSchema.optional(),
|
|
1232
|
-
});
|
|
1357
|
+
}))();
|
|
1233
1358
|
/**
|
|
1234
1359
|
* The four ways a caller may say when a schedule happens.
|
|
1235
1360
|
*
|
|
@@ -1259,7 +1384,7 @@ export const RECURRENCE_REQUEST_FIELDS = [
|
|
|
1259
1384
|
* `ambiguous_schedule`, and a scheduler that silently picks one of two instants
|
|
1260
1385
|
* a customer named is the worst kind of helpful.
|
|
1261
1386
|
*/
|
|
1262
|
-
export const scheduleRequestSchema = z
|
|
1387
|
+
export const scheduleRequestSchema = /* @__PURE__ */ (() => z
|
|
1263
1388
|
.object({
|
|
1264
1389
|
autoPauseAfterFailures: autoPauseAfterFailuresSchema.optional(),
|
|
1265
1390
|
http: httpRequestSchema.optional(),
|
|
@@ -1361,20 +1486,27 @@ export const scheduleRequestSchema = z
|
|
|
1361
1486
|
*/
|
|
1362
1487
|
url: callbackUrlSchema().optional(),
|
|
1363
1488
|
/**
|
|
1364
|
-
* 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).
|
|
1365
1491
|
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
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
|
|
1368
1496
|
* the caller whose nightly cron really is a human waiting, or whose
|
|
1369
|
-
* thirty-second one-shot really is a campaign
|
|
1370
|
-
*
|
|
1497
|
+
* thirty-second one-shot really is a campaign.
|
|
1498
|
+
*
|
|
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.
|
|
1371
1504
|
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
1375
|
-
* than obtained more of the service.
|
|
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.
|
|
1376
1508
|
*/
|
|
1377
|
-
latencyClass:
|
|
1509
|
+
latencyClass: requestedLatencyClassSchema.optional(),
|
|
1378
1510
|
/**
|
|
1379
1511
|
* What to do when an occurrence reaches its first attempt late.
|
|
1380
1512
|
*
|
|
@@ -1493,7 +1625,7 @@ export const scheduleRequestSchema = z
|
|
|
1493
1625
|
path: ["delivery", "completion"],
|
|
1494
1626
|
});
|
|
1495
1627
|
}
|
|
1496
|
-
});
|
|
1628
|
+
}))();
|
|
1497
1629
|
/** Resolve the lateness settings a create or replacement request runs under. */
|
|
1498
1630
|
export function deriveLatenessSettings(request) {
|
|
1499
1631
|
const recurrenceKind = request.cron !== undefined
|
|
@@ -1529,29 +1661,46 @@ export function deriveDeliverySettings(request) {
|
|
|
1529
1661
|
onOverlap: request.delivery?.onOverlap ?? DEFAULT_DELIVERY_OVERLAP_POLICY,
|
|
1530
1662
|
};
|
|
1531
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
|
+
*/
|
|
1532
1689
|
export function deriveLatencyClass(request, nowMs) {
|
|
1533
|
-
|
|
1534
|
-
return request.latencyClass;
|
|
1535
|
-
}
|
|
1690
|
+
const requested = request.latencyClass ?? "auto";
|
|
1536
1691
|
if (request.cron !== undefined || request.everySeconds !== undefined) {
|
|
1537
|
-
return
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
if (request.delaySeconds !== undefined) {
|
|
1541
|
-
return request.delaySeconds * 1000 <= threshold ? "realtime" : "bulk";
|
|
1542
|
-
}
|
|
1543
|
-
if (request.at === undefined) {
|
|
1544
|
-
// No recurrence at all, which the schema refuses before this is reached.
|
|
1545
|
-
// Answered rather than thrown so a caller of this function on an unvalidated
|
|
1546
|
-
// body gets the conservative class instead of an exception.
|
|
1547
|
-
return "bulk";
|
|
1548
|
-
}
|
|
1549
|
-
const atMs = Date.parse(request.at);
|
|
1550
|
-
if (Number.isNaN(atMs)) {
|
|
1551
|
-
return "bulk";
|
|
1692
|
+
return resolveService(requested, request.everySeconds === undefined
|
|
1693
|
+
? SCHEDULING_BOUNDS.everySecondsMin * 1000
|
|
1694
|
+
: request.everySeconds * 1000).latencyClass;
|
|
1552
1695
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
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;
|
|
1557
1706
|
}
|