@greatwait/contracts 0.1.0-alpha.3 → 0.1.0-alpha.4
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/README.md +11 -9
- package/dist/authority.d.ts +33 -27
- package/dist/authority.js +12 -2
- package/dist/billing.d.ts +1001 -0
- package/dist/billing.js +684 -0
- package/dist/callback-safety.d.ts +2 -2
- package/dist/callback-safety.js +2 -2
- package/dist/compatibility.d.ts +3 -3
- package/dist/compatibility.js +1 -1
- package/dist/data-export.d.ts +67 -0
- package/dist/data-export.js +182 -0
- package/dist/endpoints.d.ts +181 -240
- package/dist/endpoints.js +77 -81
- package/dist/events.d.ts +17 -17
- package/dist/events.js +1 -1
- package/dist/first-value-journey.d.ts +49 -55
- package/dist/first-value-journey.js +6 -13
- package/dist/forecast.d.ts +71 -71
- package/dist/forecast.js +1 -1
- package/dist/identifiers.d.ts +20 -20
- package/dist/identifiers.js +1 -1
- package/dist/identity.d.ts +41 -37
- package/dist/identity.js +5 -4
- package/dist/index.d.ts +10 -5
- package/dist/index.js +10 -5
- package/dist/mcp.d.ts +21 -19
- package/dist/mcp.js +1 -1
- package/dist/naming.d.ts +45 -0
- package/dist/naming.js +159 -0
- package/dist/oauth.d.ts +186 -172
- package/dist/oauth.js +1 -1
- package/dist/operation-definitions.d.ts +9 -0
- package/dist/operation-definitions.js +7 -0
- package/dist/operations.d.ts +4162 -2171
- package/dist/operations.js +2679 -908
- package/dist/organization-lifecycle.d.ts +34 -0
- package/dist/organization-lifecycle.js +122 -0
- package/dist/partition.d.ts +26 -0
- package/dist/policy.d.ts +4 -4
- package/dist/policy.js +9 -5
- package/dist/problems.d.ts +32 -26
- package/dist/problems.js +6 -2
- package/dist/protocol.d.ts +204 -75
- package/dist/protocol.js +108 -36
- package/dist/scheduling-records.d.ts +343 -285
- package/dist/scheduling-records.js +1 -1
- package/dist/scheduling.d.ts +670 -360
- package/dist/scheduling.js +382 -56
- package/dist/secrets.d.ts +119 -105
- package/dist/secrets.js +1 -1
- package/dist/signatures.d.ts +74 -74
- package/dist/signatures.js +1 -1
- package/dist/tenancy.d.ts +142 -68
- package/dist/tenancy.js +70 -17
- package/dist/test-sink.d.ts +127 -127
- package/dist/test-sink.js +1 -1
- package/dist/time.d.ts +4 -4
- package/dist/time.js +1 -1
- package/dist/timing.d.ts +65 -0
- package/dist/timing.js +82 -0
- package/dist/usage.d.ts +103 -47
- package/dist/usage.js +197 -31
- package/dist/workspace.d.ts +34 -34
- package/dist/workspace.js +1 -1
- package/dist/zod-values.d.ts +3 -0
- package/dist/zod-values.js +2 -0
- package/dist/zod.d.ts +2 -0
- package/dist/zod.js +2 -0
- package/generated/v1/examples/attempts.list.response.json +4 -0
- package/generated/v1/examples/attempts.read.response.json +4 -0
- package/generated/v1/examples/billing.autotopup.request.json +5 -0
- package/generated/v1/examples/billing.autotopup.response.json +120 -0
- package/generated/v1/examples/billing.autotopuppreview.request.json +19 -0
- package/generated/v1/examples/billing.autotopuppreview.response.json +45 -0
- package/generated/v1/examples/billing.budgets.request.json +13 -0
- package/generated/v1/examples/billing.budgets.response.json +22 -0
- package/generated/v1/examples/billing.ceiling.request.json +8 -0
- package/generated/v1/examples/billing.ceiling.response.json +120 -0
- package/generated/v1/examples/billing.change.request.json +5 -0
- package/generated/v1/examples/billing.change.response.json +12 -0
- package/generated/v1/examples/billing.funding.response.json +120 -0
- package/generated/v1/examples/billing.fundingquote.request.json +7 -0
- package/generated/v1/examples/billing.fundingquote.response.json +13 -0
- package/generated/v1/examples/billing.ledger.response.json +23 -0
- package/generated/v1/examples/billing.notices.response.json +41 -0
- package/generated/v1/examples/billing.notificationsettings.response.json +17 -0
- package/generated/v1/examples/billing.notificationsettingsupdate.request.json +9 -0
- package/generated/v1/examples/billing.notificationsettingsupdate.response.json +17 -0
- package/generated/v1/examples/billing.portal.response.json +3 -0
- package/generated/v1/examples/billing.projects.response.json +12 -0
- package/generated/v1/examples/billing.purchase.request.json +5 -0
- package/generated/v1/examples/billing.purchase.response.json +120 -0
- package/generated/v1/examples/billing.quote.request.json +4 -0
- package/generated/v1/examples/billing.quote.response.json +19 -0
- package/generated/v1/examples/billing.reconcile.response.json +73 -0
- package/generated/v1/examples/billing.refundrequest.request.json +10 -0
- package/generated/v1/examples/billing.refundrequest.response.json +11 -0
- package/generated/v1/examples/billing.refundresolution.request.json +4 -0
- package/generated/v1/examples/billing.refundresolution.response.json +120 -0
- package/generated/v1/examples/billing.summary.response.json +73 -0
- package/generated/v1/examples/endpoints.acknowledgeSigningKeyRotation.response.json +18 -0
- package/generated/v1/examples/endpoints.create.response.json +1 -1
- package/generated/v1/examples/endpoints.disable.response.json +0 -1
- package/generated/v1/examples/{endpoints.verify.response.json → endpoints.enable.response.json} +4 -5
- package/generated/v1/examples/endpoints.list.response.json +1 -2
- package/generated/v1/examples/endpoints.read.response.json +2 -2
- package/generated/v1/examples/endpoints.revealSigningSecret.response.json +7 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.response.json +1 -2
- package/generated/v1/examples/endpoints.update.response.json +1 -1
- package/generated/v1/examples/executions.list.response.json +20 -0
- package/generated/v1/examples/exports.cancel.response.json +11 -0
- package/generated/v1/examples/exports.create.request.json +1 -0
- package/generated/v1/examples/exports.create.response.json +8 -0
- package/generated/v1/examples/exports.part.response.json +5 -0
- package/generated/v1/examples/exports.read.response.json +8 -0
- package/generated/v1/examples/occurrences.read.response.json +4 -0
- package/generated/v1/examples/organizations.close.request.json +5 -0
- package/generated/v1/examples/organizations.close.response.json +10 -0
- package/generated/v1/examples/organizations.lifecycle.response.json +10 -0
- package/generated/v1/examples/organizations.restore.request.json +3 -0
- package/generated/v1/examples/organizations.restore.response.json +10 -0
- package/generated/v1/examples/projects.archive.request.json +3 -0
- package/generated/v1/examples/projects.archive.response.json +14 -0
- package/generated/v1/examples/projects.create.response.json +10 -1
- package/generated/v1/examples/projects.delete.response.json +9 -0
- package/generated/v1/examples/projects.list.response.json +12 -2
- package/generated/v1/examples/projects.rename.request.json +3 -0
- package/generated/v1/examples/projects.rename.response.json +14 -0
- package/generated/v1/examples/schedules.archive.response.json +3 -0
- package/generated/v1/examples/schedules.cancel.response.json +3 -0
- package/generated/v1/examples/schedules.create.request.json +4 -0
- package/generated/v1/examples/schedules.create.response.json +3 -0
- package/generated/v1/examples/schedules.list.response.json +3 -0
- package/generated/v1/examples/schedules.pause.response.json +3 -0
- package/generated/v1/examples/schedules.read.response.json +6 -0
- package/generated/v1/examples/schedules.replace.request.json +4 -0
- package/generated/v1/examples/schedules.replace.response.json +3 -0
- package/generated/v1/examples/schedules.resume.response.json +3 -0
- package/generated/v1/examples/timing.summary.response.json +35 -0
- package/generated/v1/examples/usage.read.response.json +1 -0
- package/generated/v1/first-value-journey.json +19 -10
- package/generated/v1/manifest.json +936 -23
- package/generated/v1/openapi.json +29983 -5784
- package/generated/v1/policy-matrix.json +278 -47
- package/generated/v1/schemas/attempts.list.response.json +24 -1
- package/generated/v1/schemas/attempts.read.response.json +24 -1
- package/generated/v1/schemas/billing.autotopup.request-headers.json +13 -0
- package/generated/v1/schemas/billing.autotopup.request.json +22 -0
- package/generated/v1/schemas/billing.autotopup.response-headers.json +10 -0
- package/generated/v1/schemas/billing.autotopup.response.json +552 -0
- package/generated/v1/schemas/billing.autotopuppreview.request.json +77 -0
- package/generated/v1/schemas/billing.autotopuppreview.response.json +277 -0
- package/generated/v1/schemas/billing.budgets.request-headers.json +13 -0
- package/generated/v1/schemas/billing.budgets.request.json +66 -0
- package/generated/v1/schemas/billing.budgets.response-headers.json +10 -0
- package/generated/v1/schemas/billing.budgets.response.json +92 -0
- package/generated/v1/schemas/billing.ceiling.request-headers.json +13 -0
- package/generated/v1/schemas/billing.ceiling.request.json +40 -0
- package/generated/v1/schemas/billing.ceiling.response-headers.json +10 -0
- package/generated/v1/schemas/billing.ceiling.response.json +552 -0
- package/generated/v1/schemas/billing.change.request-headers.json +13 -0
- package/generated/v1/schemas/billing.change.request.json +22 -0
- package/generated/v1/schemas/billing.change.response.json +119 -0
- package/generated/v1/schemas/billing.funding.response.json +552 -0
- package/generated/v1/schemas/billing.fundingquote.request.json +28 -0
- package/generated/v1/schemas/billing.fundingquote.response.json +61 -0
- package/generated/v1/schemas/billing.ledger.query.json +16 -0
- package/generated/v1/schemas/billing.ledger.response.json +125 -0
- package/generated/v1/schemas/billing.notices.query.json +16 -0
- package/generated/v1/schemas/billing.notices.response.json +278 -0
- package/generated/v1/schemas/billing.notificationsettings.response.json +70 -0
- package/generated/v1/schemas/billing.notificationsettingsupdate.request-headers.json +13 -0
- package/generated/v1/schemas/billing.notificationsettingsupdate.request.json +36 -0
- package/generated/v1/schemas/billing.notificationsettingsupdate.response-headers.json +10 -0
- package/generated/v1/schemas/billing.notificationsettingsupdate.response.json +70 -0
- package/generated/v1/schemas/billing.portal.response.json +11 -0
- package/generated/v1/schemas/billing.projects.response.json +119 -0
- package/generated/v1/schemas/billing.purchase.request-headers.json +13 -0
- package/generated/v1/schemas/billing.purchase.request.json +22 -0
- package/generated/v1/schemas/billing.purchase.response-headers.json +10 -0
- package/generated/v1/schemas/billing.purchase.response.json +552 -0
- package/generated/v1/schemas/billing.quote.request.json +20 -0
- package/generated/v1/schemas/billing.quote.response.json +86 -0
- package/generated/v1/schemas/billing.reconcile.response.json +468 -0
- package/generated/v1/schemas/billing.refundrequest.request-headers.json +13 -0
- package/generated/v1/schemas/billing.refundrequest.request.json +42 -0
- package/generated/v1/schemas/billing.refundrequest.response-headers.json +10 -0
- package/generated/v1/schemas/billing.refundrequest.response.json +45 -0
- package/generated/v1/schemas/billing.refundresolution.request-headers.json +13 -0
- package/generated/v1/schemas/billing.refundresolution.request.json +61 -0
- package/generated/v1/schemas/billing.refundresolution.response-headers.json +10 -0
- package/generated/v1/schemas/billing.refundresolution.response.json +552 -0
- package/generated/v1/schemas/billing.summary.response.json +468 -0
- package/generated/v1/schemas/context.read.response.json +2 -0
- package/generated/v1/schemas/credentials.connection.response.json +2 -0
- package/generated/v1/schemas/credentials.create.request.json +2 -0
- package/generated/v1/schemas/credentials.create.response.json +2 -0
- package/generated/v1/schemas/credentials.list.response.json +2 -0
- package/generated/v1/schemas/credentials.revoke.response.json +2 -0
- package/generated/v1/schemas/credentials.rotate.response.json +4 -0
- package/generated/v1/schemas/{endpoints.verify.request-headers.json → endpoints.acknowledgeSigningKeyRotation.request-headers.json} +2 -1
- package/generated/v1/schemas/{endpoints.verify.response.json → endpoints.acknowledgeSigningKeyRotation.response.json} +8 -21
- package/generated/v1/schemas/endpoints.create.request.json +5 -0
- package/generated/v1/schemas/endpoints.create.response.json +8 -21
- package/generated/v1/schemas/endpoints.disable.response.json +8 -21
- package/generated/v1/schemas/endpoints.enable.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.enable.response.json +87 -0
- package/generated/v1/schemas/endpoints.list.response.json +8 -21
- package/generated/v1/schemas/endpoints.read.response.json +8 -21
- package/generated/v1/schemas/endpoints.revealSigningSecret.response.json +43 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +8 -21
- package/generated/v1/schemas/endpoints.update.request.json +5 -0
- package/generated/v1/schemas/endpoints.update.response.json +8 -21
- package/generated/v1/schemas/executions.list.query.json +46 -0
- package/generated/v1/schemas/executions.list.response.json +116 -0
- package/generated/v1/schemas/exports.cancel.response.json +48 -0
- package/generated/v1/schemas/exports.create.request.json +6 -0
- package/generated/v1/schemas/exports.create.response.json +38 -0
- package/generated/v1/schemas/exports.part.query.json +11 -0
- package/generated/v1/schemas/exports.part.response.json +22 -0
- package/generated/v1/schemas/exports.read.response.json +38 -0
- package/generated/v1/schemas/grants.list.response.json +2 -0
- package/generated/v1/schemas/grants.revoke.response.json +2 -0
- package/generated/v1/schemas/occurrences.list.query.json +5 -0
- package/generated/v1/schemas/occurrences.list.response.json +23 -0
- package/generated/v1/schemas/occurrences.read.response.json +23 -0
- package/generated/v1/schemas/occurrences.redeliver.response.json +24 -1
- package/generated/v1/schemas/organizations.close.request.json +20 -0
- package/generated/v1/schemas/organizations.close.response.json +68 -0
- package/generated/v1/schemas/organizations.lifecycle.response.json +68 -0
- package/generated/v1/schemas/organizations.restore.request.json +13 -0
- package/generated/v1/schemas/organizations.restore.response.json +68 -0
- package/generated/v1/schemas/projects.archive.request-headers.json +11 -0
- package/generated/v1/schemas/projects.archive.request.json +10 -0
- package/generated/v1/schemas/projects.archive.response-headers.json +11 -0
- package/generated/v1/schemas/projects.archive.response.json +79 -0
- package/generated/v1/schemas/projects.create.response.json +55 -1
- package/generated/v1/schemas/projects.delete.request-headers.json +11 -0
- package/generated/v1/schemas/projects.delete.response.json +37 -0
- package/generated/v1/schemas/projects.list.response.json +61 -2
- package/generated/v1/schemas/projects.rename.request-headers.json +11 -0
- package/generated/v1/schemas/projects.rename.request.json +12 -0
- package/generated/v1/schemas/projects.rename.response-headers.json +11 -0
- package/generated/v1/schemas/projects.rename.response.json +79 -0
- package/generated/v1/schemas/schedules.archive.response.json +90 -2
- package/generated/v1/schemas/schedules.cancel.response.json +90 -2
- package/generated/v1/schemas/schedules.create.request.json +24 -3
- package/generated/v1/schemas/schedules.create.response.json +90 -2
- package/generated/v1/schemas/schedules.list.query.json +13 -0
- package/generated/v1/schemas/schedules.list.response.json +67 -2
- package/generated/v1/schemas/schedules.pause.response.json +90 -2
- package/generated/v1/schemas/schedules.read.response.json +90 -2
- package/generated/v1/schemas/schedules.replace.request.json +24 -3
- package/generated/v1/schemas/schedules.replace.response.json +90 -2
- package/generated/v1/schemas/schedules.resume.response.json +90 -2
- package/generated/v1/schemas/timing.summary.response.json +191 -0
- package/generated/v1/schemas/usage.read.query.json +7 -0
- package/generated/v1/schemas/usage.read.response.json +36 -1
- package/generated/v1/snippets/billing.autotopup.json +6 -0
- package/generated/v1/snippets/billing.autotopuppreview.json +6 -0
- package/generated/v1/snippets/billing.budgets.json +6 -0
- package/generated/v1/snippets/billing.ceiling.json +6 -0
- package/generated/v1/snippets/billing.change.json +6 -0
- package/generated/v1/snippets/billing.funding.json +6 -0
- package/generated/v1/snippets/billing.fundingquote.json +6 -0
- package/generated/v1/snippets/billing.ledger.json +6 -0
- package/generated/v1/snippets/billing.notices.json +6 -0
- package/generated/v1/snippets/billing.notificationsettings.json +6 -0
- package/generated/v1/snippets/billing.notificationsettingsupdate.json +6 -0
- package/generated/v1/snippets/billing.portal.json +6 -0
- package/generated/v1/snippets/billing.projects.json +6 -0
- package/generated/v1/snippets/billing.purchase.json +6 -0
- package/generated/v1/snippets/billing.quote.json +6 -0
- package/generated/v1/snippets/billing.reconcile.json +6 -0
- package/generated/v1/snippets/billing.refundrequest.json +6 -0
- package/generated/v1/snippets/billing.refundresolution.json +6 -0
- package/generated/v1/snippets/billing.summary.json +6 -0
- package/generated/v1/snippets/endpoints.acknowledgeSigningKeyRotation.json +6 -0
- package/generated/v1/snippets/{endpoints.verify.json → endpoints.enable.json} +3 -3
- package/generated/v1/snippets/endpoints.revealSigningSecret.json +6 -0
- package/generated/v1/snippets/executions.list.json +6 -0
- package/generated/v1/snippets/exports.cancel.json +6 -0
- package/generated/v1/snippets/exports.create.json +6 -0
- package/generated/v1/snippets/exports.part.json +6 -0
- package/generated/v1/snippets/exports.read.json +6 -0
- package/generated/v1/snippets/organizations.close.json +6 -0
- package/generated/v1/snippets/organizations.lifecycle.json +6 -0
- package/generated/v1/snippets/organizations.restore.json +6 -0
- package/generated/v1/snippets/projects.archive.json +6 -0
- package/generated/v1/snippets/projects.delete.json +6 -0
- package/generated/v1/snippets/projects.rename.json +6 -0
- package/generated/v1/snippets/schedules.create.json +3 -3
- package/generated/v1/snippets/schedules.replace.json +3 -3
- package/generated/v1/snippets/timing.summary.json +6 -0
- package/package.json +1 -6
- package/dist/callback-ownership.d.ts +0 -58
- package/dist/callback-ownership.js +0 -71
- package/generated/v1/callback-endpoint-verification.json +0 -59
- /package/generated/v1/schemas/{endpoints.verify.response-headers.json → endpoints.acknowledgeSigningKeyRotation.response-headers.json} +0 -0
package/dist/scheduling.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
1
|
+
import { z } from "./zod.js";
|
|
2
2
|
import { attemptIdSchema, deliveryIdSchema, endpointIdSchema, occurrenceIdSchema, scheduleIdSchema, } from "./identifiers.js";
|
|
3
3
|
import { CALLBACK_ATTEMPT_DIAGNOSTICS } from "./delivery.js";
|
|
4
|
-
import { pageSchema, versionSchema } from "./protocol.js";
|
|
4
|
+
import { pageRequestSchema, pageSchema, versionSchema, } from "./protocol.js";
|
|
5
5
|
import { callbackTriggerSchema, } from "./signatures.js";
|
|
6
6
|
import { durationSecondsSchema, timeZoneSchema, timestampSchema } from "./time.js";
|
|
7
7
|
/**
|
|
@@ -55,29 +55,87 @@ export const scheduleStatusSchema = z.enum(SCHEDULE_STATUSES);
|
|
|
55
55
|
*/
|
|
56
56
|
export const LATENCY_CLASSES = ["realtime", "bulk"];
|
|
57
57
|
export const latencyClassSchema = z.enum(LATENCY_CLASSES);
|
|
58
|
+
/**
|
|
59
|
+
* What happens when an occurrence reaches its first attempt after its planned
|
|
60
|
+
* instant.
|
|
61
|
+
*
|
|
62
|
+
* `skipAhead` never carries a recurring first arrival across its next planned
|
|
63
|
+
* instant (and a shorter custom window may retire it earlier). `catchUp`
|
|
64
|
+
* admits one first arrival while it is still inside the schedule's staleness
|
|
65
|
+
* window, even after later recurrence instants passed. A one-shot has no next
|
|
66
|
+
* recurrence, so either explicit policy uses its staleness window and its
|
|
67
|
+
* default is `catchUp`. Neither choice applies to an occurrence already on its
|
|
68
|
+
* retry ladder: that work arrived, failed, and keeps the retry/give-up policy
|
|
69
|
+
* it started under.
|
|
70
|
+
*/
|
|
71
|
+
export const LATENESS_POLICIES = ["skipAhead", "catchUp"];
|
|
72
|
+
export const latenessPolicySchema = z.enum(LATENESS_POLICIES);
|
|
73
|
+
/** The inherited one-shot staleness window: twenty-four hours. */
|
|
74
|
+
export const DEFAULT_ONE_SHOT_MAX_STALENESS_SECONDS = 86_400;
|
|
75
|
+
/**
|
|
76
|
+
* The per-schedule lateness choices a caller may state.
|
|
77
|
+
*
|
|
78
|
+
* Both members are optional so a caller can override only the part whose
|
|
79
|
+
* default does not fit. The policy defaults from recurrence kind. An omitted
|
|
80
|
+
* staleness bound uses the recurrence's own default: twenty-four hours for a
|
|
81
|
+
* one-shot, and one recurrence interval for a recurring schedule.
|
|
82
|
+
*/
|
|
83
|
+
export const scheduleLatenessRequestSchema = z
|
|
84
|
+
.object({
|
|
85
|
+
maxStalenessSeconds: durationSecondsSchema
|
|
86
|
+
.positive("must be at least 1 second")
|
|
87
|
+
.max(Number.MAX_SAFE_INTEGER, "must be a safely representable duration")
|
|
88
|
+
.optional(),
|
|
89
|
+
policy: latenessPolicySchema.optional(),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
/**
|
|
93
|
+
* Lateness settings as a schedule reads back.
|
|
94
|
+
*
|
|
95
|
+
* The policy is always effective and therefore required. The numeric member
|
|
96
|
+
* remains optional because it records a per-schedule override, not a duplicated
|
|
97
|
+
* copy of a default that can be derived from the recurrence.
|
|
98
|
+
*/
|
|
99
|
+
export const scheduleLatenessSchema = scheduleLatenessRequestSchema.extend({
|
|
100
|
+
policy: latenessPolicySchema,
|
|
101
|
+
});
|
|
58
102
|
/* -------------------------------------------------------------------------- */
|
|
59
103
|
/* Delivery duration */
|
|
60
104
|
/* -------------------------------------------------------------------------- */
|
|
61
105
|
/**
|
|
62
|
-
*
|
|
106
|
+
* The durations this product used to sell, one rung at a time.
|
|
107
|
+
*
|
|
108
|
+
* **Kept, and no longer the whole of what is accepted** (FXO-2258). The ladder
|
|
109
|
+
* was the argument that 47 seconds is a question with no answer — past the
|
|
110
|
+
* point where a delivery may occupy a partition slot, short of the point where
|
|
111
|
+
* it is worth carrying separately. The operator overruled it after the walk:
|
|
112
|
+
* "This can have a completely user definable timeout, they don't cost us hardly
|
|
113
|
+
* anything, but we should cap at something anyway, 24h is still fine."
|
|
63
114
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
115
|
+
* What the rungs were actually carrying survives without them. The line that
|
|
116
|
+
* matters commercially is {@link FREE_DELIVERY_DURATION_SECONDS} — above thirty
|
|
117
|
+
* seconds a delivery is carried outside the partition and needs a plan that
|
|
118
|
+
* allows it — and that line is a comparison, not a membership test. The line
|
|
119
|
+
* that matters technically is {@link RESPONSE_DELIVERY_MAX_DURATION_SECONDS},
|
|
120
|
+
* which is also a comparison. Neither ever needed eight fixed values to be
|
|
121
|
+
* expressible; the ladder was how they were *sold*, and per-rung pricing is
|
|
122
|
+
* what the operator withdrew.
|
|
70
123
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* where it is worth carrying separately. The ladder is what makes "which side of
|
|
75
|
-
* the line is this" a decision somebody took rather than a comparison somebody
|
|
76
|
-
* wrote.
|
|
124
|
+
* The list stays because a schedule saved on a rung must keep working, because
|
|
125
|
+
* the delivery-class rules are still exercised against exactly these values,
|
|
126
|
+
* and because the dashboard offers them as the shortcuts most people want.
|
|
77
127
|
*/
|
|
78
128
|
export const DELIVERY_DURATION_TIERS = [
|
|
79
129
|
5, 30, 300, 900, 1_800, 3_600, 43_200, 86_400,
|
|
80
130
|
];
|
|
131
|
+
/**
|
|
132
|
+
* The longest duration any delivery may be given, in seconds.
|
|
133
|
+
*
|
|
134
|
+
* Twenty-four hours, which is the ladder's own top rung — so widening the field
|
|
135
|
+
* to a free-form integer admits nothing that was not already for sale. "24h is
|
|
136
|
+
* still fine."
|
|
137
|
+
*/
|
|
138
|
+
export const MAX_DELIVERY_DURATION_SECONDS = 86_400;
|
|
81
139
|
/**
|
|
82
140
|
* What a schedule gets without asking, in seconds.
|
|
83
141
|
*
|
|
@@ -134,8 +192,26 @@ export const FREE_DELIVERY_DURATION_SECONDS = 30;
|
|
|
134
192
|
* than thirty minutes has to do is answer immediately and call us back.
|
|
135
193
|
*/
|
|
136
194
|
export const RESPONSE_DELIVERY_MAX_DURATION_SECONDS = 1_800;
|
|
195
|
+
/**
|
|
196
|
+
* How long the receiver may take to answer, in whole seconds.
|
|
197
|
+
*
|
|
198
|
+
* **A bounded integer since FXO-2258, and every value the enum accepted is
|
|
199
|
+
* still accepted.** A widening is the one shape of contract change that cannot
|
|
200
|
+
* break a caller: a request that was valid before is valid now, and a stored
|
|
201
|
+
* schedule reads back and re-saves unchanged.
|
|
202
|
+
*
|
|
203
|
+
* The two bounds that are real are still enforced, and neither of them was ever
|
|
204
|
+
* the enum's doing: `maxDurationSeconds` above
|
|
205
|
+
* {@link RESPONSE_DELIVERY_MAX_DURATION_SECONDS} is refused unless `completion`
|
|
206
|
+
* is `callback` (checked in `scheduleRequestSchema`'s refinement, because it is
|
|
207
|
+
* a relation between two fields), and above
|
|
208
|
+
* {@link FREE_DELIVERY_DURATION_SECONDS} it needs a plan that allows it.
|
|
209
|
+
*/
|
|
137
210
|
export const deliveryDurationSecondsSchema = z
|
|
138
|
-
.
|
|
211
|
+
.number()
|
|
212
|
+
.int("must be a whole number of seconds")
|
|
213
|
+
.positive("must be at least 1 second")
|
|
214
|
+
.max(MAX_DELIVERY_DURATION_SECONDS, `must be at most ${String(MAX_DELIVERY_DURATION_SECONDS)} seconds`)
|
|
139
215
|
.describe("How long the receiver may take to answer, in seconds");
|
|
140
216
|
/** Whether a duration is past the point where it needs a plan that allows it. */
|
|
141
217
|
export function isLongRunningDuration(seconds) {
|
|
@@ -247,6 +323,89 @@ export const OCCURRENCE_STATES = [
|
|
|
247
323
|
"skipped_stale",
|
|
248
324
|
];
|
|
249
325
|
export const occurrenceStateSchema = z.enum(OCCURRENCE_STATES);
|
|
326
|
+
/**
|
|
327
|
+
* One project-wide walk over retained occurrences, newest first.
|
|
328
|
+
*
|
|
329
|
+
* There is deliberately no order parameter: the Executions page answers what
|
|
330
|
+
* happened most recently, and making that fixed lets its index and cursor say
|
|
331
|
+
* the same thing. The date range is half-open, like every other time window in
|
|
332
|
+
* this contract, so adjacent requests neither overlap nor leave a gap.
|
|
333
|
+
*/
|
|
334
|
+
export const projectOccurrenceListRequestSchema = pageRequestSchema
|
|
335
|
+
.extend({
|
|
336
|
+
endpoint: endpointIdSchema.optional(),
|
|
337
|
+
from: timestampSchema.optional(),
|
|
338
|
+
outcome: occurrenceStateSchema.optional(),
|
|
339
|
+
schedule: scheduleIdSchema.optional(),
|
|
340
|
+
to: timestampSchema.optional(),
|
|
341
|
+
})
|
|
342
|
+
.refine((request) => request.from === undefined ||
|
|
343
|
+
request.to === undefined ||
|
|
344
|
+
Date.parse(request.from) < Date.parse(request.to), {
|
|
345
|
+
message: "must be later than from",
|
|
346
|
+
path: ["to"],
|
|
347
|
+
});
|
|
348
|
+
const PROJECT_OCCURRENCE_LIST_PARAMETERS = [
|
|
349
|
+
"cursor",
|
|
350
|
+
"limit",
|
|
351
|
+
"endpoint",
|
|
352
|
+
"from",
|
|
353
|
+
"outcome",
|
|
354
|
+
"schedule",
|
|
355
|
+
"to",
|
|
356
|
+
];
|
|
357
|
+
/** Read the project occurrence filters exactly as REST and MCP send them. */
|
|
358
|
+
export function parseProjectOccurrenceListRequest(query) {
|
|
359
|
+
const errors = [];
|
|
360
|
+
const raw = {};
|
|
361
|
+
for (const field of PROJECT_OCCURRENCE_LIST_PARAMETERS) {
|
|
362
|
+
const values = query.getAll(field);
|
|
363
|
+
if (values.length > 1) {
|
|
364
|
+
errors.push({
|
|
365
|
+
pointer: `/${field}`,
|
|
366
|
+
reason: "must be given at most once",
|
|
367
|
+
});
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const value = values[0];
|
|
371
|
+
if (value === undefined)
|
|
372
|
+
continue;
|
|
373
|
+
raw[field] =
|
|
374
|
+
field === "limit" && /^-?\d+$/u.test(value) ? Number(value) : value;
|
|
375
|
+
}
|
|
376
|
+
if (errors.length > 0)
|
|
377
|
+
return { errors, ok: false };
|
|
378
|
+
const parsed = projectOccurrenceListRequestSchema.safeParse(raw);
|
|
379
|
+
if (parsed.success)
|
|
380
|
+
return { ok: true, page: parsed.data };
|
|
381
|
+
return {
|
|
382
|
+
errors: parsed.error.issues.map((issue) => ({
|
|
383
|
+
pointer: `/${issue.path.join("/")}`,
|
|
384
|
+
reason: issue.message,
|
|
385
|
+
})),
|
|
386
|
+
ok: false,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* The exact operation and filters a project occurrence cursor belongs to.
|
|
391
|
+
*
|
|
392
|
+
* Cursor and limit do not affect which ordered collection is being walked, so
|
|
393
|
+
* neither belongs in the binding. Every actual filter does. Optional values
|
|
394
|
+
* stay absent rather than becoming empty strings, which are invalid inputs and
|
|
395
|
+
* must never collide with an unfiltered request.
|
|
396
|
+
*/
|
|
397
|
+
export function projectOccurrenceCursorBinding(request) {
|
|
398
|
+
return {
|
|
399
|
+
operation: "executions.list",
|
|
400
|
+
parameters: {
|
|
401
|
+
...(request.endpoint === undefined ? {} : { endpoint: request.endpoint }),
|
|
402
|
+
...(request.from === undefined ? {} : { from: request.from }),
|
|
403
|
+
...(request.outcome === undefined ? {} : { outcome: request.outcome }),
|
|
404
|
+
...(request.schedule === undefined ? {} : { schedule: request.schedule }),
|
|
405
|
+
...(request.to === undefined ? {} : { to: request.to }),
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
250
409
|
/**
|
|
251
410
|
* The states an occurrence never leaves.
|
|
252
411
|
*
|
|
@@ -296,7 +455,7 @@ export const ATTEMPT_REMEDIATIONS = [
|
|
|
296
455
|
"none",
|
|
297
456
|
"review_retry_policy",
|
|
298
457
|
"inspect_receiver",
|
|
299
|
-
"
|
|
458
|
+
"review_endpoint",
|
|
300
459
|
"replace_payload",
|
|
301
460
|
"contact_support",
|
|
302
461
|
"review_before_redelivery",
|
|
@@ -320,7 +479,7 @@ export function remediationForAttempt(diagnostic) {
|
|
|
320
479
|
return "none";
|
|
321
480
|
case "endpoint_unavailable":
|
|
322
481
|
case "endpoint_unsafe":
|
|
323
|
-
return "
|
|
482
|
+
return "review_endpoint";
|
|
324
483
|
case "http_refused":
|
|
325
484
|
case "http_terminal":
|
|
326
485
|
// The same advice for two different futures, deliberately. Both mean the
|
|
@@ -409,17 +568,37 @@ export const SCHEDULING_BOUNDS = {
|
|
|
409
568
|
* Attempts made against one occurrence before it is terminal, including the
|
|
410
569
|
* first. Bounded so a permanently broken receiver cannot make one occurrence
|
|
411
570
|
* cost an unbounded amount of work.
|
|
571
|
+
*
|
|
572
|
+
* **Eight, and the number is load-bearing against a second limit rather than
|
|
573
|
+
* on its own.** `MAX_BUDGETED_DELIVERY_FAILURES` stops an occurrence after
|
|
574
|
+
* five failures the *receiver* caused; a rate limit and this product's own
|
|
575
|
+
* infrastructure faults are exempt from that count. The gap between the two
|
|
576
|
+
* is what the exemption spends — an occurrence throttled twice still has
|
|
577
|
+
* five receiver failures left. Lowering this to five was considered during
|
|
578
|
+
* the W4-10 walk and rejected once the pair was understood: it would make
|
|
579
|
+
* both limits fire on the same attempt and leave the exemption buying
|
|
580
|
+
* nothing.
|
|
412
581
|
*/
|
|
413
582
|
attemptsPerOccurrence: 8,
|
|
414
583
|
/**
|
|
415
584
|
* The longest fixed interval a recurring schedule may ask for, in seconds.
|
|
416
585
|
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
586
|
+
* One year. **The thirty days this replaces had no reason of its own** — it
|
|
587
|
+
* was the copied scheduler's `MAX_INTERVAL_SECONDS`, carried over on the
|
|
588
|
+
* belief that "a Durable Object alarm is not arbitrarily far in the future".
|
|
589
|
+
* Cloudflare documents no maximum distance for `setAlarm()`, and the belief
|
|
590
|
+
* predates the PostgreSQL authority besides: the durable fact is a row, and
|
|
591
|
+
* the alarm is only what wakes a bucket to read it.
|
|
592
|
+
*
|
|
593
|
+
* What makes a distant alarm safe is not the alarm. `TimerBucketRepairer`
|
|
594
|
+
* asks PostgreSQL for occurrences whose instant has passed and re-signals
|
|
595
|
+
* their bucket, on committed truth, never asking whether an alarm was armed
|
|
596
|
+
* — so an alarm that is somehow lost costs lateness rather than the run.
|
|
597
|
+
*
|
|
598
|
+
* Widening a bound is additive by `classifyChange` and narrowing it is
|
|
599
|
+
* breaking, so this direction is the safe one to move in.
|
|
421
600
|
*/
|
|
422
|
-
everySecondsMax:
|
|
601
|
+
everySecondsMax: 365 * 24 * 60 * 60,
|
|
423
602
|
/**
|
|
424
603
|
* The shortest fixed interval a recurring schedule may ask for, in seconds.
|
|
425
604
|
*
|
|
@@ -573,6 +752,10 @@ export const recurrenceSchema = z.discriminatedUnion("kind", [
|
|
|
573
752
|
timeZone: timeZoneSchema,
|
|
574
753
|
}),
|
|
575
754
|
]);
|
|
755
|
+
/** The customer-safe default for a schedule's recurrence kind. */
|
|
756
|
+
export function defaultLatenessPolicyForRecurrence(kind) {
|
|
757
|
+
return kind === "at" ? "catchUp" : "skipAhead";
|
|
758
|
+
}
|
|
576
759
|
/* -------------------------------------------------------------------------- */
|
|
577
760
|
/* Public resources */
|
|
578
761
|
/* -------------------------------------------------------------------------- */
|
|
@@ -584,6 +767,23 @@ export const recurrenceSchema = z.discriminatedUnion("kind", [
|
|
|
584
767
|
* endpoint id is useful definition metadata; the encrypted payload is content
|
|
585
768
|
* and stays only in the authoritative partition.
|
|
586
769
|
*/
|
|
770
|
+
/**
|
|
771
|
+
* A schedule's name: for people, never an identifier, and free to collide.
|
|
772
|
+
*
|
|
773
|
+
* Generated on save when nobody types one (W4-14, FXO-2255) — "Every 60s →
|
|
774
|
+
* flowxo.com" — and stored as any other name, so editing the recurrence
|
|
775
|
+
* later rewrites nothing. Two schedules may share a name: two on the same
|
|
776
|
+
* cadence against the same host both generate the same one, and the operator
|
|
777
|
+
* chose to let them collide, with the other columns and the identifier
|
|
778
|
+
* telling them apart. Endpoints get the opposite rule.
|
|
779
|
+
*/
|
|
780
|
+
/** The most a schedule's name may hold; the generated one is cut to fit it. */
|
|
781
|
+
export const SCHEDULE_NAME_MAX_LENGTH = 120;
|
|
782
|
+
export const scheduleNameSchema = z
|
|
783
|
+
.string()
|
|
784
|
+
.trim()
|
|
785
|
+
.min(1, "must not be blank")
|
|
786
|
+
.max(SCHEDULE_NAME_MAX_LENGTH, `must be at most ${String(SCHEDULE_NAME_MAX_LENGTH)} characters`);
|
|
587
787
|
export const scheduleSchema = z.object({
|
|
588
788
|
/**
|
|
589
789
|
* When the schedule was filed away, and absent until it is.
|
|
@@ -596,6 +796,20 @@ export const scheduleSchema = z.object({
|
|
|
596
796
|
* terminal status — archiving is filing, not stopping.
|
|
597
797
|
*/
|
|
598
798
|
archivedAt: timestampSchema.optional(),
|
|
799
|
+
/**
|
|
800
|
+
* The payload the schedule carries, as it was given.
|
|
801
|
+
*
|
|
802
|
+
* Present on a read when the schedule has one; left off listings, which are
|
|
803
|
+
* served from a projection that holds a summary. **Readable by anyone who
|
|
804
|
+
* can read the schedule** — the dashboard, the API, the SDK and MCP alike —
|
|
805
|
+
* since FXO-2243, chosen with the cost stated: a payload somebody put a
|
|
806
|
+
* secret in is readable by anything holding a read key, and appears in an
|
|
807
|
+
* agent's tool history. The write-only custody this replaces answered that
|
|
808
|
+
* with a blanket rule over the whole body — "I've never used a platform
|
|
809
|
+
* where my configured payload was hidden from me" — and W4-13 answers it
|
|
810
|
+
* per field instead, with a header value that is a secret marked as one.
|
|
811
|
+
*/
|
|
812
|
+
data: z.json().optional(),
|
|
599
813
|
createdAt: timestampSchema,
|
|
600
814
|
/**
|
|
601
815
|
* The delivery settings in force, with the defaults filled in.
|
|
@@ -626,9 +840,33 @@ export const scheduleSchema = z.object({
|
|
|
626
840
|
.optional(),
|
|
627
841
|
/** The verified reusable callback endpoint this schedule delivers to. */
|
|
628
842
|
endpoint: endpointIdSchema.optional(),
|
|
843
|
+
/**
|
|
844
|
+
* Where the schedule delivers, as an address (W4-14, FXO-2255).
|
|
845
|
+
*
|
|
846
|
+
* Beside the identifier rather than instead of it, on the listing and the
|
|
847
|
+
* read alike, so every caller — dashboard, SDK, agent — sees a readable
|
|
848
|
+
* destination without a second request. Present when the schedule has an
|
|
849
|
+
* endpoint the service could name; absent on a timer, and on a row whose
|
|
850
|
+
* endpoint is no longer readable.
|
|
851
|
+
*/
|
|
852
|
+
endpointUrl: z.string().url().optional(),
|
|
629
853
|
generation: generationSchema,
|
|
630
854
|
id: scheduleIdSchema,
|
|
631
855
|
latencyClass: latencyClassSchema,
|
|
856
|
+
/**
|
|
857
|
+
* What this schedule does with an occurrence that reaches its first attempt
|
|
858
|
+
* late. Present on every schedule so a caller never has to duplicate the
|
|
859
|
+
* recurrence-sensitive policy default. `maxStalenessSeconds` is present only
|
|
860
|
+
* when this schedule overrides its recurrence's window.
|
|
861
|
+
*/
|
|
862
|
+
lateness: scheduleLatenessSchema,
|
|
863
|
+
/**
|
|
864
|
+
* The schedule's name. Every schedule the service has written since W4-14
|
|
865
|
+
* has one — generated on save when none was given — and it is optional on
|
|
866
|
+
* the wire only so a row from before the column, or a reader holding a
|
|
867
|
+
* projection the migration has not reached, parses rather than fails.
|
|
868
|
+
*/
|
|
869
|
+
name: scheduleNameSchema.optional(),
|
|
632
870
|
partitionKey: partitionKeySchema.optional(),
|
|
633
871
|
/**
|
|
634
872
|
* How this schedule recurs, in the vocabulary it will keep being read in.
|
|
@@ -643,6 +881,24 @@ export const scheduleSchema = z.object({
|
|
|
643
881
|
* was converted to an instant once, at acceptance, and reporting the delay
|
|
644
882
|
* back would describe a schedule whose next fire moves every time it is read.
|
|
645
883
|
*/
|
|
884
|
+
/**
|
|
885
|
+
* When this schedule next fires, and absent when nothing is on the books.
|
|
886
|
+
*
|
|
887
|
+
* **The earliest unsettled occurrence, which is a fact rather than a
|
|
888
|
+
* projection** (FXO-2283). It is absent for a schedule that will not fire
|
|
889
|
+
* again — cancelled, archived, or a one-shot that has run — and absent is the
|
|
890
|
+
* truth about those rather than a value withheld: there is no instant to
|
|
891
|
+
* report, and reporting the last one would read as a plan.
|
|
892
|
+
*
|
|
893
|
+
* **This is the one field on a listing row that does not come from the
|
|
894
|
+
* projection.** The page comment below says a listing holds a route and a
|
|
895
|
+
* summary rather than every partition's live occurrence, and that is still
|
|
896
|
+
* true of the *detail* an occurrence carries — its state, its attempts, its
|
|
897
|
+
* identifier. This is one instant, taken by a lateral against a partial index
|
|
898
|
+
* built for it, and it is here because "when does this next run" is the
|
|
899
|
+
* question a list of schedules is read to answer.
|
|
900
|
+
*/
|
|
901
|
+
nextDueAt: timestampSchema.optional(),
|
|
646
902
|
recurrence: recurrenceSchema,
|
|
647
903
|
status: scheduleStatusSchema,
|
|
648
904
|
updatedAt: timestampSchema,
|
|
@@ -659,6 +915,32 @@ export const scheduleSchema = z.object({
|
|
|
659
915
|
* is authoritative, and detail is one call away.
|
|
660
916
|
*/
|
|
661
917
|
export const schedulePageSchema = pageSchema(scheduleSchema);
|
|
918
|
+
function hasCompleteTimingEvidence(value) {
|
|
919
|
+
return ((value.firstAttemptStartedAt === undefined) ===
|
|
920
|
+
(value.firstAttemptLatenessMs === undefined) &&
|
|
921
|
+
(value.firstAttemptStartedAt === undefined) ===
|
|
922
|
+
(value.timingPolicyVersion === undefined));
|
|
923
|
+
}
|
|
924
|
+
function hasConsistentTimingEligibility(value) {
|
|
925
|
+
return value.timingEligibility === undefined
|
|
926
|
+
? value.timingEligibilityReason === undefined
|
|
927
|
+
: value.timingEligibility === "eligible"
|
|
928
|
+
? value.timingEligibilityReason === undefined
|
|
929
|
+
: value.timingEligibilityReason !== undefined;
|
|
930
|
+
}
|
|
931
|
+
const timingEvidenceShape = {
|
|
932
|
+
/** Signed difference from `plannedFor`; a negative value is an early send. */
|
|
933
|
+
firstAttemptLatenessMs: z.int().optional(),
|
|
934
|
+
/** Earliest trusted outbound start across this occurrence's attempts. */
|
|
935
|
+
firstAttemptStartedAt: timestampSchema.optional(),
|
|
936
|
+
/** Absent on records that predate timing evidence, which means unknown. */
|
|
937
|
+
timingEligibility: z
|
|
938
|
+
.enum(["eligible", "customer_blocked", "platform_blocked"])
|
|
939
|
+
.optional(),
|
|
940
|
+
timingEligibilityReason: z.string().min(1).max(128).optional(),
|
|
941
|
+
/** Policy pinned when trusted first-attempt timing evidence is recorded. */
|
|
942
|
+
timingPolicyVersion: z.string().min(1).max(64).optional(),
|
|
943
|
+
};
|
|
662
944
|
/**
|
|
663
945
|
* One concrete occurrence of a schedule.
|
|
664
946
|
*
|
|
@@ -670,6 +952,7 @@ export const schedulePageSchema = pageSchema(scheduleSchema);
|
|
|
670
952
|
export const occurrenceSchema = z
|
|
671
953
|
.object({
|
|
672
954
|
attemptCount: z.number().int().nonnegative(),
|
|
955
|
+
...timingEvidenceShape,
|
|
673
956
|
generation: generationSchema,
|
|
674
957
|
id: occurrenceIdSchema,
|
|
675
958
|
/** When the occurrence was planned to fire. Never earlier than this. */
|
|
@@ -689,6 +972,14 @@ export const occurrenceSchema = z
|
|
|
689
972
|
(occurrence.settledAt !== undefined), {
|
|
690
973
|
message: "settledAt must be present for a terminal state and absent otherwise",
|
|
691
974
|
path: ["settledAt"],
|
|
975
|
+
})
|
|
976
|
+
.refine(hasCompleteTimingEvidence, {
|
|
977
|
+
message: "firstAttemptStartedAt, firstAttemptLatenessMs and timingPolicyVersion must be present together",
|
|
978
|
+
path: ["firstAttemptStartedAt"],
|
|
979
|
+
})
|
|
980
|
+
.refine(hasConsistentTimingEligibility, {
|
|
981
|
+
message: "timingEligibilityReason is required for a blocked population and absent otherwise",
|
|
982
|
+
path: ["timingEligibilityReason"],
|
|
692
983
|
});
|
|
693
984
|
/**
|
|
694
985
|
* One delivery attempt against one occurrence.
|
|
@@ -753,6 +1044,7 @@ export const attemptSchema = z
|
|
|
753
1044
|
.nonnegative()
|
|
754
1045
|
.max(ATTEMPT_DURATION_MAX_MS)
|
|
755
1046
|
.optional(),
|
|
1047
|
+
...timingEvidenceShape,
|
|
756
1048
|
id: attemptIdSchema,
|
|
757
1049
|
occurrence: occurrenceIdSchema,
|
|
758
1050
|
/** Stable next-action code paired with a diagnostic when one is known. */
|
|
@@ -802,11 +1094,24 @@ export const attemptSchema = z
|
|
|
802
1094
|
attempt.responseStatus === undefined), {
|
|
803
1095
|
message: "classification metadata must be absent while an attempt is in flight",
|
|
804
1096
|
path: ["state"],
|
|
1097
|
+
})
|
|
1098
|
+
.refine(hasCompleteTimingEvidence, {
|
|
1099
|
+
message: "firstAttemptStartedAt, firstAttemptLatenessMs and timingPolicyVersion must be present together",
|
|
1100
|
+
path: ["firstAttemptStartedAt"],
|
|
1101
|
+
})
|
|
1102
|
+
.refine(hasConsistentTimingEligibility, {
|
|
1103
|
+
message: "timingEligibilityReason is required for a blocked population and absent otherwise",
|
|
1104
|
+
path: ["timingEligibilityReason"],
|
|
805
1105
|
});
|
|
806
1106
|
/** An authoritative page over one schedule's retained occurrences. */
|
|
807
1107
|
export const occurrencePageSchema = pageSchema(occurrenceSchema).extend({
|
|
808
1108
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
809
1109
|
});
|
|
1110
|
+
/** A project-wide occurrence page and its plan's normal history boundary. */
|
|
1111
|
+
export const projectOccurrencePageSchema = pageSchema(occurrenceSchema).extend({
|
|
1112
|
+
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
1113
|
+
retention: z.object({ from: timestampSchema }),
|
|
1114
|
+
});
|
|
810
1115
|
/** An authoritative occurrence detail response. */
|
|
811
1116
|
export const occurrenceViewSchema = z.object({
|
|
812
1117
|
freshness: z.object({ kind: z.literal("authoritative") }),
|
|
@@ -877,8 +1182,8 @@ export const scheduleRequestSchema = z
|
|
|
877
1182
|
/**
|
|
878
1183
|
* Customer data placed in the signed callback envelope.
|
|
879
1184
|
*
|
|
880
|
-
* Any JSON value is accepted, including `null`, arrays and scalars.
|
|
881
|
-
*
|
|
1185
|
+
* Any JSON value is accepted, including `null`, arrays and scalars. A
|
|
1186
|
+
* schedule read returns it; listings leave it off. The accepting adapter
|
|
882
1187
|
* serializes it once, enforces `SCHEDULING_BOUNDS.payloadBytes` against
|
|
883
1188
|
* those UTF-8 bytes, then encrypts those same bytes before persistence.
|
|
884
1189
|
*/
|
|
@@ -904,16 +1209,15 @@ export const scheduleRequestSchema = z
|
|
|
904
1209
|
*
|
|
905
1210
|
* **Grouped rather than three sibling fields**, because two of the three are
|
|
906
1211
|
* meaningless without the first. `onOverlap` only answers a question a long
|
|
907
|
-
* run can ask
|
|
908
|
-
*
|
|
909
|
-
* visible in the shape instead of only in a refusal message.
|
|
1212
|
+
* run can ask; beside a five-second delivery it is stored and never
|
|
1213
|
+
* consulted. Nesting makes the dependency visible in the shape.
|
|
910
1214
|
*/
|
|
911
1215
|
delivery: z
|
|
912
1216
|
.object({
|
|
913
1217
|
/**
|
|
914
1218
|
* What to do when this schedule comes due and its previous occurrence
|
|
915
|
-
* has not finished.
|
|
916
|
-
*
|
|
1219
|
+
* has not finished. Consulted only on a recurring schedule whose runs
|
|
1220
|
+
* can outlast their interval; accepted and ignored elsewhere.
|
|
917
1221
|
*/
|
|
918
1222
|
onOverlap: deliveryOverlapPolicySchema.optional(),
|
|
919
1223
|
/**
|
|
@@ -922,8 +1226,11 @@ export const scheduleRequestSchema = z
|
|
|
922
1226
|
*/
|
|
923
1227
|
completion: deliveryCompletionModeSchema.optional(),
|
|
924
1228
|
/**
|
|
925
|
-
* How long the receiver may take to answer, in seconds
|
|
926
|
-
* `
|
|
1229
|
+
* How long the receiver may take to answer, in whole seconds, up to
|
|
1230
|
+
* `MAX_DELIVERY_DURATION_SECONDS`. Above
|
|
1231
|
+
* `RESPONSE_DELIVERY_MAX_DURATION_SECONDS` the receiver has to answer
|
|
1232
|
+
* immediately and call the completion URL instead, which is what
|
|
1233
|
+
* `completion: "callback"` says.
|
|
927
1234
|
*/
|
|
928
1235
|
maxDurationSeconds: deliveryDurationSecondsSchema.optional(),
|
|
929
1236
|
})
|
|
@@ -965,6 +1272,17 @@ export const scheduleRequestSchema = z
|
|
|
965
1272
|
* than obtained more of the service.
|
|
966
1273
|
*/
|
|
967
1274
|
latencyClass: latencyClassSchema.optional(),
|
|
1275
|
+
/**
|
|
1276
|
+
* What to do when an occurrence reaches its first attempt late.
|
|
1277
|
+
*
|
|
1278
|
+
* Omitted policy defaults to `catchUp` for `at` and `delaySeconds`, and to
|
|
1279
|
+
* `skipAhead` for `everySeconds` and `cron`. `maxStalenessSeconds` overrides
|
|
1280
|
+
* the schedule-derived window with a positive whole number of seconds.
|
|
1281
|
+
* Retries are exempt: once an occurrence has made an attempt, retry and
|
|
1282
|
+
* give-up policy decide its outcome instead of measuring again from the
|
|
1283
|
+
* original planned instant.
|
|
1284
|
+
*/
|
|
1285
|
+
lateness: scheduleLatenessRequestSchema.optional(),
|
|
968
1286
|
/**
|
|
969
1287
|
* A fairness lane within the caller's own project and environment.
|
|
970
1288
|
*
|
|
@@ -983,6 +1301,15 @@ export const scheduleRequestSchema = z
|
|
|
983
1301
|
* somebody asks otherwise.
|
|
984
1302
|
*/
|
|
985
1303
|
partitionKey: partitionKeySchema.optional(),
|
|
1304
|
+
/**
|
|
1305
|
+
* A name for people (W4-14, FXO-2255). On a create, omitted means the
|
|
1306
|
+
* service generates one from the type, period and target domain —
|
|
1307
|
+
* "Every 60s → flowxo.com" — and stores it. On a replace, omitted means
|
|
1308
|
+
* the stored name stays as it is: a generated name is recomputed on
|
|
1309
|
+
* nothing, so editing the recurrence does not silently rewrite a name
|
|
1310
|
+
* the person kept or changed. Names may collide.
|
|
1311
|
+
*/
|
|
1312
|
+
name: scheduleNameSchema.optional(),
|
|
986
1313
|
/**
|
|
987
1314
|
* The IANA timezone `cron` is read against. `UTC` when omitted.
|
|
988
1315
|
*
|
|
@@ -1024,30 +1351,15 @@ export const scheduleRequestSchema = z
|
|
|
1024
1351
|
path: ["timeZone"],
|
|
1025
1352
|
});
|
|
1026
1353
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
// Only checked when the duration is in *this* request. A replace may carry
|
|
1039
|
-
// `onOverlap` while the stored schedule holds the duration, exactly as it
|
|
1040
|
-
// may carry `data` while the stored schedule holds the endpoint, and the
|
|
1041
|
-
// partition is where a request is resolved against what is stored.
|
|
1042
|
-
const duration = request.delivery.maxDurationSeconds;
|
|
1043
|
-
if (duration !== undefined && !isLongRunningDuration(duration)) {
|
|
1044
|
-
context.addIssue({
|
|
1045
|
-
code: "custom",
|
|
1046
|
-
message: `\`onOverlap\` is only meaningful above ${String(FREE_DELIVERY_DURATION_SECONDS)} seconds, where a run can still be going when the next one is due`,
|
|
1047
|
-
path: ["delivery", "onOverlap"],
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1354
|
+
// **`onOverlap` is accepted wherever it is given, and ignored where it
|
|
1355
|
+
// cannot apply** — a one-shot, or a run too short to outlast its own
|
|
1356
|
+
// interval. It used to be refused in both cases, and the refusal became a
|
|
1357
|
+
// trap: every stored schedule carries the default policy, the dashboard's
|
|
1358
|
+
// editor sent it back on every save, and a five-second delivery was
|
|
1359
|
+
// refused for a field nobody had touched, under a collapsed section. The
|
|
1360
|
+
// operator's ruling on the W4-10 walk: "so what if it's not meaningful —
|
|
1361
|
+
// then we ignore it." A policy that cannot fire is not a guarantee anyone
|
|
1362
|
+
// can rely on, and the schedule's read reports what is stored.
|
|
1051
1363
|
// Held open past the point a held-open request survives. Checked against the
|
|
1052
1364
|
// *derived* pair rather than against what the body names, because
|
|
1053
1365
|
// `completion` defaults to `response`: a caller who asks for twelve hours and
|
|
@@ -1067,6 +1379,20 @@ export const scheduleRequestSchema = z
|
|
|
1067
1379
|
});
|
|
1068
1380
|
}
|
|
1069
1381
|
});
|
|
1382
|
+
/** Resolve the lateness settings a create or replacement request runs under. */
|
|
1383
|
+
export function deriveLatenessSettings(request) {
|
|
1384
|
+
const recurrenceKind = request.cron !== undefined
|
|
1385
|
+
? "cron"
|
|
1386
|
+
: request.everySeconds !== undefined
|
|
1387
|
+
? "every"
|
|
1388
|
+
: "at";
|
|
1389
|
+
const policy = request.lateness?.policy ??
|
|
1390
|
+
defaultLatenessPolicyForRecurrence(recurrenceKind);
|
|
1391
|
+
const maxStalenessSeconds = request.lateness?.maxStalenessSeconds;
|
|
1392
|
+
return maxStalenessSeconds === undefined
|
|
1393
|
+
? { policy }
|
|
1394
|
+
: { maxStalenessSeconds, policy };
|
|
1395
|
+
}
|
|
1070
1396
|
/**
|
|
1071
1397
|
* The delivery settings a request runs under: the caller's, or the defaults.
|
|
1072
1398
|
*
|