@greatwait/contracts 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/authority.d.ts +217 -0
- package/dist/authority.js +261 -0
- package/dist/callback-ownership.d.ts +58 -0
- package/dist/callback-ownership.js +71 -0
- package/dist/callback-safety.d.ts +38 -0
- package/dist/callback-safety.js +403 -0
- package/dist/compatibility.d.ts +82 -0
- package/dist/compatibility.js +115 -0
- package/dist/delivery.d.ts +401 -0
- package/dist/delivery.js +311 -0
- package/dist/endpoints.d.ts +305 -0
- package/dist/endpoints.js +179 -0
- package/dist/events.d.ts +62 -0
- package/dist/events.js +38 -0
- package/dist/first-value-journey.d.ts +305 -0
- package/dist/first-value-journey.js +466 -0
- package/dist/forecast.d.ts +443 -0
- package/dist/forecast.js +729 -0
- package/dist/identifiers.d.ts +109 -0
- package/dist/identifiers.js +131 -0
- package/dist/identity.d.ts +196 -0
- package/dist/identity.js +182 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/mcp.d.ts +51 -0
- package/dist/mcp.js +56 -0
- package/dist/oauth.d.ts +796 -0
- package/dist/oauth.js +708 -0
- package/dist/operations.d.ts +3985 -0
- package/dist/operations.js +1837 -0
- package/dist/partition.d.ts +771 -0
- package/dist/partition.js +206 -0
- package/dist/policy.d.ts +93 -0
- package/dist/policy.js +180 -0
- package/dist/problems.d.ts +158 -0
- package/dist/problems.js +104 -0
- package/dist/protocol.d.ts +308 -0
- package/dist/protocol.js +515 -0
- package/dist/scheduling-records.d.ts +395 -0
- package/dist/scheduling-records.js +284 -0
- package/dist/scheduling.d.ts +1098 -0
- package/dist/scheduling.js +1116 -0
- package/dist/secrets.d.ts +383 -0
- package/dist/secrets.js +272 -0
- package/dist/signatures.d.ts +704 -0
- package/dist/signatures.js +867 -0
- package/dist/tenancy.d.ts +178 -0
- package/dist/tenancy.js +145 -0
- package/dist/test-sink.d.ts +986 -0
- package/dist/test-sink.js +755 -0
- package/dist/time.d.ts +45 -0
- package/dist/time.js +128 -0
- package/dist/usage.d.ts +121 -0
- package/dist/usage.js +200 -0
- package/dist/webhooks.d.ts +116 -0
- package/dist/webhooks.js +161 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.js +59 -0
- package/generated/v1/callback-delivery-signature.json +467 -0
- package/generated/v1/callback-endpoint-verification.json +59 -0
- package/generated/v1/callback-receiver.json +241 -0
- package/generated/v1/examples/attempts.list.response.json +24 -0
- package/generated/v1/examples/attempts.read.response.json +20 -0
- package/generated/v1/examples/credentials.create.request.json +5 -0
- package/generated/v1/examples/credentials.create.response.json +10 -0
- package/generated/v1/examples/credentials.list.response.json +17 -0
- package/generated/v1/examples/credentials.revoke.response.json +11 -0
- package/generated/v1/examples/credentials.rotate.request.json +3 -0
- package/generated/v1/examples/credentials.rotate.response.json +22 -0
- package/generated/v1/examples/endpoints.create.request.json +3 -0
- package/generated/v1/examples/endpoints.create.response.json +16 -0
- package/generated/v1/examples/endpoints.disable.response.json +15 -0
- package/generated/v1/examples/endpoints.list.response.json +21 -0
- package/generated/v1/examples/endpoints.read.response.json +14 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.request.json +3 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.response.json +23 -0
- package/generated/v1/examples/endpoints.update.request.json +3 -0
- package/generated/v1/examples/endpoints.update.response.json +13 -0
- package/generated/v1/examples/endpoints.verify.response.json +15 -0
- package/generated/v1/examples/forecast.read.response.json +28 -0
- package/generated/v1/examples/forecast.windows.response.json +21 -0
- package/generated/v1/examples/grants.list.response.json +17 -0
- package/generated/v1/examples/grants.revoke.response.json +16 -0
- package/generated/v1/examples/meta.scaffold.response.json +5 -0
- package/generated/v1/examples/meta.version.response.json +5 -0
- package/generated/v1/examples/occurrences.list.response.json +17 -0
- package/generated/v1/examples/occurrences.read.response.json +15 -0
- package/generated/v1/examples/occurrences.redeliver.response.json +16 -0
- package/generated/v1/examples/projects.create.request.json +3 -0
- package/generated/v1/examples/projects.create.response.json +5 -0
- package/generated/v1/examples/projects.list.response.json +9 -0
- package/generated/v1/examples/schedules.archive.response.json +25 -0
- package/generated/v1/examples/schedules.cancel.response.json +24 -0
- package/generated/v1/examples/schedules.create.request.json +9 -0
- package/generated/v1/examples/schedules.create.response.json +25 -0
- package/generated/v1/examples/schedules.list.response.json +23 -0
- package/generated/v1/examples/schedules.pause.response.json +23 -0
- package/generated/v1/examples/schedules.read.response.json +25 -0
- package/generated/v1/examples/schedules.replace.request.json +8 -0
- package/generated/v1/examples/schedules.replace.response.json +25 -0
- package/generated/v1/examples/schedules.resume.response.json +23 -0
- package/generated/v1/examples/sinkdeliveries.list.response.json +19 -0
- package/generated/v1/examples/sinkdeliveries.read.response.json +16 -0
- package/generated/v1/examples/testsinks.configure.request.json +3 -0
- package/generated/v1/examples/testsinks.configure.response.json +13 -0
- package/generated/v1/examples/testsinks.create.request.json +8 -0
- package/generated/v1/examples/testsinks.create.response.json +13 -0
- package/generated/v1/examples/testsinks.delete.response.json +13 -0
- package/generated/v1/examples/testsinks.list.response.json +20 -0
- package/generated/v1/examples/testsinks.read.response.json +14 -0
- package/generated/v1/examples/usage.read.response.json +20 -0
- package/generated/v1/first-value-journey.json +436 -0
- package/generated/v1/manifest.json +1037 -0
- package/generated/v1/openapi.json +23665 -0
- package/generated/v1/policy-matrix.json +305 -0
- package/generated/v1/schemas/attempts.list.query.json +16 -0
- package/generated/v1/schemas/attempts.list.response.json +175 -0
- package/generated/v1/schemas/attempts.read.response.json +168 -0
- package/generated/v1/schemas/credentials.create.request.json +39 -0
- package/generated/v1/schemas/credentials.create.response.json +88 -0
- package/generated/v1/schemas/credentials.list.query.json +16 -0
- package/generated/v1/schemas/credentials.list.response.json +124 -0
- package/generated/v1/schemas/credentials.revoke.response.json +84 -0
- package/generated/v1/schemas/credentials.rotate.request.json +11 -0
- package/generated/v1/schemas/credentials.rotate.response.json +171 -0
- package/generated/v1/schemas/endpoints.create.request.json +12 -0
- package/generated/v1/schemas/endpoints.create.response.json +115 -0
- package/generated/v1/schemas/endpoints.disable.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.disable.response.json +100 -0
- package/generated/v1/schemas/endpoints.list.query.json +16 -0
- package/generated/v1/schemas/endpoints.list.response.json +146 -0
- package/generated/v1/schemas/endpoints.read.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.read.response.json +100 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.request.json +11 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +118 -0
- package/generated/v1/schemas/endpoints.update.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.update.request.json +12 -0
- package/generated/v1/schemas/endpoints.update.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.update.response.json +100 -0
- package/generated/v1/schemas/endpoints.verify.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.verify.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.verify.response.json +100 -0
- package/generated/v1/schemas/forecast.read.query.json +26 -0
- package/generated/v1/schemas/forecast.read.response.json +100 -0
- package/generated/v1/schemas/forecast.windows.query.json +26 -0
- package/generated/v1/schemas/forecast.windows.response.json +94 -0
- package/generated/v1/schemas/grants.list.response.json +98 -0
- package/generated/v1/schemas/grants.revoke.response.json +95 -0
- package/generated/v1/schemas/meta.scaffold.response.json +36 -0
- package/generated/v1/schemas/meta.version.response.json +19 -0
- package/generated/v1/schemas/occurrences.list.query.json +16 -0
- package/generated/v1/schemas/occurrences.list.response.json +83 -0
- package/generated/v1/schemas/occurrences.read.response.json +76 -0
- package/generated/v1/schemas/occurrences.redeliver.request-headers.json +13 -0
- package/generated/v1/schemas/occurrences.redeliver.response-headers.json +10 -0
- package/generated/v1/schemas/occurrences.redeliver.response.json +168 -0
- package/generated/v1/schemas/projects.create.request.json +12 -0
- package/generated/v1/schemas/projects.create.response.json +25 -0
- package/generated/v1/schemas/projects.list.response.json +35 -0
- package/generated/v1/schemas/schedules.archive.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.archive.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.archive.response.json +189 -0
- package/generated/v1/schemas/schedules.cancel.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.cancel.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.cancel.response.json +189 -0
- package/generated/v1/schemas/schedules.create.request-headers.json +12 -0
- package/generated/v1/schemas/schedules.create.request.json +99 -0
- package/generated/v1/schemas/schedules.create.response-headers.json +15 -0
- package/generated/v1/schemas/schedules.create.response.json +189 -0
- package/generated/v1/schemas/schedules.list.query.json +16 -0
- package/generated/v1/schemas/schedules.list.response.json +170 -0
- package/generated/v1/schemas/schedules.pause.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.pause.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.pause.response.json +189 -0
- package/generated/v1/schemas/schedules.read.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.read.response.json +189 -0
- package/generated/v1/schemas/schedules.replace.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.replace.request.json +99 -0
- package/generated/v1/schemas/schedules.replace.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.replace.response.json +189 -0
- package/generated/v1/schemas/schedules.resume.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.resume.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.resume.response.json +189 -0
- package/generated/v1/schemas/sinkdeliveries.list.query.json +16 -0
- package/generated/v1/schemas/sinkdeliveries.list.response.json +126 -0
- package/generated/v1/schemas/sinkdeliveries.read.response.json +98 -0
- package/generated/v1/schemas/testsinks.configure.request.json +66 -0
- package/generated/v1/schemas/testsinks.configure.response.json +115 -0
- package/generated/v1/schemas/testsinks.create.request.json +76 -0
- package/generated/v1/schemas/testsinks.create.response.json +115 -0
- package/generated/v1/schemas/testsinks.delete.response.json +115 -0
- package/generated/v1/schemas/testsinks.list.query.json +16 -0
- package/generated/v1/schemas/testsinks.list.response.json +161 -0
- package/generated/v1/schemas/testsinks.read.response.json +115 -0
- package/generated/v1/schemas/usage.read.query.json +15 -0
- package/generated/v1/schemas/usage.read.response.json +71 -0
- package/generated/v1/snippets/attempts.list.json +5 -0
- package/generated/v1/snippets/attempts.read.json +5 -0
- package/generated/v1/snippets/credentials.create.json +5 -0
- package/generated/v1/snippets/credentials.list.json +5 -0
- package/generated/v1/snippets/credentials.revoke.json +5 -0
- package/generated/v1/snippets/credentials.rotate.json +5 -0
- package/generated/v1/snippets/endpoints.create.json +5 -0
- package/generated/v1/snippets/endpoints.disable.json +5 -0
- package/generated/v1/snippets/endpoints.list.json +5 -0
- package/generated/v1/snippets/endpoints.read.json +5 -0
- package/generated/v1/snippets/endpoints.rotateSigningKey.json +5 -0
- package/generated/v1/snippets/endpoints.update.json +5 -0
- package/generated/v1/snippets/endpoints.verify.json +5 -0
- package/generated/v1/snippets/forecast.read.json +5 -0
- package/generated/v1/snippets/forecast.windows.json +5 -0
- package/generated/v1/snippets/grants.list.json +5 -0
- package/generated/v1/snippets/grants.revoke.json +5 -0
- package/generated/v1/snippets/meta.scaffold.json +5 -0
- package/generated/v1/snippets/meta.version.json +5 -0
- package/generated/v1/snippets/occurrences.list.json +5 -0
- package/generated/v1/snippets/occurrences.read.json +5 -0
- package/generated/v1/snippets/occurrences.redeliver.json +5 -0
- package/generated/v1/snippets/projects.create.json +5 -0
- package/generated/v1/snippets/projects.list.json +5 -0
- package/generated/v1/snippets/schedules.archive.json +5 -0
- package/generated/v1/snippets/schedules.cancel.json +5 -0
- package/generated/v1/snippets/schedules.create.json +5 -0
- package/generated/v1/snippets/schedules.list.json +5 -0
- package/generated/v1/snippets/schedules.pause.json +5 -0
- package/generated/v1/snippets/schedules.read.json +5 -0
- package/generated/v1/snippets/schedules.replace.json +5 -0
- package/generated/v1/snippets/schedules.resume.json +5 -0
- package/generated/v1/snippets/sinkdeliveries.list.json +5 -0
- package/generated/v1/snippets/sinkdeliveries.read.json +5 -0
- package/generated/v1/snippets/testsinks.configure.json +5 -0
- package/generated/v1/snippets/testsinks.create.json +5 -0
- package/generated/v1/snippets/testsinks.delete.json +5 -0
- package/generated/v1/snippets/testsinks.list.json +5 -0
- package/generated/v1/snippets/testsinks.read.json +5 -0
- package/generated/v1/snippets/usage.read.json +5 -0
- package/package.json +153 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type OccurrenceId, type ScheduleId } from "./identifiers.js";
|
|
3
|
+
/**
|
|
4
|
+
* Callback authenticity.
|
|
5
|
+
*
|
|
6
|
+
* The signature covers the timestamp, the delivery id, and the exact raw
|
|
7
|
+
* request-body bytes. A receiver verifies before parsing, in constant time.
|
|
8
|
+
*
|
|
9
|
+
* This is the most consequential break from the legacy scheduler, which signed
|
|
10
|
+
* `scheduleId.owner.tenant.timestampMs` and never touched the body: a signature
|
|
11
|
+
* that does not cover the body authenticates only the envelope, so an attacker
|
|
12
|
+
* who can replay a captured header set may substitute any payload. W6-03
|
|
13
|
+
* migrates the autoscaler onto this contract through a separate v2 endpoint so
|
|
14
|
+
* the old and new verifiers never weaken each other.
|
|
15
|
+
*
|
|
16
|
+
* The `Callback-*` header namespace is intentionally independent of the display
|
|
17
|
+
* brand. Renaming the product must not force receivers to change verification
|
|
18
|
+
* code.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CALLBACK_HEADERS: {
|
|
21
|
+
readonly deliveryId: "Callback-Delivery-Id";
|
|
22
|
+
readonly keyId: "Callback-Key-Id";
|
|
23
|
+
readonly occurrenceId: "Callback-Occurrence-Id";
|
|
24
|
+
readonly signature: "Callback-Signature";
|
|
25
|
+
readonly timestamp: "Callback-Timestamp";
|
|
26
|
+
};
|
|
27
|
+
/** Only `v1` exists. A new scheme is added alongside, never in place of. */
|
|
28
|
+
export declare const SIGNATURE_SCHEME: "v1";
|
|
29
|
+
/** How far a receiver's clock may differ before a signature is stale. */
|
|
30
|
+
export declare const SIGNATURE_TOLERANCE_SECONDS = 300;
|
|
31
|
+
/** Version of the complete delivery-authentication protocol artifact. */
|
|
32
|
+
export declare const CALLBACK_DELIVERY_PROTOCOL_VERSION = "2026-08-23";
|
|
33
|
+
/** The media type sent on every occurrence delivery. */
|
|
34
|
+
export declare const CALLBACK_CONTENT_TYPE = "application/json";
|
|
35
|
+
export declare const signatureHeaderSchema: z.ZodString;
|
|
36
|
+
/**
|
|
37
|
+
* The exact bytes that are signed: `<timestamp>.<delivery-id>.` followed by the
|
|
38
|
+
* raw body, with no re-serialization anywhere in the path. Re-encoding JSON
|
|
39
|
+
* before signing or verifying is the classic way to break this.
|
|
40
|
+
*
|
|
41
|
+
* Both prefix fields are validated here rather than trusted, and that is a
|
|
42
|
+
* security property, not tidiness. The delimiter `.` also occurs inside every
|
|
43
|
+
* contract timestamp, so without validation the encoding is not injective:
|
|
44
|
+
* `("2026-07-28T15:00:00", "000Z.dlv_…", body)` produces the same bytes as
|
|
45
|
+
* `("2026-07-28T15:00:00.000Z", "dlv_…", body)`, and an attacker holding one
|
|
46
|
+
* legitimate signature could move bytes across the delimiter — shortening the
|
|
47
|
+
* body and lengthening the delivery-id header — and still verify.
|
|
48
|
+
*
|
|
49
|
+
* Validating makes both fields fixed-width, so the prefix is a constant 56
|
|
50
|
+
* bytes and the encoding is injective. A verifier that computes the MAC before
|
|
51
|
+
* validating its headers is the natural implementation and would be
|
|
52
|
+
* vulnerable; making that impossible here means no receiver has to know.
|
|
53
|
+
*
|
|
54
|
+
* **Throws** on a malformed timestamp or delivery id. That is right on the
|
|
55
|
+
* signing side — refusing to sign something we cannot encode unambiguously —
|
|
56
|
+
* but a receiver verifying attacker-controlled headers wants a boolean, not an
|
|
57
|
+
* exception: use `trySigningInput`, or an uncaught throw turns a
|
|
58
|
+
* malformed-header probe into a 500.
|
|
59
|
+
*/
|
|
60
|
+
export declare function signingInput(timestamp: string, deliveryId: string, rawBody: Uint8Array): Uint8Array;
|
|
61
|
+
/**
|
|
62
|
+
* `signingInput` for untrusted input: returns `undefined` instead of throwing
|
|
63
|
+
* when the prefix fields are malformed, which is what a receiver verifying
|
|
64
|
+
* headers off the wire needs.
|
|
65
|
+
*/
|
|
66
|
+
export declare function trySigningInput(timestamp: string, deliveryId: string, rawBody: Uint8Array): Uint8Array | undefined;
|
|
67
|
+
export declare function formatSignature(hexMac: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Whether a signed timestamp is close enough to now. Replay protection beyond
|
|
70
|
+
* this window is the receiver's, keyed on the delivery id.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isTimestampFresh(timestamp: string, nowMs: number, toleranceSeconds?: number): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Why this delivery exists.
|
|
75
|
+
*
|
|
76
|
+
* `automatic` is everything the system decided for itself: a first attempt, and
|
|
77
|
+
* every retry after an ambiguous or failed one. Nobody asked for it, it may
|
|
78
|
+
* duplicate work already done, and a receiver must therefore treat it as at
|
|
79
|
+
* most one execution of the occurrence.
|
|
80
|
+
*
|
|
81
|
+
* `redelivery` is a person pressing the button, scoped by
|
|
82
|
+
* `occurrences:redeliver` and named against the retained attempt they chose to
|
|
83
|
+
* follow. It is a *request for the work to happen again*, and a receiver that
|
|
84
|
+
* suppresses it as a duplicate is answering a deliberate instruction with
|
|
85
|
+
* silence.
|
|
86
|
+
*
|
|
87
|
+
* The two were indistinguishable on the wire until this field existed, so the
|
|
88
|
+
* only rule safe against the first was applied to both. That is the defect
|
|
89
|
+
* W3-08 exists to remove, and the reason the field is inside the signed bytes
|
|
90
|
+
* rather than in a header: a receiver that would re-run work on the strength of
|
|
91
|
+
* an unauthenticated hint has handed anyone who can reach its endpoint a
|
|
92
|
+
* duplicate-execution button.
|
|
93
|
+
*/
|
|
94
|
+
export declare const callbackTriggerSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
95
|
+
kind: z.ZodLiteral<"automatic">;
|
|
96
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
97
|
+
kind: z.ZodLiteral<"redelivery">;
|
|
98
|
+
sourceAttemptId: z.ZodString;
|
|
99
|
+
}, z.core.$strip>], "kind">;
|
|
100
|
+
export type CallbackTrigger = z.infer<typeof callbackTriggerSchema>;
|
|
101
|
+
/**
|
|
102
|
+
* The envelope version this service emits.
|
|
103
|
+
*
|
|
104
|
+
* Dated and independent of the contract version. It moved on 2026-08-08 because
|
|
105
|
+
* the envelope gained `trigger`, and again on 2026-08-23 because `completion`
|
|
106
|
+
* stopped being optional: every delivery now carries a completion URL, a
|
|
107
|
+
* per-dispatch credential and an absolute deadline, whatever the schedule asked
|
|
108
|
+
* for. `assess` classifies both moves as `breaking` — see
|
|
109
|
+
* `CALLBACK_ENVELOPE_VERSION_CHANGE`.
|
|
110
|
+
*
|
|
111
|
+
* **A required field is why this is a version rather than an addition.** Under
|
|
112
|
+
* `2026-08-08` an absent `completion` means "this schedule did not ask for
|
|
113
|
+
* one"; under `2026-08-23` it means the envelope is malformed. A receiver that
|
|
114
|
+
* could not tell those apart would have to treat "no completion material" as
|
|
115
|
+
* both a normal delivery and a broken one.
|
|
116
|
+
*/
|
|
117
|
+
export declare const CALLBACK_ENVELOPE_VERSION: "2026-08-23";
|
|
118
|
+
/** The first published envelope version, which this service no longer emits. */
|
|
119
|
+
export declare const CALLBACK_ENVELOPE_VERSION_2026_07_01: "2026-07-01";
|
|
120
|
+
/**
|
|
121
|
+
* The second published version, which this service no longer emits.
|
|
122
|
+
*
|
|
123
|
+
* Still accepted inbound, and its known-answer vectors are still published:
|
|
124
|
+
* a receiver deployed before the sender moved keeps verifying deliveries that
|
|
125
|
+
* were signed under it.
|
|
126
|
+
*/
|
|
127
|
+
export declare const CALLBACK_ENVELOPE_VERSION_2026_08_08: "2026-08-08";
|
|
128
|
+
/**
|
|
129
|
+
* Every envelope version a receiver may be handed, oldest first.
|
|
130
|
+
*
|
|
131
|
+
* All are accepted on the way in and only the newest is sent, which is what
|
|
132
|
+
* makes a receiver deployable ahead of the sender. A receiver upgraded first
|
|
133
|
+
* keeps verifying the deliveries already in flight; one upgraded afterwards
|
|
134
|
+
* answers `invalid_envelope` to everything, because a version literal it has
|
|
135
|
+
* never heard of is exactly the case the field exists to catch.
|
|
136
|
+
*/
|
|
137
|
+
export declare const CALLBACK_ENVELOPE_VERSIONS: readonly ["2026-07-01", "2026-08-08", "2026-08-23"];
|
|
138
|
+
/**
|
|
139
|
+
* Where a `callback` receiver reports that it finished, and with what.
|
|
140
|
+
*
|
|
141
|
+
* **Present only on a delivery whose schedule asked for `completion:
|
|
142
|
+
* "callback"`**, which is what makes adding it to an existing envelope version
|
|
143
|
+
* safe rather than a break. A receiver sees this field for the first time on a
|
|
144
|
+
* delivery its own customer deliberately reconfigured; no receiver that has not
|
|
145
|
+
* opted in can be handed one. That is a different situation from `trigger`,
|
|
146
|
+
* which appeared on *every* delivery and moved the version to `2026-08-08` in
|
|
147
|
+
* consequence.
|
|
148
|
+
*
|
|
149
|
+
* The token authenticates the report and nothing else. It is minted per dispatch
|
|
150
|
+
* and names one attempt at one dispatch epoch, so a report replayed against a
|
|
151
|
+
* later attempt is refused by the same fence that refuses a late executor
|
|
152
|
+
* result.
|
|
153
|
+
*/
|
|
154
|
+
export declare const callbackCompletionSchema: z.ZodObject<{
|
|
155
|
+
token: z.ZodString;
|
|
156
|
+
url: z.ZodURL;
|
|
157
|
+
}, z.core.$strip>;
|
|
158
|
+
export type CallbackCompletion = z.infer<typeof callbackCompletionSchema>;
|
|
159
|
+
/**
|
|
160
|
+
* The completion material every `2026-08-23` delivery carries.
|
|
161
|
+
*
|
|
162
|
+
* The same URL and credential, plus the one thing a receiver could not
|
|
163
|
+
* previously know: **when Greatwait stops waiting.** Without it a receiver
|
|
164
|
+
* deciding whether to accept the work has to infer the deadline from the
|
|
165
|
+
* schedule's configured duration, which it cannot see, and a receiver that
|
|
166
|
+
* reports one second late looks identical to one that never reported at all.
|
|
167
|
+
*
|
|
168
|
+
* `deadline` is the schedule's own allowance measured from this attempt, in
|
|
169
|
+
* the wire's one timestamp representation. It is the maximum: a receiver may
|
|
170
|
+
* ask for an earlier instant in its acknowledgement and may not ask for a
|
|
171
|
+
* later one.
|
|
172
|
+
*/
|
|
173
|
+
export declare const callbackDeferredCompletionSchema: z.ZodObject<{
|
|
174
|
+
deadline: z.ZodString;
|
|
175
|
+
token: z.ZodString;
|
|
176
|
+
url: z.ZodURL;
|
|
177
|
+
}, z.core.$strip>;
|
|
178
|
+
export type CallbackDeferredCompletion = z.infer<typeof callbackDeferredCompletionSchema>;
|
|
179
|
+
/**
|
|
180
|
+
* The report body, whose vocabulary lives in the Zod-free `delivery` module so
|
|
181
|
+
* the scheduler's completion route can read it without importing Zod.
|
|
182
|
+
*/
|
|
183
|
+
export declare const callbackCompletionReportSchema: z.ZodObject<{
|
|
184
|
+
outcome: z.ZodDefault<z.ZodEnum<{
|
|
185
|
+
succeeded: "succeeded";
|
|
186
|
+
failed: "failed";
|
|
187
|
+
}>>;
|
|
188
|
+
}, z.core.$strip>;
|
|
189
|
+
export type CallbackCompletionReport = z.infer<typeof callbackCompletionReportSchema>;
|
|
190
|
+
/**
|
|
191
|
+
* The versioned JSON envelope delivered to a customer endpoint.
|
|
192
|
+
*
|
|
193
|
+
* A union rather than one object with an optional field, and the difference is
|
|
194
|
+
* the whole point of a version: under `2026-07-01` a missing `trigger` means
|
|
195
|
+
* "this sender cannot tell you", and under `2026-08-08` it is a malformed
|
|
196
|
+
* envelope. Collapsing the two would leave a receiver unable to know whether
|
|
197
|
+
* silence about a redelivery is an answer or an absence.
|
|
198
|
+
*/
|
|
199
|
+
export declare const callbackEnvelopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
200
|
+
version: z.ZodLiteral<"2026-07-01">;
|
|
201
|
+
attempt: z.ZodNumber;
|
|
202
|
+
data: z.ZodUnknown;
|
|
203
|
+
occurrenceId: z.ZodString;
|
|
204
|
+
plannedFor: z.ZodString;
|
|
205
|
+
scheduleId: z.ZodString;
|
|
206
|
+
type: z.ZodLiteral<"schedule.occurrence">;
|
|
207
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
208
|
+
completion: z.ZodOptional<z.ZodObject<{
|
|
209
|
+
token: z.ZodString;
|
|
210
|
+
url: z.ZodURL;
|
|
211
|
+
}, z.core.$strip>>;
|
|
212
|
+
trigger: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
213
|
+
kind: z.ZodLiteral<"automatic">;
|
|
214
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
215
|
+
kind: z.ZodLiteral<"redelivery">;
|
|
216
|
+
sourceAttemptId: z.ZodString;
|
|
217
|
+
}, z.core.$strip>], "kind">;
|
|
218
|
+
version: z.ZodLiteral<"2026-08-08">;
|
|
219
|
+
attempt: z.ZodNumber;
|
|
220
|
+
data: z.ZodUnknown;
|
|
221
|
+
occurrenceId: z.ZodString;
|
|
222
|
+
plannedFor: z.ZodString;
|
|
223
|
+
scheduleId: z.ZodString;
|
|
224
|
+
type: z.ZodLiteral<"schedule.occurrence">;
|
|
225
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
226
|
+
completion: z.ZodObject<{
|
|
227
|
+
deadline: z.ZodString;
|
|
228
|
+
token: z.ZodString;
|
|
229
|
+
url: z.ZodURL;
|
|
230
|
+
}, z.core.$strip>;
|
|
231
|
+
trigger: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
232
|
+
kind: z.ZodLiteral<"automatic">;
|
|
233
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
234
|
+
kind: z.ZodLiteral<"redelivery">;
|
|
235
|
+
sourceAttemptId: z.ZodString;
|
|
236
|
+
}, z.core.$strip>], "kind">;
|
|
237
|
+
version: z.ZodLiteral<"2026-08-23">;
|
|
238
|
+
attempt: z.ZodNumber;
|
|
239
|
+
data: z.ZodUnknown;
|
|
240
|
+
occurrenceId: z.ZodString;
|
|
241
|
+
plannedFor: z.ZodString;
|
|
242
|
+
scheduleId: z.ZodString;
|
|
243
|
+
type: z.ZodLiteral<"schedule.occurrence">;
|
|
244
|
+
}, z.core.$strip>], "version">;
|
|
245
|
+
export type CallbackEnvelope = z.infer<typeof callbackEnvelopeSchema>;
|
|
246
|
+
/**
|
|
247
|
+
* What caused a delivery, for an envelope of any published version.
|
|
248
|
+
*
|
|
249
|
+
* A `2026-07-01` envelope carries no trigger and reads as `automatic`, which is
|
|
250
|
+
* the conservative answer rather than a neutral one: it keeps duplicate
|
|
251
|
+
* suppression in force for a sender that cannot say a redelivery was ordered.
|
|
252
|
+
* Every receiver-side decision goes through this function so that default lives
|
|
253
|
+
* in one place instead of being re-decided at each call site.
|
|
254
|
+
*/
|
|
255
|
+
export declare function callbackEnvelopeTrigger(envelope: CallbackEnvelope): CallbackTrigger;
|
|
256
|
+
/**
|
|
257
|
+
* The unit of work a receiver performs at most once.
|
|
258
|
+
*
|
|
259
|
+
* Not the occurrence: the occurrence *and what asked for it*. Every automatic
|
|
260
|
+
* delivery of one occurrence is one unit, so a retry can never duplicate work.
|
|
261
|
+
* Each ordered redelivery is its own unit, numbered by `attempt` — the
|
|
262
|
+
* occurrence's own counter, which only ever goes up and which every copy of one
|
|
263
|
+
* redelivery shares.
|
|
264
|
+
*
|
|
265
|
+
* **The number is what makes a repeat recognisable with constant memory, and
|
|
266
|
+
* that is the whole reason it is the identity.** A receiver keeps the highest
|
|
267
|
+
* redelivery it has finished and refuses anything at or below it, so a copy
|
|
268
|
+
* delayed in a proxy, a queue or an abandoned connection is still recognised
|
|
269
|
+
* however long it takes to arrive and however many redeliveries have completed
|
|
270
|
+
* since. External review on 2026-08-08 showed why the alternative fails: keyed
|
|
271
|
+
* on *which attempt the person followed*, a receiver remembering only the last
|
|
272
|
+
* one finished cannot tell a stale copy of an older instruction from a new one,
|
|
273
|
+
* and runs a customer's work twice. Remembering every instruction instead would
|
|
274
|
+
* hold the promise, at the price of a table that grows forever in every
|
|
275
|
+
* customer's receiver, in every language.
|
|
276
|
+
*
|
|
277
|
+
* `sourceAttemptId` stays on the wire because it says which attempt a person
|
|
278
|
+
* chose to follow, which is worth showing them. It is no longer what decides
|
|
279
|
+
* whether the work runs.
|
|
280
|
+
*
|
|
281
|
+
* Published here, and used by both the shipped middleware and the test sink,
|
|
282
|
+
* because a receiver and the reference receiver computing this differently is
|
|
283
|
+
* indistinguishable from a receiver with an idempotency bug.
|
|
284
|
+
*/
|
|
285
|
+
export type CallbackRun = {
|
|
286
|
+
readonly kind: "automatic";
|
|
287
|
+
} | {
|
|
288
|
+
readonly kind: "redelivery";
|
|
289
|
+
readonly ordinal: number;
|
|
290
|
+
};
|
|
291
|
+
export declare function callbackRun(envelope: CallbackEnvelope): CallbackRun;
|
|
292
|
+
/**
|
|
293
|
+
* Serialize one callback envelope exactly once in the frozen wire order.
|
|
294
|
+
*
|
|
295
|
+
* The returned bytes are both the bytes HMAC signs and the bytes `fetch`
|
|
296
|
+
* sends. `data` is checked as JSON before stringification so values such as
|
|
297
|
+
* `undefined`, `NaN`, `bigint`, a `Date`, or a cycle cannot be silently omitted
|
|
298
|
+
* or rewritten into a body different from the caller's input.
|
|
299
|
+
*/
|
|
300
|
+
export declare function serializeCallbackEnvelope(envelope: CallbackEnvelope): Uint8Array;
|
|
301
|
+
/**
|
|
302
|
+
* Read the two leading envelope identifiers without parsing attacker-controlled
|
|
303
|
+
* JSON. This lets receiver middleware reject a mutated occurrence header after
|
|
304
|
+
* authenticating the exact bytes but before `JSON.parse` or an application
|
|
305
|
+
* handler sees them.
|
|
306
|
+
*/
|
|
307
|
+
export declare function extractCanonicalEnvelopeIds(rawBody: Uint8Array): {
|
|
308
|
+
readonly occurrenceId: OccurrenceId;
|
|
309
|
+
readonly scheduleId: ScheduleId;
|
|
310
|
+
} | undefined;
|
|
311
|
+
export declare const callbackHeadersSchema: z.ZodObject<{
|
|
312
|
+
"Callback-Delivery-Id": z.ZodString;
|
|
313
|
+
"Callback-Key-Id": z.ZodString;
|
|
314
|
+
"Callback-Occurrence-Id": z.ZodString;
|
|
315
|
+
"Callback-Signature": z.ZodString;
|
|
316
|
+
"Callback-Timestamp": z.ZodString;
|
|
317
|
+
}, z.core.$strip>;
|
|
318
|
+
/**
|
|
319
|
+
* Language-neutral known-answer contract for one complete delivery request.
|
|
320
|
+
* The secret is synthetic and deliberately not shaped like a credential.
|
|
321
|
+
*/
|
|
322
|
+
export declare const CALLBACK_DELIVERY_PROTOCOL: {
|
|
323
|
+
readonly envelope: {
|
|
324
|
+
/**
|
|
325
|
+
* The frozen wire order, and `completion` sits inside it.
|
|
326
|
+
*
|
|
327
|
+
* **This list is what a receiver outside JavaScript implements against**,
|
|
328
|
+
* so a field that reached the wire without reaching this array would make
|
|
329
|
+
* every callback delivery fail signature verification for exactly the
|
|
330
|
+
* integrators who cannot read the serializer. From `2026-08-23` every
|
|
331
|
+
* delivery carries `completion`; under `2026-08-08` it was optional, and
|
|
332
|
+
* the order was fixed anyway, because a position decided at emit time is a
|
|
333
|
+
* position nobody else can reproduce.
|
|
334
|
+
*/
|
|
335
|
+
readonly propertyOrder: readonly ["version", "type", "scheduleId", "occurrenceId", "plannedFor", "attempt", "completion", "trigger", "data"];
|
|
336
|
+
/**
|
|
337
|
+
* The order **inside** `completion`, which is frozen for the same reason.
|
|
338
|
+
*
|
|
339
|
+
* `deadline` is appended rather than inserted, so a receiver that already
|
|
340
|
+
* reconstructs the `2026-08-08` object adds a key instead of re-deriving
|
|
341
|
+
* an order. Under `2026-08-08` the same object is the first two entries
|
|
342
|
+
* and nothing else.
|
|
343
|
+
*/
|
|
344
|
+
readonly completionPropertyOrder: readonly ["token", "url", "deadline"];
|
|
345
|
+
/**
|
|
346
|
+
* The order under `2026-08-08`, whose `completion` is optional and carries
|
|
347
|
+
* no `deadline`. The top-level list is the same; what differs is inside.
|
|
348
|
+
*/
|
|
349
|
+
readonly propertyOrder_2026_08_08: readonly ["version", "type", "scheduleId", "occurrenceId", "plannedFor", "attempt", "completion", "trigger", "data"];
|
|
350
|
+
/** The order under `2026-07-01`, which carries no trigger. */
|
|
351
|
+
readonly propertyOrder_2026_07_01: readonly ["version", "type", "scheduleId", "occurrenceId", "plannedFor", "attempt", "data"];
|
|
352
|
+
readonly serialization: "compact UTF-8 JSON with the stated top-level property order";
|
|
353
|
+
readonly trigger: {
|
|
354
|
+
readonly automatic: "A first attempt or a retry the system chose. Nobody asked for it, it may repeat work already done, and a receiver must run the occurrence at most once.";
|
|
355
|
+
readonly default: "An envelope with no trigger field is automatic. Only 2026-07-01 can omit it.";
|
|
356
|
+
readonly propertyOrder: readonly ["kind", "sourceAttemptId"];
|
|
357
|
+
readonly ordinal: "attempt. It only ever increases within an occurrence and every copy of one redelivery carries the same value, so a receiver recognises a repeat by keeping the highest it has run to completion rather than by keeping them all.";
|
|
358
|
+
readonly redelivery: "A person ordered this delivery under the occurrences:redeliver scope. sourceAttemptId names the retained attempt they chose to follow and is there to show them, not to decide whether the work runs — attempt is what decides that. It is a request for the work to happen again, and a receiver that suppresses it as a duplicate answers a deliberate instruction with silence.";
|
|
359
|
+
readonly requirement: "run the handler again for a redelivery whose attempt is above the highest this receiver has run to completion for the occurrence, keep suppressing every automatic delivery of a completed occurrence, and suppress every redelivery at or below that high-water mark";
|
|
360
|
+
};
|
|
361
|
+
readonly version: "2026-08-23";
|
|
362
|
+
/**
|
|
363
|
+
* Every version a receiver may be handed. A sender emits only the last;
|
|
364
|
+
* accepting the whole list is what lets a receiver deploy first.
|
|
365
|
+
*/
|
|
366
|
+
readonly versions: readonly ["2026-07-01", "2026-08-08", "2026-08-23"];
|
|
367
|
+
};
|
|
368
|
+
readonly headers: {
|
|
369
|
+
readonly contentType: "application/json";
|
|
370
|
+
readonly required: readonly ["Content-Type", "Callback-Delivery-Id", "Callback-Occurrence-Id", "Callback-Timestamp", "Callback-Key-Id", "Callback-Signature"];
|
|
371
|
+
};
|
|
372
|
+
readonly replay: {
|
|
373
|
+
readonly identity: "Callback-Delivery-Id";
|
|
374
|
+
readonly requirement: "atomically reject a previously accepted delivery id before parsing";
|
|
375
|
+
};
|
|
376
|
+
readonly signature: {
|
|
377
|
+
readonly algorithm: "HMAC-SHA-256";
|
|
378
|
+
readonly format: "v1=<64 lowercase hexadecimal characters>";
|
|
379
|
+
readonly input: "ASCII <Callback-Timestamp>.<Callback-Delivery-Id>. followed by the exact raw request body bytes";
|
|
380
|
+
readonly scheme: "v1";
|
|
381
|
+
readonly timestampToleranceSeconds: 300;
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* The retired `2026-07-01` envelope, kept as a known answer.
|
|
385
|
+
*
|
|
386
|
+
* A receiver is still held to verifying it, because a sender mid-rollout may
|
|
387
|
+
* have signed it before the version moved. Its MAC is the one published on
|
|
388
|
+
* 2026-07-28 and is deliberately not recomputed.
|
|
389
|
+
*/
|
|
390
|
+
readonly legacyTestVector: {
|
|
391
|
+
readonly envelope: {
|
|
392
|
+
readonly attempt: 1;
|
|
393
|
+
readonly data: {};
|
|
394
|
+
readonly occurrenceId: "occ_0123456789abcdefghjkmnpqrs";
|
|
395
|
+
readonly plannedFor: "2026-07-28T15:00:00.000Z";
|
|
396
|
+
readonly scheduleId: "sch_0123456789abcdefghjkmnpqrs";
|
|
397
|
+
readonly type: "schedule.occurrence";
|
|
398
|
+
readonly version: "2026-07-01";
|
|
399
|
+
};
|
|
400
|
+
readonly exactBodyBase64: "eyJ2ZXJzaW9uIjoiMjAyNi0wNy0wMSIsInR5cGUiOiJzY2hlZHVsZS5vY2N1cnJlbmNlIiwic2NoZWR1bGVJZCI6InNjaF8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsIm9jY3VycmVuY2VJZCI6Im9jY18wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsInBsYW5uZWRGb3IiOiIyMDI2LTA3LTI4VDE1OjAwOjAwLjAwMFoiLCJhdHRlbXB0IjoxLCJkYXRhIjp7fX0=";
|
|
401
|
+
readonly exactBodyUtf8: "{\"version\":\"2026-07-01\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"data\":{}}";
|
|
402
|
+
readonly headers: {
|
|
403
|
+
readonly "Callback-Delivery-Id": "dlv_0123456789abcdefghjkmnpqrs";
|
|
404
|
+
readonly "Callback-Key-Id": "csk_0123456789abcdefghjkmnpqrs";
|
|
405
|
+
readonly "Callback-Occurrence-Id": "occ_0123456789abcdefghjkmnpqrs";
|
|
406
|
+
readonly "Callback-Signature": "v1=11135fdfc0bc5d49ebcc9d3a0d8df1116179097cefe961c6eca281339e5cfab3";
|
|
407
|
+
readonly "Callback-Timestamp": "2026-07-28T15:00:00.000Z";
|
|
408
|
+
readonly "Content-Type": "application/json";
|
|
409
|
+
};
|
|
410
|
+
readonly secret: "greatwait-signature-vector-key-not-a-credential";
|
|
411
|
+
readonly signingInputBase64: "MjAyNi0wNy0yOFQxNTowMDowMC4wMDBaLmRsdl8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycy57InZlcnNpb24iOiIyMDI2LTA3LTAxIiwidHlwZSI6InNjaGVkdWxlLm9jY3VycmVuY2UiLCJzY2hlZHVsZUlkIjoic2NoXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwib2NjdXJyZW5jZUlkIjoib2NjXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwicGxhbm5lZEZvciI6IjIwMjYtMDctMjhUMTU6MDA6MDAuMDAwWiIsImF0dGVtcHQiOjEsImRhdGEiOnt9fQ==";
|
|
412
|
+
readonly signingInputUtf8: "2026-07-28T15:00:00.000Z.dlv_0123456789abcdefghjkmnpqrs.{\"version\":\"2026-07-01\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"data\":{}}";
|
|
413
|
+
};
|
|
414
|
+
/**
|
|
415
|
+
* The retired `2026-08-08` envelope **without** `completion`, kept as a known
|
|
416
|
+
* answer.
|
|
417
|
+
*
|
|
418
|
+
* It is the vector `testVector` was until 2026-08-23, moved here rather than
|
|
419
|
+
* recomputed. Under `2026-08-08` an absent `completion` was the ordinary
|
|
420
|
+
* case, so this is the shape most deliveries signed under that version had,
|
|
421
|
+
* and a receiver still verifying them needs its published answer to stay put.
|
|
422
|
+
*/
|
|
423
|
+
readonly triggerTestVector: {
|
|
424
|
+
readonly envelope: {
|
|
425
|
+
readonly attempt: 1;
|
|
426
|
+
readonly data: {};
|
|
427
|
+
readonly occurrenceId: "occ_0123456789abcdefghjkmnpqrs";
|
|
428
|
+
readonly plannedFor: "2026-07-28T15:00:00.000Z";
|
|
429
|
+
readonly scheduleId: "sch_0123456789abcdefghjkmnpqrs";
|
|
430
|
+
readonly trigger: {
|
|
431
|
+
readonly kind: "automatic";
|
|
432
|
+
};
|
|
433
|
+
readonly type: "schedule.occurrence";
|
|
434
|
+
readonly version: "2026-08-08";
|
|
435
|
+
};
|
|
436
|
+
readonly exactBodyBase64: "eyJ2ZXJzaW9uIjoiMjAyNi0wOC0wOCIsInR5cGUiOiJzY2hlZHVsZS5vY2N1cnJlbmNlIiwic2NoZWR1bGVJZCI6InNjaF8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsIm9jY3VycmVuY2VJZCI6Im9jY18wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsInBsYW5uZWRGb3IiOiIyMDI2LTA3LTI4VDE1OjAwOjAwLjAwMFoiLCJhdHRlbXB0IjoxLCJ0cmlnZ2VyIjp7ImtpbmQiOiJhdXRvbWF0aWMifSwiZGF0YSI6e319";
|
|
437
|
+
readonly exactBodyUtf8: "{\"version\":\"2026-08-08\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"trigger\":{\"kind\":\"automatic\"},\"data\":{}}";
|
|
438
|
+
readonly headers: {
|
|
439
|
+
readonly "Callback-Delivery-Id": "dlv_0123456789abcdefghjkmnpqrs";
|
|
440
|
+
readonly "Callback-Key-Id": "csk_0123456789abcdefghjkmnpqrs";
|
|
441
|
+
readonly "Callback-Occurrence-Id": "occ_0123456789abcdefghjkmnpqrs";
|
|
442
|
+
readonly "Callback-Signature": "v1=bd96c6d68a5c0521c18600d92913ce4dcf32036f2c930d0f9db9de55da9280e7";
|
|
443
|
+
readonly "Callback-Timestamp": "2026-07-28T15:00:00.000Z";
|
|
444
|
+
readonly "Content-Type": "application/json";
|
|
445
|
+
};
|
|
446
|
+
readonly secret: "greatwait-signature-vector-key-not-a-credential";
|
|
447
|
+
readonly signingInputBase64: "MjAyNi0wNy0yOFQxNTowMDowMC4wMDBaLmRsdl8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycy57InZlcnNpb24iOiIyMDI2LTA4LTA4IiwidHlwZSI6InNjaGVkdWxlLm9jY3VycmVuY2UiLCJzY2hlZHVsZUlkIjoic2NoXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwib2NjdXJyZW5jZUlkIjoib2NjXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwicGxhbm5lZEZvciI6IjIwMjYtMDctMjhUMTU6MDA6MDAuMDAwWiIsImF0dGVtcHQiOjEsInRyaWdnZXIiOnsia2luZCI6ImF1dG9tYXRpYyJ9LCJkYXRhIjp7fX0=";
|
|
448
|
+
readonly signingInputUtf8: "2026-07-28T15:00:00.000Z.dlv_0123456789abcdefghjkmnpqrs.{\"version\":\"2026-08-08\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"trigger\":{\"kind\":\"automatic\"},\"data\":{}}";
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* The retired `2026-08-08` envelope **with** `completion`, kept as a known
|
|
452
|
+
* answer.
|
|
453
|
+
*
|
|
454
|
+
* Under that version the field was optional and appeared only on a schedule
|
|
455
|
+
* configured for `callback` completion, and it carried no `deadline`. This is
|
|
456
|
+
* the published answer for that shape, byte for byte and MAC unchanged: a
|
|
457
|
+
* receiver mid-rollout is still handed it, and recomputing this vector is
|
|
458
|
+
* exactly the move that would hide a change to `signingInput`.
|
|
459
|
+
*
|
|
460
|
+
* The token is synthetic and shaped like ours without being one. A real token
|
|
461
|
+
* names an attempt at a dispatch epoch and expires; this is a vector.
|
|
462
|
+
*/
|
|
463
|
+
readonly completionTestVector: {
|
|
464
|
+
readonly envelope: {
|
|
465
|
+
readonly attempt: 1;
|
|
466
|
+
readonly completion: {
|
|
467
|
+
readonly token: "v1.example-claim-not-a-real-token.example-mac";
|
|
468
|
+
readonly url: "https://greatwait-scheduler.example/callbacks/completion";
|
|
469
|
+
};
|
|
470
|
+
readonly data: {};
|
|
471
|
+
readonly occurrenceId: "occ_0123456789abcdefghjkmnpqrs";
|
|
472
|
+
readonly plannedFor: "2026-07-28T15:00:00.000Z";
|
|
473
|
+
readonly scheduleId: "sch_0123456789abcdefghjkmnpqrs";
|
|
474
|
+
readonly trigger: {
|
|
475
|
+
readonly kind: "automatic";
|
|
476
|
+
};
|
|
477
|
+
readonly type: "schedule.occurrence";
|
|
478
|
+
readonly version: "2026-08-08";
|
|
479
|
+
};
|
|
480
|
+
readonly exactBodyBase64: "eyJ2ZXJzaW9uIjoiMjAyNi0wOC0wOCIsInR5cGUiOiJzY2hlZHVsZS5vY2N1cnJlbmNlIiwic2NoZWR1bGVJZCI6InNjaF8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsIm9jY3VycmVuY2VJZCI6Im9jY18wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsInBsYW5uZWRGb3IiOiIyMDI2LTA3LTI4VDE1OjAwOjAwLjAwMFoiLCJhdHRlbXB0IjoxLCJjb21wbGV0aW9uIjp7InRva2VuIjoidjEuZXhhbXBsZS1jbGFpbS1ub3QtYS1yZWFsLXRva2VuLmV4YW1wbGUtbWFjIiwidXJsIjoiaHR0cHM6Ly9ncmVhdHdhaXQtc2NoZWR1bGVyLmV4YW1wbGUvY2FsbGJhY2tzL2NvbXBsZXRpb24ifSwidHJpZ2dlciI6eyJraW5kIjoiYXV0b21hdGljIn0sImRhdGEiOnt9fQ==";
|
|
481
|
+
readonly exactBodyUtf8: "{\"version\":\"2026-08-08\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"completion\":{\"token\":\"v1.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\"},\"trigger\":{\"kind\":\"automatic\"},\"data\":{}}";
|
|
482
|
+
readonly headers: {
|
|
483
|
+
readonly "Callback-Delivery-Id": "dlv_0123456789abcdefghjkmnpqrs";
|
|
484
|
+
readonly "Callback-Key-Id": "csk_0123456789abcdefghjkmnpqrs";
|
|
485
|
+
readonly "Callback-Occurrence-Id": "occ_0123456789abcdefghjkmnpqrs";
|
|
486
|
+
readonly "Callback-Signature": "v1=38c6d854051087ed408a5f6252d90375859f305aeb0bca1c3f3a5bb63135e138";
|
|
487
|
+
readonly "Callback-Timestamp": "2026-07-28T15:00:00.000Z";
|
|
488
|
+
readonly "Content-Type": "application/json";
|
|
489
|
+
};
|
|
490
|
+
readonly secret: "greatwait-signature-vector-key-not-a-credential";
|
|
491
|
+
readonly signingInputBase64: "MjAyNi0wNy0yOFQxNTowMDowMC4wMDBaLmRsdl8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycy57InZlcnNpb24iOiIyMDI2LTA4LTA4IiwidHlwZSI6InNjaGVkdWxlLm9jY3VycmVuY2UiLCJzY2hlZHVsZUlkIjoic2NoXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwib2NjdXJyZW5jZUlkIjoib2NjXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwicGxhbm5lZEZvciI6IjIwMjYtMDctMjhUMTU6MDA6MDAuMDAwWiIsImF0dGVtcHQiOjEsImNvbXBsZXRpb24iOnsidG9rZW4iOiJ2MS5leGFtcGxlLWNsYWltLW5vdC1hLXJlYWwtdG9rZW4uZXhhbXBsZS1tYWMiLCJ1cmwiOiJodHRwczovL2dyZWF0d2FpdC1zY2hlZHVsZXIuZXhhbXBsZS9jYWxsYmFja3MvY29tcGxldGlvbiJ9LCJ0cmlnZ2VyIjp7ImtpbmQiOiJhdXRvbWF0aWMifSwiZGF0YSI6e319";
|
|
492
|
+
readonly signingInputUtf8: "2026-07-28T15:00:00.000Z.dlv_0123456789abcdefghjkmnpqrs.{\"version\":\"2026-08-08\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"completion\":{\"token\":\"v1.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\"},\"trigger\":{\"kind\":\"automatic\"},\"data\":{}}";
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* A redelivery, which is the only vector that discriminates this change.
|
|
496
|
+
*
|
|
497
|
+
* The two automatic vectors would still pass an implementation that dropped
|
|
498
|
+
* `trigger` on the floor and hard-coded `{"kind":"automatic"}`; this one
|
|
499
|
+
* cannot, because both the kind and the source attempt are inside the signed
|
|
500
|
+
* bytes.
|
|
501
|
+
*/
|
|
502
|
+
readonly redeliveryTestVector: {
|
|
503
|
+
readonly envelope: {
|
|
504
|
+
readonly attempt: 2;
|
|
505
|
+
readonly completion: {
|
|
506
|
+
readonly deadline: "2026-07-28T15:00:05.000Z";
|
|
507
|
+
readonly token: "v2.example-claim-not-a-real-token.example-mac";
|
|
508
|
+
readonly url: "https://greatwait-scheduler.example/callbacks/completion";
|
|
509
|
+
};
|
|
510
|
+
readonly data: {};
|
|
511
|
+
readonly occurrenceId: "occ_0123456789abcdefghjkmnpqrs";
|
|
512
|
+
readonly plannedFor: "2026-07-28T15:00:00.000Z";
|
|
513
|
+
readonly scheduleId: "sch_0123456789abcdefghjkmnpqrs";
|
|
514
|
+
readonly trigger: {
|
|
515
|
+
readonly kind: "redelivery";
|
|
516
|
+
readonly sourceAttemptId: "att_0123456789abcdefghjkmnpqrs";
|
|
517
|
+
};
|
|
518
|
+
readonly type: "schedule.occurrence";
|
|
519
|
+
readonly version: "2026-08-23";
|
|
520
|
+
};
|
|
521
|
+
readonly exactBodyBase64: "eyJ2ZXJzaW9uIjoiMjAyNi0wOC0yMyIsInR5cGUiOiJzY2hlZHVsZS5vY2N1cnJlbmNlIiwic2NoZWR1bGVJZCI6InNjaF8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsIm9jY3VycmVuY2VJZCI6Im9jY18wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsInBsYW5uZWRGb3IiOiIyMDI2LTA3LTI4VDE1OjAwOjAwLjAwMFoiLCJhdHRlbXB0IjoyLCJjb21wbGV0aW9uIjp7InRva2VuIjoidjIuZXhhbXBsZS1jbGFpbS1ub3QtYS1yZWFsLXRva2VuLmV4YW1wbGUtbWFjIiwidXJsIjoiaHR0cHM6Ly9ncmVhdHdhaXQtc2NoZWR1bGVyLmV4YW1wbGUvY2FsbGJhY2tzL2NvbXBsZXRpb24iLCJkZWFkbGluZSI6IjIwMjYtMDctMjhUMTU6MDA6MDUuMDAwWiJ9LCJ0cmlnZ2VyIjp7ImtpbmQiOiJyZWRlbGl2ZXJ5Iiwic291cmNlQXR0ZW1wdElkIjoiYXR0XzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIn0sImRhdGEiOnt9fQ==";
|
|
522
|
+
readonly exactBodyUtf8: "{\"version\":\"2026-08-23\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":2,\"completion\":{\"token\":\"v2.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\",\"deadline\":\"2026-07-28T15:00:05.000Z\"},\"trigger\":{\"kind\":\"redelivery\",\"sourceAttemptId\":\"att_0123456789abcdefghjkmnpqrs\"},\"data\":{}}";
|
|
523
|
+
readonly headers: {
|
|
524
|
+
readonly "Callback-Delivery-Id": "dlv_0123456789abcdefghjkmnpqrs";
|
|
525
|
+
readonly "Callback-Key-Id": "csk_0123456789abcdefghjkmnpqrs";
|
|
526
|
+
readonly "Callback-Occurrence-Id": "occ_0123456789abcdefghjkmnpqrs";
|
|
527
|
+
readonly "Callback-Signature": "v1=9d698aed6d05dcd964f00d6e6796aab238f4f50e6f5cfeb61751fd7850778a04";
|
|
528
|
+
readonly "Callback-Timestamp": "2026-07-28T15:00:00.000Z";
|
|
529
|
+
readonly "Content-Type": "application/json";
|
|
530
|
+
};
|
|
531
|
+
readonly secret: "greatwait-signature-vector-key-not-a-credential";
|
|
532
|
+
readonly signingInputBase64: "MjAyNi0wNy0yOFQxNTowMDowMC4wMDBaLmRsdl8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycy57InZlcnNpb24iOiIyMDI2LTA4LTIzIiwidHlwZSI6InNjaGVkdWxlLm9jY3VycmVuY2UiLCJzY2hlZHVsZUlkIjoic2NoXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwib2NjdXJyZW5jZUlkIjoib2NjXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwicGxhbm5lZEZvciI6IjIwMjYtMDctMjhUMTU6MDA6MDAuMDAwWiIsImF0dGVtcHQiOjIsImNvbXBsZXRpb24iOnsidG9rZW4iOiJ2Mi5leGFtcGxlLWNsYWltLW5vdC1hLXJlYWwtdG9rZW4uZXhhbXBsZS1tYWMiLCJ1cmwiOiJodHRwczovL2dyZWF0d2FpdC1zY2hlZHVsZXIuZXhhbXBsZS9jYWxsYmFja3MvY29tcGxldGlvbiIsImRlYWRsaW5lIjoiMjAyNi0wNy0yOFQxNTowMDowNS4wMDBaIn0sInRyaWdnZXIiOnsia2luZCI6InJlZGVsaXZlcnkiLCJzb3VyY2VBdHRlbXB0SWQiOiJhdHRfMDEyMzQ1Njc4OWFiY2RlZmdoamttbnBxcnMifSwiZGF0YSI6e319";
|
|
533
|
+
readonly signingInputUtf8: "2026-07-28T15:00:00.000Z.dlv_0123456789abcdefghjkmnpqrs.{\"version\":\"2026-08-23\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":2,\"completion\":{\"token\":\"v2.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\",\"deadline\":\"2026-07-28T15:00:05.000Z\"},\"trigger\":{\"kind\":\"redelivery\",\"sourceAttemptId\":\"att_0123456789abcdefghjkmnpqrs\"},\"data\":{}}";
|
|
534
|
+
};
|
|
535
|
+
/**
|
|
536
|
+
* The ordinary current-version delivery: automatic, and carrying completion
|
|
537
|
+
* material because every `2026-08-23` delivery does.
|
|
538
|
+
*/
|
|
539
|
+
readonly testVector: {
|
|
540
|
+
readonly envelope: {
|
|
541
|
+
readonly attempt: 1;
|
|
542
|
+
readonly completion: {
|
|
543
|
+
readonly deadline: "2026-07-28T15:00:05.000Z";
|
|
544
|
+
readonly token: "v2.example-claim-not-a-real-token.example-mac";
|
|
545
|
+
readonly url: "https://greatwait-scheduler.example/callbacks/completion";
|
|
546
|
+
};
|
|
547
|
+
readonly data: {};
|
|
548
|
+
readonly occurrenceId: "occ_0123456789abcdefghjkmnpqrs";
|
|
549
|
+
readonly plannedFor: "2026-07-28T15:00:00.000Z";
|
|
550
|
+
readonly scheduleId: "sch_0123456789abcdefghjkmnpqrs";
|
|
551
|
+
readonly trigger: {
|
|
552
|
+
readonly kind: "automatic";
|
|
553
|
+
};
|
|
554
|
+
readonly type: "schedule.occurrence";
|
|
555
|
+
readonly version: "2026-08-23";
|
|
556
|
+
};
|
|
557
|
+
readonly exactBodyBase64: "eyJ2ZXJzaW9uIjoiMjAyNi0wOC0yMyIsInR5cGUiOiJzY2hlZHVsZS5vY2N1cnJlbmNlIiwic2NoZWR1bGVJZCI6InNjaF8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsIm9jY3VycmVuY2VJZCI6Im9jY18wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsInBsYW5uZWRGb3IiOiIyMDI2LTA3LTI4VDE1OjAwOjAwLjAwMFoiLCJhdHRlbXB0IjoxLCJjb21wbGV0aW9uIjp7InRva2VuIjoidjIuZXhhbXBsZS1jbGFpbS1ub3QtYS1yZWFsLXRva2VuLmV4YW1wbGUtbWFjIiwidXJsIjoiaHR0cHM6Ly9ncmVhdHdhaXQtc2NoZWR1bGVyLmV4YW1wbGUvY2FsbGJhY2tzL2NvbXBsZXRpb24iLCJkZWFkbGluZSI6IjIwMjYtMDctMjhUMTU6MDA6MDUuMDAwWiJ9LCJ0cmlnZ2VyIjp7ImtpbmQiOiJhdXRvbWF0aWMifSwiZGF0YSI6e319";
|
|
558
|
+
readonly exactBodyUtf8: "{\"version\":\"2026-08-23\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"completion\":{\"token\":\"v2.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\",\"deadline\":\"2026-07-28T15:00:05.000Z\"},\"trigger\":{\"kind\":\"automatic\"},\"data\":{}}";
|
|
559
|
+
readonly headers: {
|
|
560
|
+
readonly "Callback-Delivery-Id": "dlv_0123456789abcdefghjkmnpqrs";
|
|
561
|
+
readonly "Callback-Key-Id": "csk_0123456789abcdefghjkmnpqrs";
|
|
562
|
+
readonly "Callback-Occurrence-Id": "occ_0123456789abcdefghjkmnpqrs";
|
|
563
|
+
readonly "Callback-Signature": "v1=5bf68b786fbac1afa7e3474ae5039ad27900b0185bca43a3e45185d56315a312";
|
|
564
|
+
readonly "Callback-Timestamp": "2026-07-28T15:00:00.000Z";
|
|
565
|
+
readonly "Content-Type": "application/json";
|
|
566
|
+
};
|
|
567
|
+
readonly secret: "greatwait-signature-vector-key-not-a-credential";
|
|
568
|
+
readonly signingInputBase64: "MjAyNi0wNy0yOFQxNTowMDowMC4wMDBaLmRsdl8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycy57InZlcnNpb24iOiIyMDI2LTA4LTIzIiwidHlwZSI6InNjaGVkdWxlLm9jY3VycmVuY2UiLCJzY2hlZHVsZUlkIjoic2NoXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwib2NjdXJyZW5jZUlkIjoib2NjXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwicGxhbm5lZEZvciI6IjIwMjYtMDctMjhUMTU6MDA6MDAuMDAwWiIsImF0dGVtcHQiOjEsImNvbXBsZXRpb24iOnsidG9rZW4iOiJ2Mi5leGFtcGxlLWNsYWltLW5vdC1hLXJlYWwtdG9rZW4uZXhhbXBsZS1tYWMiLCJ1cmwiOiJodHRwczovL2dyZWF0d2FpdC1zY2hlZHVsZXIuZXhhbXBsZS9jYWxsYmFja3MvY29tcGxldGlvbiIsImRlYWRsaW5lIjoiMjAyNi0wNy0yOFQxNTowMDowNS4wMDBaIn0sInRyaWdnZXIiOnsia2luZCI6ImF1dG9tYXRpYyJ9LCJkYXRhIjp7fX0=";
|
|
569
|
+
readonly signingInputUtf8: "2026-07-28T15:00:00.000Z.dlv_0123456789abcdefghjkmnpqrs.{\"version\":\"2026-08-23\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"completion\":{\"token\":\"v2.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\",\"deadline\":\"2026-07-28T15:00:05.000Z\"},\"trigger\":{\"kind\":\"automatic\"},\"data\":{}}";
|
|
570
|
+
};
|
|
571
|
+
readonly unicodeTestVector: {
|
|
572
|
+
readonly envelope: {
|
|
573
|
+
readonly attempt: 1;
|
|
574
|
+
readonly completion: {
|
|
575
|
+
readonly deadline: "2026-07-28T15:00:05.000Z";
|
|
576
|
+
readonly token: "v2.example-claim-not-a-real-token.example-mac";
|
|
577
|
+
readonly url: "https://greatwait-scheduler.example/callbacks/completion";
|
|
578
|
+
};
|
|
579
|
+
readonly data: {
|
|
580
|
+
readonly note: "café ☃";
|
|
581
|
+
};
|
|
582
|
+
readonly occurrenceId: "occ_0123456789abcdefghjkmnpqrs";
|
|
583
|
+
readonly plannedFor: "2026-07-28T15:00:00.000Z";
|
|
584
|
+
readonly scheduleId: "sch_0123456789abcdefghjkmnpqrs";
|
|
585
|
+
readonly trigger: {
|
|
586
|
+
readonly kind: "automatic";
|
|
587
|
+
};
|
|
588
|
+
readonly type: "schedule.occurrence";
|
|
589
|
+
readonly version: "2026-08-23";
|
|
590
|
+
};
|
|
591
|
+
readonly exactBodyBase64: "eyJ2ZXJzaW9uIjoiMjAyNi0wOC0yMyIsInR5cGUiOiJzY2hlZHVsZS5vY2N1cnJlbmNlIiwic2NoZWR1bGVJZCI6InNjaF8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsIm9jY3VycmVuY2VJZCI6Im9jY18wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycyIsInBsYW5uZWRGb3IiOiIyMDI2LTA3LTI4VDE1OjAwOjAwLjAwMFoiLCJhdHRlbXB0IjoxLCJjb21wbGV0aW9uIjp7InRva2VuIjoidjIuZXhhbXBsZS1jbGFpbS1ub3QtYS1yZWFsLXRva2VuLmV4YW1wbGUtbWFjIiwidXJsIjoiaHR0cHM6Ly9ncmVhdHdhaXQtc2NoZWR1bGVyLmV4YW1wbGUvY2FsbGJhY2tzL2NvbXBsZXRpb24iLCJkZWFkbGluZSI6IjIwMjYtMDctMjhUMTU6MDA6MDUuMDAwWiJ9LCJ0cmlnZ2VyIjp7ImtpbmQiOiJhdXRvbWF0aWMifSwiZGF0YSI6eyJub3RlIjoiY2Fmw6kg4piDIn19";
|
|
592
|
+
readonly exactBodyUtf8: "{\"version\":\"2026-08-23\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"completion\":{\"token\":\"v2.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\",\"deadline\":\"2026-07-28T15:00:05.000Z\"},\"trigger\":{\"kind\":\"automatic\"},\"data\":{\"note\":\"café ☃\"}}";
|
|
593
|
+
readonly headers: {
|
|
594
|
+
readonly "Callback-Delivery-Id": "dlv_0123456789abcdefghjkmnpqrs";
|
|
595
|
+
readonly "Callback-Key-Id": "csk_0123456789abcdefghjkmnpqrs";
|
|
596
|
+
readonly "Callback-Occurrence-Id": "occ_0123456789abcdefghjkmnpqrs";
|
|
597
|
+
readonly "Callback-Signature": "v1=18a8d1c50ea54bd0a5ec27abe80d1c98e44a7f358ecf8f06615c174075100e72";
|
|
598
|
+
readonly "Callback-Timestamp": "2026-07-28T15:00:00.000Z";
|
|
599
|
+
readonly "Content-Type": "application/json";
|
|
600
|
+
};
|
|
601
|
+
readonly secret: "greatwait-signature-vector-key-not-a-credential";
|
|
602
|
+
readonly signingInputBase64: "MjAyNi0wNy0yOFQxNTowMDowMC4wMDBaLmRsdl8wMTIzNDU2Nzg5YWJjZGVmZ2hqa21ucHFycy57InZlcnNpb24iOiIyMDI2LTA4LTIzIiwidHlwZSI6InNjaGVkdWxlLm9jY3VycmVuY2UiLCJzY2hlZHVsZUlkIjoic2NoXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwib2NjdXJyZW5jZUlkIjoib2NjXzAxMjM0NTY3ODlhYmNkZWZnaGprbW5wcXJzIiwicGxhbm5lZEZvciI6IjIwMjYtMDctMjhUMTU6MDA6MDAuMDAwWiIsImF0dGVtcHQiOjEsImNvbXBsZXRpb24iOnsidG9rZW4iOiJ2Mi5leGFtcGxlLWNsYWltLW5vdC1hLXJlYWwtdG9rZW4uZXhhbXBsZS1tYWMiLCJ1cmwiOiJodHRwczovL2dyZWF0d2FpdC1zY2hlZHVsZXIuZXhhbXBsZS9jYWxsYmFja3MvY29tcGxldGlvbiIsImRlYWRsaW5lIjoiMjAyNi0wNy0yOFQxNTowMDowNS4wMDBaIn0sInRyaWdnZXIiOnsia2luZCI6ImF1dG9tYXRpYyJ9LCJkYXRhIjp7Im5vdGUiOiJjYWbDqSDimIMifX0=";
|
|
603
|
+
readonly signingInputUtf8: "2026-07-28T15:00:00.000Z.dlv_0123456789abcdefghjkmnpqrs.{\"version\":\"2026-08-23\",\"type\":\"schedule.occurrence\",\"scheduleId\":\"sch_0123456789abcdefghjkmnpqrs\",\"occurrenceId\":\"occ_0123456789abcdefghjkmnpqrs\",\"plannedFor\":\"2026-07-28T15:00:00.000Z\",\"attempt\":1,\"completion\":{\"token\":\"v2.example-claim-not-a-real-token.example-mac\",\"url\":\"https://greatwait-scheduler.example/callbacks/completion\",\"deadline\":\"2026-07-28T15:00:05.000Z\"},\"trigger\":{\"kind\":\"automatic\"},\"data\":{\"note\":\"café ☃\"}}";
|
|
604
|
+
};
|
|
605
|
+
readonly version: "2026-08-23";
|
|
606
|
+
};
|
|
607
|
+
/**
|
|
608
|
+
* One published known-answer vector, whatever version it is for.
|
|
609
|
+
*
|
|
610
|
+
* The shape every vector in {@link CALLBACK_DELIVERY_PROTOCOL} has, named so
|
|
611
|
+
* the whole set can be enumerated rather than listed.
|
|
612
|
+
*/
|
|
613
|
+
export interface CallbackDeliveryTestVector {
|
|
614
|
+
readonly envelope: CallbackEnvelope;
|
|
615
|
+
readonly exactBodyBase64: string;
|
|
616
|
+
readonly exactBodyUtf8: string;
|
|
617
|
+
/**
|
|
618
|
+
* Every header the delivery carries, named rather than indexed.
|
|
619
|
+
*
|
|
620
|
+
* A `Record<string, string>` would read back as `string | undefined` under
|
|
621
|
+
* `noUncheckedIndexedAccess`, which makes every consumer assert a header the
|
|
622
|
+
* vector is defined to have — and an assertion is exactly what a published
|
|
623
|
+
* known answer should not need.
|
|
624
|
+
*/
|
|
625
|
+
readonly headers: {
|
|
626
|
+
readonly "Callback-Delivery-Id": string;
|
|
627
|
+
readonly "Callback-Key-Id": string;
|
|
628
|
+
readonly "Callback-Occurrence-Id": string;
|
|
629
|
+
readonly "Callback-Signature": string;
|
|
630
|
+
readonly "Callback-Timestamp": string;
|
|
631
|
+
readonly "Content-Type": string;
|
|
632
|
+
};
|
|
633
|
+
readonly secret: string;
|
|
634
|
+
readonly signingInputBase64: string;
|
|
635
|
+
readonly signingInputUtf8: string;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Every published vector, derived from the protocol rather than listed.
|
|
639
|
+
*
|
|
640
|
+
* **A hand-written list is the failure this exists to prevent.** The duplicated
|
|
641
|
+
* serializer in `@greatwait/delivery` is held byte-for-byte against these, and
|
|
642
|
+
* the one time it drifted, it drifted on the field whose vector nobody had
|
|
643
|
+
* added to the list yet. Reading the protocol object means a vector added later
|
|
644
|
+
* is driven by every suite that already iterates this, with no second edit.
|
|
645
|
+
*
|
|
646
|
+
* **Matched by shape rather than by name**, which is not fastidiousness: the
|
|
647
|
+
* first version of this matched keys ending in `TestVector` and silently
|
|
648
|
+
* dropped `testVector`, whose lower-case first letter makes that suffix test
|
|
649
|
+
* false. A filter that quietly returns one fewer than it should is the same
|
|
650
|
+
* failure as the hand-written list, arrived at differently.
|
|
651
|
+
*/
|
|
652
|
+
export declare function callbackDeliveryTestVectors(): readonly (CallbackDeliveryTestVector & {
|
|
653
|
+
readonly name: string;
|
|
654
|
+
})[];
|
|
655
|
+
/**
|
|
656
|
+
* What moving the envelope to `2026-08-08` is, in the vocabulary the
|
|
657
|
+
* compatibility module classifies.
|
|
658
|
+
*
|
|
659
|
+
* Recorded as data rather than asserted in prose because the verdict is an
|
|
660
|
+
* output: `assess(CALLBACK_ENVELOPE_VERSION_CHANGE)` returns
|
|
661
|
+
* `{ kind: "breaking", requiresNewContractVersion: true }`, and a fixture holds
|
|
662
|
+
* it there. "It only added a field, so it is additive" is the reading this
|
|
663
|
+
* exists to refuse — what a client parses is not the field, it is the version
|
|
664
|
+
* literal, and that literal *changed*.
|
|
665
|
+
*
|
|
666
|
+
* The contract version does not move with it. `CONTRACT_VERSION` versions the
|
|
667
|
+
* REST surface; the envelope carries its own dated version precisely so a break
|
|
668
|
+
* in the callback body does not force every API client to re-pin.
|
|
669
|
+
*/
|
|
670
|
+
export declare const CALLBACK_ENVELOPE_VERSION_CHANGE: {
|
|
671
|
+
readonly operation: "changed";
|
|
672
|
+
readonly subject: "response-field";
|
|
673
|
+
};
|
|
674
|
+
/**
|
|
675
|
+
* How a receiver's response is classified. Public and stable: a customer tunes
|
|
676
|
+
* their endpoint against these categories.
|
|
677
|
+
*/
|
|
678
|
+
export type ResponseClass = "ambiguous" | "retryable" | "success" | "terminal";
|
|
679
|
+
/**
|
|
680
|
+
* The outcome of one delivery attempt. `no-response` is a first-class case,
|
|
681
|
+
* not an absent status: a timeout or a dropped connection means the receiver
|
|
682
|
+
* may or may not have acted, and collapsing that into a status code is how an
|
|
683
|
+
* occurrence gets silently dropped. The copied engine already produces exactly
|
|
684
|
+
* this case.
|
|
685
|
+
*/
|
|
686
|
+
export type AttemptOutcome = {
|
|
687
|
+
readonly kind: "no-response";
|
|
688
|
+
} | {
|
|
689
|
+
readonly kind: "status";
|
|
690
|
+
readonly status: number;
|
|
691
|
+
};
|
|
692
|
+
/**
|
|
693
|
+
* Classifies a status code alone. Use `classifyOutcome` where a request may not
|
|
694
|
+
* complete.
|
|
695
|
+
*
|
|
696
|
+
* **`isCallbackRefusalStatus` is consulted rather than duplicated here.** This
|
|
697
|
+
* function is the published statement of what a receiver's answer means and
|
|
698
|
+
* `performGreatwaitAttempt` is what acts on it; before FXO-1596 the two carried
|
|
699
|
+
* the same status boundary as two independent literals, kept equal by hand.
|
|
700
|
+
* They are now one list, so a status cannot be retried by the executor while
|
|
701
|
+
* this function tells the customer it was terminal.
|
|
702
|
+
*/
|
|
703
|
+
export declare function classifyStatus(status: number): ResponseClass;
|
|
704
|
+
export declare function classifyOutcome(outcome: AttemptOutcome): ResponseClass;
|