@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,986 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type CallbackCompletionOutcome } from "./delivery.js";
|
|
3
|
+
/**
|
|
4
|
+
* The deterministic test sink and the receiver contract it and the shipped
|
|
5
|
+
* middleware both implement.
|
|
6
|
+
*
|
|
7
|
+
* An integrator cannot rehearse a delivery failure against their own receiver
|
|
8
|
+
* without first building one that fails on demand, and the failures that matter
|
|
9
|
+
* — a dropped connection, a receiver that answers three minutes late, a 429
|
|
10
|
+
* carrying a delay hint — are exactly the ones hardest to produce by hand. The
|
|
11
|
+
* sink produces them on request, in test environments only.
|
|
12
|
+
*
|
|
13
|
+
* Two properties are load-bearing and are the reason this vocabulary lives in
|
|
14
|
+
* the contract package rather than inside the API Worker.
|
|
15
|
+
*
|
|
16
|
+
* **The sink and the SDK middleware must agree.** The sink is a reference
|
|
17
|
+
* receiver built on the middleware this story ships, so an integrator who
|
|
18
|
+
* compares the two is comparing one implementation against itself. That is only
|
|
19
|
+
* worth anything if the status a receiver returns for each outcome is published
|
|
20
|
+
* rather than chosen twice — hence `CALLBACK_RECEIVER_STATUS` below.
|
|
21
|
+
*
|
|
22
|
+
* **A sink is a receiver, and the only thing it ever sends is a report about a
|
|
23
|
+
* delivery it has already received.** That sentence used to read "never a
|
|
24
|
+
* sender", and the deferring behaviors added on 2026-08-23 are why it does not:
|
|
25
|
+
* a receiver that defers is one that answers `202` now and reports later, and a
|
|
26
|
+
* sink that could not report later could rehearse the first half of that
|
|
27
|
+
* exchange and none of the half where it goes wrong.
|
|
28
|
+
*
|
|
29
|
+
* What has not changed is that **there is deliberately no field anywhere in
|
|
30
|
+
* this file that names a place to send to.** A completion report goes to the
|
|
31
|
+
* credential inside the signed envelope of the delivery in hand, sent to the
|
|
32
|
+
* one address the stage is configured with, and
|
|
33
|
+
* nowhere else: no program, no control operation and no request header names a
|
|
34
|
+
* destination, so a sink still cannot be asked to forward, redirect or fetch,
|
|
35
|
+
* and still cannot be configured into an egress proxy. A delivery whose
|
|
36
|
+
* envelope carries no completion material cannot defer at all.
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* What a sink does with a delivery. Exhaustive, and each entry corresponds to a
|
|
40
|
+
* distinct sender-visible outcome class rather than to a distinct status code.
|
|
41
|
+
*
|
|
42
|
+
* `timeout` and `disconnect` are both ambiguous to the sender, and that is the
|
|
43
|
+
* point rather than a defect in the simulation: a sender genuinely cannot tell
|
|
44
|
+
* them apart, which is why the architecture records ambiguity honestly instead
|
|
45
|
+
* of guessing. They differ in what the *receiver* did — see
|
|
46
|
+
* `TEST_SINK_BEHAVIOR_FACTS`.
|
|
47
|
+
*
|
|
48
|
+
* **The three `defer_*` behaviors are three entries rather than one entry with
|
|
49
|
+
* an option**, for exactly the reason stated above: they produce three
|
|
50
|
+
* different sender-visible outcomes. All three answer the same `202` and the
|
|
51
|
+
* same pending marker, so a list that collapsed them would be a list keyed on
|
|
52
|
+
* the status a receiver returns — which is the thing this list is deliberately
|
|
53
|
+
* not keyed on. What separates them happens *after* the acknowledgement, and it
|
|
54
|
+
* is the whole of what an integrator rehearsing deferral needs to see.
|
|
55
|
+
*/
|
|
56
|
+
export declare const TEST_SINK_BEHAVIORS: readonly ["client_error", "defer_abandon", "defer_fail", "defer_succeed", "delay", "disconnect", "duplicate", "invalid_signature", "rate_limited", "server_error", "success", "timeout"];
|
|
57
|
+
export declare const testSinkBehaviorSchema: z.ZodEnum<{
|
|
58
|
+
rate_limited: "rate_limited";
|
|
59
|
+
success: "success";
|
|
60
|
+
server_error: "server_error";
|
|
61
|
+
client_error: "client_error";
|
|
62
|
+
defer_abandon: "defer_abandon";
|
|
63
|
+
defer_fail: "defer_fail";
|
|
64
|
+
defer_succeed: "defer_succeed";
|
|
65
|
+
delay: "delay";
|
|
66
|
+
disconnect: "disconnect";
|
|
67
|
+
duplicate: "duplicate";
|
|
68
|
+
invalid_signature: "invalid_signature";
|
|
69
|
+
timeout: "timeout";
|
|
70
|
+
}>;
|
|
71
|
+
export type TestSinkBehavior = z.infer<typeof testSinkBehaviorSchema>;
|
|
72
|
+
/**
|
|
73
|
+
* Bounds on what a sink may be asked to do.
|
|
74
|
+
*
|
|
75
|
+
* Every one of these exists because the sink runs inside the API Worker's
|
|
76
|
+
* request budget on the product's own account. A test facility that can be
|
|
77
|
+
* asked to hold a request open indefinitely, or to accumulate history without
|
|
78
|
+
* limit, is a denial-of-service surface wearing a test badge.
|
|
79
|
+
*/
|
|
80
|
+
export declare const TEST_SINK_BOUNDS: {
|
|
81
|
+
/**
|
|
82
|
+
* Longest a sink will wait between acknowledging a deferral and reporting
|
|
83
|
+
* completion.
|
|
84
|
+
*
|
|
85
|
+
* Bounded for the same reason `maxDelayMs` is, and the reason survives the
|
|
86
|
+
* response having already been sent: the report is performed on the same
|
|
87
|
+
* invocation, so a sink asked to report an hour later would be asking the API
|
|
88
|
+
* Worker to stay alive for an hour on the product's own account. Smaller than
|
|
89
|
+
* `maxDelayMs` because nothing is waiting on this wait — a rehearsal of
|
|
90
|
+
* deferral is a rehearsal of the *shape* of the exchange, and any integrator
|
|
91
|
+
* who needs to watch a real deadline expire has `defer_abandon`, which costs
|
|
92
|
+
* nothing at all.
|
|
93
|
+
*/
|
|
94
|
+
readonly maxCompletionDelayMs: 5000;
|
|
95
|
+
/** Longest `delay` a sink will honour before answering. */
|
|
96
|
+
readonly maxDelayMs: 10000;
|
|
97
|
+
/** Longest a `timeout` or `disconnect` holds a request open. */
|
|
98
|
+
readonly maxHoldMs: 45000;
|
|
99
|
+
/** Retained delivery receipts per sink; the oldest are evicted. */
|
|
100
|
+
readonly maxRetainedDeliveries: 50;
|
|
101
|
+
/** Sinks per project/environment. */
|
|
102
|
+
readonly maxSinksPerEnvironment: 10;
|
|
103
|
+
/** Largest request body a sink will read. Larger is refused unread. */
|
|
104
|
+
readonly maxRequestBodyBytes: 65536;
|
|
105
|
+
/** Captured body bytes retained on a receipt; beyond this it is truncated. */
|
|
106
|
+
readonly maxRetainedBodyBytes: 4096;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Selected 4xx a sink may answer with.
|
|
110
|
+
*
|
|
111
|
+
* **They are no longer all terminal to the sender, and which are is not this
|
|
112
|
+
* file's decision.** FXO-1596 moved the refusals — a receiver declining *this
|
|
113
|
+
* delivery now* rather than objecting to the message — onto the receiver's
|
|
114
|
+
* ordinary five-failure budget. `TEST_SINK_REFUSAL_STATUSES` below is the
|
|
115
|
+
* subset of this list that the sender will retry, and `test-sink.test.ts`
|
|
116
|
+
* holds it equal to what `classifyStatus` actually says, so a status moved in
|
|
117
|
+
* `CALLBACK_REFUSAL_STATUSES` and not here fails a test rather than teaching an
|
|
118
|
+
* integrator the wrong lesson.
|
|
119
|
+
*
|
|
120
|
+
* Nothing is removed from what a caller may request: every status here was
|
|
121
|
+
* accepted before and still is.
|
|
122
|
+
*/
|
|
123
|
+
export declare const TEST_SINK_CLIENT_ERROR_STATUSES: readonly [400, 401, 403, 404, 409, 410, 422];
|
|
124
|
+
/**
|
|
125
|
+
* The statuses above that the sender retries rather than settling on.
|
|
126
|
+
*
|
|
127
|
+
* Exists so an integrator simulating a rotated credential (401) or a WAF rule
|
|
128
|
+
* (403) can see five attempts arrive, and one simulating a malformed body (400)
|
|
129
|
+
* or a duplicate (409) can see exactly one.
|
|
130
|
+
*/
|
|
131
|
+
export declare const TEST_SINK_REFUSAL_STATUSES: readonly [401, 403, 404];
|
|
132
|
+
/** Selected 5xx a sink may answer with. Each is retryable to the sender. */
|
|
133
|
+
export declare const TEST_SINK_SERVER_ERROR_STATUSES: readonly [500, 502, 503, 504];
|
|
134
|
+
/**
|
|
135
|
+
* A deterministic behavior program.
|
|
136
|
+
*
|
|
137
|
+
* `behavior` applies to the first `forDeliveries` deliveries and `then` to
|
|
138
|
+
* every one after that, so "fail twice, then succeed" is one declaration rather
|
|
139
|
+
* than a sequence of control calls racing the schedule that is firing. Leaving
|
|
140
|
+
* `forDeliveries` out makes `behavior` permanent, which is what a single
|
|
141
|
+
* failure mode under test wants.
|
|
142
|
+
*
|
|
143
|
+
* The count is over deliveries this sink has *received*, not over a particular
|
|
144
|
+
* occurrence's attempts: a sink is a destination and does not get to know which
|
|
145
|
+
* occurrence the sender considers current.
|
|
146
|
+
*/
|
|
147
|
+
export declare const testSinkProgramSchema: z.ZodObject<{
|
|
148
|
+
behavior: z.ZodEnum<{
|
|
149
|
+
rate_limited: "rate_limited";
|
|
150
|
+
success: "success";
|
|
151
|
+
server_error: "server_error";
|
|
152
|
+
client_error: "client_error";
|
|
153
|
+
defer_abandon: "defer_abandon";
|
|
154
|
+
defer_fail: "defer_fail";
|
|
155
|
+
defer_succeed: "defer_succeed";
|
|
156
|
+
delay: "delay";
|
|
157
|
+
disconnect: "disconnect";
|
|
158
|
+
duplicate: "duplicate";
|
|
159
|
+
invalid_signature: "invalid_signature";
|
|
160
|
+
timeout: "timeout";
|
|
161
|
+
}>;
|
|
162
|
+
completionDelayMs: z.ZodOptional<z.ZodNumber>;
|
|
163
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
164
|
+
forDeliveries: z.ZodOptional<z.ZodNumber>;
|
|
165
|
+
retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
|
|
166
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
167
|
+
then: z.ZodOptional<z.ZodEnum<{
|
|
168
|
+
rate_limited: "rate_limited";
|
|
169
|
+
success: "success";
|
|
170
|
+
server_error: "server_error";
|
|
171
|
+
client_error: "client_error";
|
|
172
|
+
defer_abandon: "defer_abandon";
|
|
173
|
+
defer_fail: "defer_fail";
|
|
174
|
+
defer_succeed: "defer_succeed";
|
|
175
|
+
delay: "delay";
|
|
176
|
+
disconnect: "disconnect";
|
|
177
|
+
duplicate: "duplicate";
|
|
178
|
+
invalid_signature: "invalid_signature";
|
|
179
|
+
timeout: "timeout";
|
|
180
|
+
}>>;
|
|
181
|
+
}, z.core.$strip>;
|
|
182
|
+
export type TestSinkProgram = z.infer<typeof testSinkProgramSchema>;
|
|
183
|
+
/**
|
|
184
|
+
* What each behavior does, as data rather than prose, so the published artifact
|
|
185
|
+
* and the implementation cannot drift and a receiver author can read the whole
|
|
186
|
+
* simulation without running it.
|
|
187
|
+
*
|
|
188
|
+
* `senderOutcome` is stated in the vocabulary of `classifyOutcome`, which is
|
|
189
|
+
* what the delivery path actually applies. `receiverActed` is the honest answer
|
|
190
|
+
* to "may the receiver have processed this?" — the question a sender cannot
|
|
191
|
+
* answer for itself and must therefore record as ambiguity.
|
|
192
|
+
*
|
|
193
|
+
* `deferredReport` is present on the behaviors that acknowledge now and report
|
|
194
|
+
* later, and it is the reason `senderOutcome` cannot be derived from
|
|
195
|
+
* `defaultStatus` for those three: all three answer the same `202`, which
|
|
196
|
+
* `classifyStatus` reads as an ordinary success, and what the sender finally
|
|
197
|
+
* records comes from the report — or, for `never`, from the deadline passing
|
|
198
|
+
* with no report at all.
|
|
199
|
+
*/
|
|
200
|
+
export declare const TEST_SINK_BEHAVIOR_FACTS: {
|
|
201
|
+
readonly client_error: {
|
|
202
|
+
readonly defaultStatus: 422;
|
|
203
|
+
readonly deferredReport: undefined;
|
|
204
|
+
readonly receiverActed: false;
|
|
205
|
+
readonly senderOutcome: "terminal";
|
|
206
|
+
readonly summary: "Answers a selected 4xx. The sender records a customer refusal, and retries it only when the status is one the receiver may recover from — 401, 403 and 404, listed as `clientErrorStatusesRetried`. The default, 422, is not one of those, so it settles on the first attempt.";
|
|
207
|
+
};
|
|
208
|
+
readonly defer_abandon: {
|
|
209
|
+
readonly defaultStatus: 202;
|
|
210
|
+
readonly deferredReport: "never";
|
|
211
|
+
readonly receiverActed: true;
|
|
212
|
+
readonly senderOutcome: "ambiguous";
|
|
213
|
+
readonly summary: "Acknowledges the delivery and then never reports at all. The sender waits until the deadline in the envelope and records ambiguity, because a receiver that took the work and went quiet may well have done it. This is the case the completion deadline exists to bound.";
|
|
214
|
+
};
|
|
215
|
+
readonly defer_fail: {
|
|
216
|
+
readonly defaultStatus: 202;
|
|
217
|
+
readonly deferredReport: "failed";
|
|
218
|
+
readonly receiverActed: true;
|
|
219
|
+
readonly senderOutcome: "retryable";
|
|
220
|
+
readonly summary: "Acknowledges the delivery, then reports `failed` with the credential in its signed envelope. The occurrence goes back on the ordinary retry ladder — the same place a 500 puts it — which is how a receiver says its job died rather than that it vanished.";
|
|
221
|
+
};
|
|
222
|
+
readonly defer_succeed: {
|
|
223
|
+
readonly defaultStatus: 202;
|
|
224
|
+
readonly deferredReport: "succeeded";
|
|
225
|
+
readonly receiverActed: true;
|
|
226
|
+
readonly senderOutcome: "success";
|
|
227
|
+
readonly summary: "Acknowledges the delivery with 202 and the pending marker, keeps the work, then reports `succeeded` with the credential in its signed envelope. The whole deferred exchange, without writing a receiver.";
|
|
228
|
+
};
|
|
229
|
+
readonly delay: {
|
|
230
|
+
readonly defaultStatus: 200;
|
|
231
|
+
readonly deferredReport: undefined;
|
|
232
|
+
readonly receiverActed: true;
|
|
233
|
+
readonly senderOutcome: "success";
|
|
234
|
+
readonly summary: "Waits a bounded interval, then accepts. Exercises a slow receiver that still succeeds.";
|
|
235
|
+
};
|
|
236
|
+
readonly disconnect: {
|
|
237
|
+
readonly defaultStatus: undefined;
|
|
238
|
+
readonly deferredReport: undefined;
|
|
239
|
+
readonly receiverActed: false;
|
|
240
|
+
readonly senderOutcome: "ambiguous";
|
|
241
|
+
readonly summary: "Authenticates the delivery and then drops the connection without processing it, holding until the sender gives up. Nothing was claimed and nothing ran, but the sender cannot know that.";
|
|
242
|
+
};
|
|
243
|
+
readonly duplicate: {
|
|
244
|
+
readonly defaultStatus: 200;
|
|
245
|
+
readonly deferredReport: undefined;
|
|
246
|
+
readonly receiverActed: false;
|
|
247
|
+
readonly senderOutcome: "success";
|
|
248
|
+
readonly summary: "Accepts and reports the occurrence as already handled, without processing it again. What occurrence idempotency looks like from the sender's side.";
|
|
249
|
+
};
|
|
250
|
+
readonly invalid_signature: {
|
|
251
|
+
readonly defaultStatus: 401;
|
|
252
|
+
readonly deferredReport: undefined;
|
|
253
|
+
readonly receiverActed: false;
|
|
254
|
+
readonly senderOutcome: "retryable";
|
|
255
|
+
readonly summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The sender retries on the receiver's ordinary budget, because the usual cause is a credential the receiver is about to redeploy.";
|
|
256
|
+
};
|
|
257
|
+
readonly rate_limited: {
|
|
258
|
+
readonly defaultStatus: 429;
|
|
259
|
+
readonly deferredReport: undefined;
|
|
260
|
+
readonly receiverActed: false;
|
|
261
|
+
readonly senderOutcome: "retryable";
|
|
262
|
+
readonly summary: "Answers 429 with a bounded Retry-After. The sender honours the hint and does not spend its give-up budget.";
|
|
263
|
+
};
|
|
264
|
+
readonly server_error: {
|
|
265
|
+
readonly defaultStatus: 503;
|
|
266
|
+
readonly deferredReport: undefined;
|
|
267
|
+
readonly receiverActed: false;
|
|
268
|
+
readonly senderOutcome: "retryable";
|
|
269
|
+
readonly summary: "Answers a selected 5xx. The sender retries with backoff.";
|
|
270
|
+
};
|
|
271
|
+
readonly success: {
|
|
272
|
+
readonly defaultStatus: 200;
|
|
273
|
+
readonly deferredReport: undefined;
|
|
274
|
+
readonly receiverActed: true;
|
|
275
|
+
readonly senderOutcome: "success";
|
|
276
|
+
readonly summary: "Verifies, records, and accepts.";
|
|
277
|
+
};
|
|
278
|
+
readonly timeout: {
|
|
279
|
+
readonly defaultStatus: undefined;
|
|
280
|
+
readonly deferredReport: undefined;
|
|
281
|
+
readonly receiverActed: true;
|
|
282
|
+
readonly senderOutcome: "ambiguous";
|
|
283
|
+
readonly summary: "Verifies and records the delivery, then never answers. The receiver acted and the sender cannot know it.";
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* The exact body a deferring sink answers with.
|
|
288
|
+
*
|
|
289
|
+
* Built from the published discriminator rather than written out, so a sink
|
|
290
|
+
* cannot emit a marker the sender does not recognise. Deliberately without
|
|
291
|
+
* `completeBy`: the sink's report follows within `completionDelayMs`, and an
|
|
292
|
+
* acknowledgement that named an instant that close would be asking the sender
|
|
293
|
+
* to stop waiting at roughly the moment the report arrives — a race the
|
|
294
|
+
* rehearsal would lose intermittently and blame on the protocol. A receiver
|
|
295
|
+
* that wants to bring its deadline forward is exercising
|
|
296
|
+
* `effectiveCompletionDeadlineMs`, which is the delivery path's own test rather
|
|
297
|
+
* than this simulation's.
|
|
298
|
+
*/
|
|
299
|
+
export declare const TEST_SINK_PENDING_ACKNOWLEDGEMENT: string;
|
|
300
|
+
/**
|
|
301
|
+
* What the sink should do with one delivery.
|
|
302
|
+
*
|
|
303
|
+
* `hold` means no response is ever produced.
|
|
304
|
+
*
|
|
305
|
+
* `claims` says whether the receiver's idempotency ledgers are the durable
|
|
306
|
+
* ones. It is false only for `disconnect`, which models a connection that died
|
|
307
|
+
* before the receiver processed anything: the delivery is still authenticated —
|
|
308
|
+
* a sink that held an *unsigned* request open would be a way for anyone holding
|
|
309
|
+
* a sink url to pin the Worker's request budget, not a simulation of a customer
|
|
310
|
+
* receiver — but no occurrence is claimed and no work is recorded as done, so
|
|
311
|
+
* `receiverActed: false` stays true.
|
|
312
|
+
*/
|
|
313
|
+
export interface TestSinkAction {
|
|
314
|
+
/**
|
|
315
|
+
* The exact acknowledgement bytes a deferral answers with, present exactly
|
|
316
|
+
* when `defers` is true.
|
|
317
|
+
*
|
|
318
|
+
* Carried here rather than composed by the runtime because these bytes are
|
|
319
|
+
* the protocol: a sink that assembled its own marker could assemble one the
|
|
320
|
+
* sender does not recognise, and the failure would be a delivery quietly
|
|
321
|
+
* settled `succeeded` while the receiver was still working — the one thing
|
|
322
|
+
* the whole deferral protocol exists to prevent.
|
|
323
|
+
*/
|
|
324
|
+
readonly acknowledgement?: string;
|
|
325
|
+
readonly behavior: TestSinkBehavior;
|
|
326
|
+
/** Whether the occurrence and delivery ledgers this delivery touches persist. */
|
|
327
|
+
readonly claims: boolean;
|
|
328
|
+
/**
|
|
329
|
+
* Milliseconds between the acknowledgement and the report. Zero when there
|
|
330
|
+
* is no report to send, the way `delayMs` is zero when there is no delay.
|
|
331
|
+
*/
|
|
332
|
+
readonly completionDelayMs: number;
|
|
333
|
+
/**
|
|
334
|
+
* What to report, with the credential in the delivery's signed envelope.
|
|
335
|
+
*
|
|
336
|
+
* Absent means "report nothing", which is `defer_abandon` and every behavior
|
|
337
|
+
* that does not defer. The two are told apart by `defers`, never by this.
|
|
338
|
+
*/
|
|
339
|
+
readonly completionOutcome?: CallbackCompletionOutcome;
|
|
340
|
+
/** Whether to acknowledge the work and answer later instead of now. */
|
|
341
|
+
readonly defers: boolean;
|
|
342
|
+
readonly delayMs: number;
|
|
343
|
+
readonly hold: boolean;
|
|
344
|
+
readonly retryAfterSeconds?: number;
|
|
345
|
+
readonly status?: number;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Resolve a program against how many deliveries this sink has already taken.
|
|
349
|
+
*
|
|
350
|
+
* Pure and total: the same `(program, deliveryIndex)` always produces the same
|
|
351
|
+
* action, which is what "deterministic" means here and what lets the same
|
|
352
|
+
* engine drive both the hosted sink and an offline conformance run.
|
|
353
|
+
*
|
|
354
|
+
* `deliveryIndex` is zero-based and counts every delivery the sink accepted for
|
|
355
|
+
* processing, including refused ones — a receiver that answers 401 has still
|
|
356
|
+
* received a request, and a program that said "fail the first two" must not be
|
|
357
|
+
* satisfiable by failing the same delivery twice.
|
|
358
|
+
*/
|
|
359
|
+
export declare function resolveTestSinkAction(program: TestSinkProgram, deliveryIndex: number): TestSinkAction;
|
|
360
|
+
/**
|
|
361
|
+
* Every way a receiver can dispose of one delivery.
|
|
362
|
+
*
|
|
363
|
+
* `duplicate` and `replay` are both "already handled" and both succeed, and
|
|
364
|
+
* separating them is what makes an integrator's logs legible: `replay` is the
|
|
365
|
+
* same delivery identity arriving twice — a duplicated request — while
|
|
366
|
+
* `duplicate` is a *different* delivery for an occurrence already processed,
|
|
367
|
+
* which is what a retry after an ambiguous attempt, and what a customer-ordered
|
|
368
|
+
* redelivery, both look like on the wire.
|
|
369
|
+
*
|
|
370
|
+
* `in_flight` is the concurrent case: another worker holds the occurrence and
|
|
371
|
+
* has not finished. Answering success there would claim an outcome nobody has
|
|
372
|
+
* yet produced.
|
|
373
|
+
*/
|
|
374
|
+
export declare const CALLBACK_RECEIVER_DISPOSITIONS: readonly ["body_too_large", "duplicate", "handler_failed", "in_flight", "invalid_envelope", "invalid_headers", "invalid_signature", "processed", "replay", "stale_timestamp"];
|
|
375
|
+
export declare const callbackReceiverDispositionSchema: z.ZodEnum<{
|
|
376
|
+
duplicate: "duplicate";
|
|
377
|
+
invalid_signature: "invalid_signature";
|
|
378
|
+
body_too_large: "body_too_large";
|
|
379
|
+
handler_failed: "handler_failed";
|
|
380
|
+
in_flight: "in_flight";
|
|
381
|
+
invalid_envelope: "invalid_envelope";
|
|
382
|
+
invalid_headers: "invalid_headers";
|
|
383
|
+
processed: "processed";
|
|
384
|
+
replay: "replay";
|
|
385
|
+
stale_timestamp: "stale_timestamp";
|
|
386
|
+
}>;
|
|
387
|
+
export type CallbackReceiverDisposition = z.infer<typeof callbackReceiverDispositionSchema>;
|
|
388
|
+
/**
|
|
389
|
+
* What a sink recorded, which is one value wider than what a receiver can
|
|
390
|
+
* answer with.
|
|
391
|
+
*
|
|
392
|
+
* `connection_dropped` has no status because the point of it is that there was
|
|
393
|
+
* none: the sink modelled a connection that died before the receiver read
|
|
394
|
+
* anything, so nothing was authenticated and nothing ran. Widening
|
|
395
|
+
* `CALLBACK_RECEIVER_DISPOSITIONS` to hold it would break the property that
|
|
396
|
+
* makes that list useful — every member maps to exactly one status.
|
|
397
|
+
*/
|
|
398
|
+
export declare const TEST_SINK_DISPOSITIONS: readonly ["body_too_large", "duplicate", "handler_failed", "in_flight", "invalid_envelope", "invalid_headers", "invalid_signature", "processed", "replay", "stale_timestamp", "connection_dropped"];
|
|
399
|
+
export declare const testSinkDispositionSchema: z.ZodEnum<{
|
|
400
|
+
duplicate: "duplicate";
|
|
401
|
+
invalid_signature: "invalid_signature";
|
|
402
|
+
body_too_large: "body_too_large";
|
|
403
|
+
handler_failed: "handler_failed";
|
|
404
|
+
in_flight: "in_flight";
|
|
405
|
+
invalid_envelope: "invalid_envelope";
|
|
406
|
+
invalid_headers: "invalid_headers";
|
|
407
|
+
processed: "processed";
|
|
408
|
+
replay: "replay";
|
|
409
|
+
stale_timestamp: "stale_timestamp";
|
|
410
|
+
connection_dropped: "connection_dropped";
|
|
411
|
+
}>;
|
|
412
|
+
export type TestSinkDisposition = z.infer<typeof testSinkDispositionSchema>;
|
|
413
|
+
/**
|
|
414
|
+
* The HTTP status a conforming receiver returns for each disposition.
|
|
415
|
+
*
|
|
416
|
+
* Published, and published *here*, because the sender classifies on the status
|
|
417
|
+
* and the classification decides whether an occurrence retries, settles, or is
|
|
418
|
+
* abandoned. A receiver that picks its own numbers can be perfectly correct
|
|
419
|
+
* about what it did and still tell the sender the opposite.
|
|
420
|
+
*
|
|
421
|
+
* Each choice is answerable against `classifyStatus`:
|
|
422
|
+
*
|
|
423
|
+
* - `processed`, `duplicate`, `replay` — 200. All three mean the occurrence is
|
|
424
|
+
* handled. A replayed or duplicated delivery answering 4xx would make the
|
|
425
|
+
* sender record a terminal failure for work that succeeded, which is the
|
|
426
|
+
* single most damaging thing a receiver can do.
|
|
427
|
+
* - `stale_timestamp` — 408, which is retryable. A clock skew wide enough to
|
|
428
|
+
* fail tolerance is an operator problem no retry fixes, but a request held on
|
|
429
|
+
* the network for six minutes is not, and every attempt re-signs with a fresh
|
|
430
|
+
* timestamp, so the transient case self-heals and the permanent case exhausts
|
|
431
|
+
* the attempt bound honestly.
|
|
432
|
+
* - `invalid_signature`, `invalid_headers` — 401, **retryable since
|
|
433
|
+
* FXO-1596**, and this entry used to say the opposite. The reasoning it
|
|
434
|
+
* replaces was that retrying with the same key cannot succeed and a retryable
|
|
435
|
+
* answer would spend the give-up budget hiding a misconfiguration. That is
|
|
436
|
+
* true of a mismatch in *our* signing and false of the population that
|
|
437
|
+
* actually reaches a 401: a receiver holding a credential it has rotated,
|
|
438
|
+
* expired, or not yet deployed. Those recover on the receiver's next release,
|
|
439
|
+
* and the delivery after it carries a fresh signature. The misconfiguration
|
|
440
|
+
* is not hidden either — five identical 401s settle the occurrence 82 seconds
|
|
441
|
+
* later with the same `inspect_receiver` advice, five rows instead of one.
|
|
442
|
+
* - `invalid_envelope` — 400, terminal. The bytes authenticated and did not
|
|
443
|
+
* parse, so the sender and receiver disagree about the contract.
|
|
444
|
+
* - `body_too_large` — 413, terminal, and matched to the `payload_too_large`
|
|
445
|
+
* problem code the API surface already publishes.
|
|
446
|
+
* - `in_flight` — 503, retryable. Honest: someone else holds the occurrence.
|
|
447
|
+
* - `handler_failed` — 500, retryable. The receiver's own fault and worth
|
|
448
|
+
* another attempt.
|
|
449
|
+
*/
|
|
450
|
+
export declare const CALLBACK_RECEIVER_STATUS: {
|
|
451
|
+
readonly body_too_large: 413;
|
|
452
|
+
readonly duplicate: 200;
|
|
453
|
+
readonly handler_failed: 500;
|
|
454
|
+
readonly in_flight: 503;
|
|
455
|
+
readonly invalid_envelope: 400;
|
|
456
|
+
readonly invalid_headers: 401;
|
|
457
|
+
readonly invalid_signature: 401;
|
|
458
|
+
readonly processed: 200;
|
|
459
|
+
readonly replay: 200;
|
|
460
|
+
readonly stale_timestamp: 408;
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* The response body a conforming receiver returns.
|
|
464
|
+
*
|
|
465
|
+
* Deliberately tiny and deliberately free of prose: a receiver's response is
|
|
466
|
+
* read by a machine, and a body that carries an exception message is how a
|
|
467
|
+
* receiver leaks its internals to whoever can provoke an error.
|
|
468
|
+
*/
|
|
469
|
+
export declare const callbackReceiverAcknowledgementSchema: z.ZodObject<{
|
|
470
|
+
disposition: z.ZodEnum<{
|
|
471
|
+
duplicate: "duplicate";
|
|
472
|
+
invalid_signature: "invalid_signature";
|
|
473
|
+
body_too_large: "body_too_large";
|
|
474
|
+
handler_failed: "handler_failed";
|
|
475
|
+
in_flight: "in_flight";
|
|
476
|
+
invalid_envelope: "invalid_envelope";
|
|
477
|
+
invalid_headers: "invalid_headers";
|
|
478
|
+
processed: "processed";
|
|
479
|
+
replay: "replay";
|
|
480
|
+
stale_timestamp: "stale_timestamp";
|
|
481
|
+
}>;
|
|
482
|
+
occurrenceId: z.ZodOptional<z.ZodString>;
|
|
483
|
+
}, z.core.$strip>;
|
|
484
|
+
export type CallbackReceiverAcknowledgement = z.infer<typeof callbackReceiverAcknowledgementSchema>;
|
|
485
|
+
export declare const testSinkSchema: z.ZodObject<{
|
|
486
|
+
createdAt: z.ZodString;
|
|
487
|
+
deliveryCount: z.ZodNumber;
|
|
488
|
+
endpoint: z.ZodString;
|
|
489
|
+
environmentKind: z.ZodLiteral<"test">;
|
|
490
|
+
id: z.ZodString;
|
|
491
|
+
program: z.ZodObject<{
|
|
492
|
+
behavior: z.ZodEnum<{
|
|
493
|
+
rate_limited: "rate_limited";
|
|
494
|
+
success: "success";
|
|
495
|
+
server_error: "server_error";
|
|
496
|
+
client_error: "client_error";
|
|
497
|
+
defer_abandon: "defer_abandon";
|
|
498
|
+
defer_fail: "defer_fail";
|
|
499
|
+
defer_succeed: "defer_succeed";
|
|
500
|
+
delay: "delay";
|
|
501
|
+
disconnect: "disconnect";
|
|
502
|
+
duplicate: "duplicate";
|
|
503
|
+
invalid_signature: "invalid_signature";
|
|
504
|
+
timeout: "timeout";
|
|
505
|
+
}>;
|
|
506
|
+
completionDelayMs: z.ZodOptional<z.ZodNumber>;
|
|
507
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
508
|
+
forDeliveries: z.ZodOptional<z.ZodNumber>;
|
|
509
|
+
retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
|
|
510
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
511
|
+
then: z.ZodOptional<z.ZodEnum<{
|
|
512
|
+
rate_limited: "rate_limited";
|
|
513
|
+
success: "success";
|
|
514
|
+
server_error: "server_error";
|
|
515
|
+
client_error: "client_error";
|
|
516
|
+
defer_abandon: "defer_abandon";
|
|
517
|
+
defer_fail: "defer_fail";
|
|
518
|
+
defer_succeed: "defer_succeed";
|
|
519
|
+
delay: "delay";
|
|
520
|
+
disconnect: "disconnect";
|
|
521
|
+
duplicate: "duplicate";
|
|
522
|
+
invalid_signature: "invalid_signature";
|
|
523
|
+
timeout: "timeout";
|
|
524
|
+
}>>;
|
|
525
|
+
}, z.core.$strip>;
|
|
526
|
+
project: z.ZodString;
|
|
527
|
+
updatedAt: z.ZodString;
|
|
528
|
+
url: z.ZodURL;
|
|
529
|
+
}, z.core.$strip>;
|
|
530
|
+
export type TestSink = z.infer<typeof testSinkSchema>;
|
|
531
|
+
export declare const testSinkPageSchema: z.ZodObject<{
|
|
532
|
+
data: z.ZodArray<z.ZodObject<{
|
|
533
|
+
createdAt: z.ZodString;
|
|
534
|
+
deliveryCount: z.ZodNumber;
|
|
535
|
+
endpoint: z.ZodString;
|
|
536
|
+
environmentKind: z.ZodLiteral<"test">;
|
|
537
|
+
id: z.ZodString;
|
|
538
|
+
program: z.ZodObject<{
|
|
539
|
+
behavior: z.ZodEnum<{
|
|
540
|
+
rate_limited: "rate_limited";
|
|
541
|
+
success: "success";
|
|
542
|
+
server_error: "server_error";
|
|
543
|
+
client_error: "client_error";
|
|
544
|
+
defer_abandon: "defer_abandon";
|
|
545
|
+
defer_fail: "defer_fail";
|
|
546
|
+
defer_succeed: "defer_succeed";
|
|
547
|
+
delay: "delay";
|
|
548
|
+
disconnect: "disconnect";
|
|
549
|
+
duplicate: "duplicate";
|
|
550
|
+
invalid_signature: "invalid_signature";
|
|
551
|
+
timeout: "timeout";
|
|
552
|
+
}>;
|
|
553
|
+
completionDelayMs: z.ZodOptional<z.ZodNumber>;
|
|
554
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
555
|
+
forDeliveries: z.ZodOptional<z.ZodNumber>;
|
|
556
|
+
retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
|
|
557
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
558
|
+
then: z.ZodOptional<z.ZodEnum<{
|
|
559
|
+
rate_limited: "rate_limited";
|
|
560
|
+
success: "success";
|
|
561
|
+
server_error: "server_error";
|
|
562
|
+
client_error: "client_error";
|
|
563
|
+
defer_abandon: "defer_abandon";
|
|
564
|
+
defer_fail: "defer_fail";
|
|
565
|
+
defer_succeed: "defer_succeed";
|
|
566
|
+
delay: "delay";
|
|
567
|
+
disconnect: "disconnect";
|
|
568
|
+
duplicate: "duplicate";
|
|
569
|
+
invalid_signature: "invalid_signature";
|
|
570
|
+
timeout: "timeout";
|
|
571
|
+
}>>;
|
|
572
|
+
}, z.core.$strip>;
|
|
573
|
+
project: z.ZodString;
|
|
574
|
+
updatedAt: z.ZodString;
|
|
575
|
+
url: z.ZodURL;
|
|
576
|
+
}, z.core.$strip>>;
|
|
577
|
+
freshness: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
578
|
+
kind: z.ZodLiteral<"authoritative">;
|
|
579
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
580
|
+
asOf: z.ZodString;
|
|
581
|
+
kind: z.ZodLiteral<"projected">;
|
|
582
|
+
lagSeconds: z.ZodNumber;
|
|
583
|
+
}, z.core.$strip>], "kind">;
|
|
584
|
+
nextCursor: z.ZodOptional<z.ZodString>;
|
|
585
|
+
}, z.core.$strip>;
|
|
586
|
+
export type TestSinkPage = z.infer<typeof testSinkPageSchema>;
|
|
587
|
+
export declare const testSinkCreateRequestSchema: z.ZodObject<{
|
|
588
|
+
endpoint: z.ZodString;
|
|
589
|
+
program: z.ZodOptional<z.ZodObject<{
|
|
590
|
+
behavior: z.ZodEnum<{
|
|
591
|
+
rate_limited: "rate_limited";
|
|
592
|
+
success: "success";
|
|
593
|
+
server_error: "server_error";
|
|
594
|
+
client_error: "client_error";
|
|
595
|
+
defer_abandon: "defer_abandon";
|
|
596
|
+
defer_fail: "defer_fail";
|
|
597
|
+
defer_succeed: "defer_succeed";
|
|
598
|
+
delay: "delay";
|
|
599
|
+
disconnect: "disconnect";
|
|
600
|
+
duplicate: "duplicate";
|
|
601
|
+
invalid_signature: "invalid_signature";
|
|
602
|
+
timeout: "timeout";
|
|
603
|
+
}>;
|
|
604
|
+
completionDelayMs: z.ZodOptional<z.ZodNumber>;
|
|
605
|
+
delayMs: z.ZodOptional<z.ZodNumber>;
|
|
606
|
+
forDeliveries: z.ZodOptional<z.ZodNumber>;
|
|
607
|
+
retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
|
|
608
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
609
|
+
then: z.ZodOptional<z.ZodEnum<{
|
|
610
|
+
rate_limited: "rate_limited";
|
|
611
|
+
success: "success";
|
|
612
|
+
server_error: "server_error";
|
|
613
|
+
client_error: "client_error";
|
|
614
|
+
defer_abandon: "defer_abandon";
|
|
615
|
+
defer_fail: "defer_fail";
|
|
616
|
+
defer_succeed: "defer_succeed";
|
|
617
|
+
delay: "delay";
|
|
618
|
+
disconnect: "disconnect";
|
|
619
|
+
duplicate: "duplicate";
|
|
620
|
+
invalid_signature: "invalid_signature";
|
|
621
|
+
timeout: "timeout";
|
|
622
|
+
}>>;
|
|
623
|
+
}, z.core.$strip>>;
|
|
624
|
+
}, z.core.$strip>;
|
|
625
|
+
export type TestSinkCreateRequest = z.infer<typeof testSinkCreateRequestSchema>;
|
|
626
|
+
/**
|
|
627
|
+
* One received delivery, as the sink recorded it.
|
|
628
|
+
*
|
|
629
|
+
* The identifiers, the key that signed it, and the disposition are the whole of
|
|
630
|
+
* what a *list* returns. The exact body is available only from the single-record
|
|
631
|
+
* read, capped, and only in a test environment — see `testSinkDeliveryBodySchema`.
|
|
632
|
+
*/
|
|
633
|
+
export declare const testSinkDeliverySchema: z.ZodObject<{
|
|
634
|
+
behavior: z.ZodEnum<{
|
|
635
|
+
rate_limited: "rate_limited";
|
|
636
|
+
success: "success";
|
|
637
|
+
server_error: "server_error";
|
|
638
|
+
client_error: "client_error";
|
|
639
|
+
defer_abandon: "defer_abandon";
|
|
640
|
+
defer_fail: "defer_fail";
|
|
641
|
+
defer_succeed: "defer_succeed";
|
|
642
|
+
delay: "delay";
|
|
643
|
+
disconnect: "disconnect";
|
|
644
|
+
duplicate: "duplicate";
|
|
645
|
+
invalid_signature: "invalid_signature";
|
|
646
|
+
timeout: "timeout";
|
|
647
|
+
}>;
|
|
648
|
+
disposition: z.ZodEnum<{
|
|
649
|
+
duplicate: "duplicate";
|
|
650
|
+
invalid_signature: "invalid_signature";
|
|
651
|
+
body_too_large: "body_too_large";
|
|
652
|
+
handler_failed: "handler_failed";
|
|
653
|
+
in_flight: "in_flight";
|
|
654
|
+
invalid_envelope: "invalid_envelope";
|
|
655
|
+
invalid_headers: "invalid_headers";
|
|
656
|
+
processed: "processed";
|
|
657
|
+
replay: "replay";
|
|
658
|
+
stale_timestamp: "stale_timestamp";
|
|
659
|
+
connection_dropped: "connection_dropped";
|
|
660
|
+
}>;
|
|
661
|
+
delivery: z.ZodString;
|
|
662
|
+
id: z.ZodString;
|
|
663
|
+
keyId: z.ZodOptional<z.ZodString>;
|
|
664
|
+
occurrenceId: z.ZodOptional<z.ZodString>;
|
|
665
|
+
receivedAt: z.ZodString;
|
|
666
|
+
respondedWith: z.ZodOptional<z.ZodNumber>;
|
|
667
|
+
scheduleId: z.ZodOptional<z.ZodString>;
|
|
668
|
+
sink: z.ZodString;
|
|
669
|
+
}, z.core.$strip>;
|
|
670
|
+
export type TestSinkDelivery = z.infer<typeof testSinkDeliverySchema>;
|
|
671
|
+
export declare const testSinkDeliveryPageSchema: z.ZodObject<{
|
|
672
|
+
data: z.ZodArray<z.ZodObject<{
|
|
673
|
+
behavior: z.ZodEnum<{
|
|
674
|
+
rate_limited: "rate_limited";
|
|
675
|
+
success: "success";
|
|
676
|
+
server_error: "server_error";
|
|
677
|
+
client_error: "client_error";
|
|
678
|
+
defer_abandon: "defer_abandon";
|
|
679
|
+
defer_fail: "defer_fail";
|
|
680
|
+
defer_succeed: "defer_succeed";
|
|
681
|
+
delay: "delay";
|
|
682
|
+
disconnect: "disconnect";
|
|
683
|
+
duplicate: "duplicate";
|
|
684
|
+
invalid_signature: "invalid_signature";
|
|
685
|
+
timeout: "timeout";
|
|
686
|
+
}>;
|
|
687
|
+
disposition: z.ZodEnum<{
|
|
688
|
+
duplicate: "duplicate";
|
|
689
|
+
invalid_signature: "invalid_signature";
|
|
690
|
+
body_too_large: "body_too_large";
|
|
691
|
+
handler_failed: "handler_failed";
|
|
692
|
+
in_flight: "in_flight";
|
|
693
|
+
invalid_envelope: "invalid_envelope";
|
|
694
|
+
invalid_headers: "invalid_headers";
|
|
695
|
+
processed: "processed";
|
|
696
|
+
replay: "replay";
|
|
697
|
+
stale_timestamp: "stale_timestamp";
|
|
698
|
+
connection_dropped: "connection_dropped";
|
|
699
|
+
}>;
|
|
700
|
+
delivery: z.ZodString;
|
|
701
|
+
id: z.ZodString;
|
|
702
|
+
keyId: z.ZodOptional<z.ZodString>;
|
|
703
|
+
occurrenceId: z.ZodOptional<z.ZodString>;
|
|
704
|
+
receivedAt: z.ZodString;
|
|
705
|
+
respondedWith: z.ZodOptional<z.ZodNumber>;
|
|
706
|
+
scheduleId: z.ZodOptional<z.ZodString>;
|
|
707
|
+
sink: z.ZodString;
|
|
708
|
+
}, z.core.$strip>>;
|
|
709
|
+
freshness: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
710
|
+
kind: z.ZodLiteral<"authoritative">;
|
|
711
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
712
|
+
asOf: z.ZodString;
|
|
713
|
+
kind: z.ZodLiteral<"projected">;
|
|
714
|
+
lagSeconds: z.ZodNumber;
|
|
715
|
+
}, z.core.$strip>], "kind">;
|
|
716
|
+
nextCursor: z.ZodOptional<z.ZodString>;
|
|
717
|
+
}, z.core.$strip>;
|
|
718
|
+
export type TestSinkDeliveryPage = z.infer<typeof testSinkDeliveryPageSchema>;
|
|
719
|
+
/**
|
|
720
|
+
* One received delivery including the bytes that arrived.
|
|
721
|
+
*
|
|
722
|
+
* **The one deliberate exception to "callback bodies never leave the delivery
|
|
723
|
+
* path".** The rule exists because a payload is customer content and a log,
|
|
724
|
+
* metric, or support index is the wrong place for it. A test sink is none of
|
|
725
|
+
* those: it is a destination the customer created, in their own test
|
|
726
|
+
* environment, to look at what their receiver would have been sent, and
|
|
727
|
+
* withholding it would leave the sink unable to answer the first question
|
|
728
|
+
* anybody asks it.
|
|
729
|
+
*
|
|
730
|
+
* The exception is kept narrow rather than assumed safe. The body is available
|
|
731
|
+
* only from the single-delivery read and never from a listing, only under the
|
|
732
|
+
* creating authority, only in test, capped at
|
|
733
|
+
* `TEST_SINK_BOUNDS.maxRetainedBodyBytes` with the truncation stated, and never
|
|
734
|
+
* written to a log. `signature` is included because a MAC is not secret and
|
|
735
|
+
* comparing it against a locally computed one is how a receiver author finds a
|
|
736
|
+
* body-mutation bug; no signing key has a field anywhere in this file.
|
|
737
|
+
*/
|
|
738
|
+
export declare const testSinkDeliveryBodySchema: z.ZodObject<{
|
|
739
|
+
behavior: z.ZodEnum<{
|
|
740
|
+
rate_limited: "rate_limited";
|
|
741
|
+
success: "success";
|
|
742
|
+
server_error: "server_error";
|
|
743
|
+
client_error: "client_error";
|
|
744
|
+
defer_abandon: "defer_abandon";
|
|
745
|
+
defer_fail: "defer_fail";
|
|
746
|
+
defer_succeed: "defer_succeed";
|
|
747
|
+
delay: "delay";
|
|
748
|
+
disconnect: "disconnect";
|
|
749
|
+
duplicate: "duplicate";
|
|
750
|
+
invalid_signature: "invalid_signature";
|
|
751
|
+
timeout: "timeout";
|
|
752
|
+
}>;
|
|
753
|
+
disposition: z.ZodEnum<{
|
|
754
|
+
duplicate: "duplicate";
|
|
755
|
+
invalid_signature: "invalid_signature";
|
|
756
|
+
body_too_large: "body_too_large";
|
|
757
|
+
handler_failed: "handler_failed";
|
|
758
|
+
in_flight: "in_flight";
|
|
759
|
+
invalid_envelope: "invalid_envelope";
|
|
760
|
+
invalid_headers: "invalid_headers";
|
|
761
|
+
processed: "processed";
|
|
762
|
+
replay: "replay";
|
|
763
|
+
stale_timestamp: "stale_timestamp";
|
|
764
|
+
connection_dropped: "connection_dropped";
|
|
765
|
+
}>;
|
|
766
|
+
delivery: z.ZodString;
|
|
767
|
+
id: z.ZodString;
|
|
768
|
+
keyId: z.ZodOptional<z.ZodString>;
|
|
769
|
+
occurrenceId: z.ZodOptional<z.ZodString>;
|
|
770
|
+
receivedAt: z.ZodString;
|
|
771
|
+
respondedWith: z.ZodOptional<z.ZodNumber>;
|
|
772
|
+
scheduleId: z.ZodOptional<z.ZodString>;
|
|
773
|
+
sink: z.ZodString;
|
|
774
|
+
body: z.ZodString;
|
|
775
|
+
bodyTruncated: z.ZodBoolean;
|
|
776
|
+
receivedBytes: z.ZodNumber;
|
|
777
|
+
signature: z.ZodOptional<z.ZodString>;
|
|
778
|
+
}, z.core.$strip>;
|
|
779
|
+
export type TestSinkDeliveryBody = z.infer<typeof testSinkDeliveryBodySchema>;
|
|
780
|
+
/**
|
|
781
|
+
* Dated, and moved on 2026-08-23 because the sink gained the three deferring
|
|
782
|
+
* behaviors and the bound on how long one may wait before reporting. A reader
|
|
783
|
+
* holding the older artifact has a complete and correct description of a sink
|
|
784
|
+
* that could not defer.
|
|
785
|
+
*/
|
|
786
|
+
export declare const TEST_SINK_PROTOCOL_VERSION = "2026-08-23";
|
|
787
|
+
/**
|
|
788
|
+
* The language-neutral receiver contract.
|
|
789
|
+
*
|
|
790
|
+
* A receiver written in any stack can be held to this: verify these bytes,
|
|
791
|
+
* answer these statuses, dedupe on this identity. It is generated beside
|
|
792
|
+
* OpenAPI so an integrator does not have to read TypeScript to write a Go
|
|
793
|
+
* receiver.
|
|
794
|
+
*/
|
|
795
|
+
export declare const CALLBACK_RECEIVER_PROTOCOL: {
|
|
796
|
+
readonly idempotency: {
|
|
797
|
+
/**
|
|
798
|
+
* Stated plainly because it is the property most often got wrong, and the
|
|
799
|
+
* one the contract vocabulary already implies without saying so anywhere a
|
|
800
|
+
* receiver author would look.
|
|
801
|
+
*/
|
|
802
|
+
readonly occurrence: {
|
|
803
|
+
readonly identity: "Callback-Occurrence-Id";
|
|
804
|
+
readonly note: "Every automatic delivery of one occurrence — the first attempt and every retry the system chose — is at most one execution. A receiver claims the occurrence atomically and answers 200 for every later automatic delivery of it. This is what makes at-least-once delivery safe; a receiver that needs a second execution asks for a second occurrence, or a person orders a redelivery.";
|
|
805
|
+
readonly requirement: "claim the occurrence atomically before running the handler; release the claim if the handler fails so a later attempt can retry it";
|
|
806
|
+
};
|
|
807
|
+
/**
|
|
808
|
+
* The one case where an occurrence runs twice, and the reason it is safe.
|
|
809
|
+
*
|
|
810
|
+
* Until the envelope carried `trigger` this could not be expressed: a retry
|
|
811
|
+
* nobody asked for and a redelivery a person ordered arrived identically, so
|
|
812
|
+
* the rule safe against the first was applied to both and pressing
|
|
813
|
+
* "redeliver" changed nothing. The unit of idempotency is therefore not the
|
|
814
|
+
* occurrence alone but the occurrence *and what asked for it*.
|
|
815
|
+
*/
|
|
816
|
+
readonly redelivery: {
|
|
817
|
+
readonly identity: "Callback-Occurrence-Id together with the signed attempt number, which is the redelivery's ordinal";
|
|
818
|
+
readonly note: "A redelivery is a deliberate, authorized instruction to do the work again. Suppressing it as a duplicate answers a person with silence, which is what a customer pressing redeliver after fixing their endpoint experiences as the attempt succeeding while nothing happens.";
|
|
819
|
+
readonly optOut: "A receiver that genuinely requires strict once-only per occurrence chooses it explicitly and keeps answering duplicate. It is an opt-out, not the default, because a default of suppression makes the product lie to the person who pressed the button.";
|
|
820
|
+
readonly ordering: "attempt only ever increases within an occurrence and every copy of one redelivery carries the same value, so a receiver keeps the highest redelivery it has run to completion and refuses anything at or below it. That is what recognises a copy delayed in a proxy, a queue or an abandoned connection, however late it arrives and however many redeliveries have completed since. Remembering only the most recent instruction cannot do this and will run a customer's work twice; remembering every instruction can, at the cost of a store that grows without bound. sourceAttemptId names the attempt the person chose to follow and is there to show them, not to decide whether the work runs.";
|
|
821
|
+
readonly requirement: "run the handler again for a redelivery whose attempt is above the highest this receiver has run to completion for the occurrence, and suppress every redelivery at or below that high-water mark";
|
|
822
|
+
readonly strictness: "A receiver that chose strict once-only suppresses every delivery for an occurrence once anything has finished, whatever caused it. Reading strictness as a rule about redeliveries alone leaves a hole: a strict receiver that processed a redelivery first would then admit a delayed automatic delivery and run a payment twice.";
|
|
823
|
+
readonly warranty: "Each accepted redelivery is one logical run. Repeats are suppressed once the receiver has durably completed that run — not from the moment it starts one. A handler that performs its external effect and then dies before its claim is settled will be run again when the lease expires, which is the honest limit of what any at-least-once receiver can promise. A customer whose effect must happen exactly once commits the occurrence and ordinal alongside their own state, or passes them downstream as an idempotency key.";
|
|
824
|
+
};
|
|
825
|
+
readonly replay: {
|
|
826
|
+
readonly identity: "Callback-Delivery-Id";
|
|
827
|
+
readonly requirement: "reject a previously accepted delivery identity before parsing, and answer 200 — a duplicated request is not a failure";
|
|
828
|
+
};
|
|
829
|
+
};
|
|
830
|
+
/**
|
|
831
|
+
* How a receiver's occurrence store answers one claim, in order.
|
|
832
|
+
*
|
|
833
|
+
* Published as data because a receiver author outside JavaScript had nowhere
|
|
834
|
+
* to read it: `order` covers authenticating a request, and the prose guide is
|
|
835
|
+
* TypeScript-shaped and not machine-readable. It drifted from the code three
|
|
836
|
+
* times during W3-08 — and the third time it published a rule that would have
|
|
837
|
+
* had a customer take a payment twice — so the ordered list now lives beside
|
|
838
|
+
* the rules it describes and the guide is checked against it.
|
|
839
|
+
*
|
|
840
|
+
* Rule 2 is deliberately about what has *finished* rather than what *arrived*.
|
|
841
|
+
* Splitting it — suppress an automatic delivery when the automatic run
|
|
842
|
+
* finished, apply strictness only to redeliveries — is the hole: a strict
|
|
843
|
+
* receiver that legitimately saw an ordered redelivery first would then admit
|
|
844
|
+
* a delayed automatic copy and run the work again.
|
|
845
|
+
*/
|
|
846
|
+
readonly claimRules: readonly ["another delivery holds an unexpired lease, whatever the run, so an ordered redelivery never runs alongside the attempt it follows -> in_flight", "the delivery may not retake a settled occurrence and anything at all has finished for it -> settled. This covers every automatic delivery and every delivery at a receiver that chose strict once-only", "the delivery is a redelivery whose ordinal is at or below the highest this receiver has run to completion -> settled", "otherwise take it, recording the holder and the run -> claimed"];
|
|
847
|
+
readonly order: readonly ["read the exact request bytes before any body parser sees them", "reject a body over the receiver's byte bound without buffering it", "validate the five Callback-* headers and the content type", "reject a timestamp outside tolerance", "select the signing key by Callback-Key-Id within the receiver's own endpoint, project, and environment", "compare the HMAC in constant time over <timestamp>.<delivery-id>. plus the exact bytes", "compare the canonical envelope identifiers with the occurrence header", "claim the delivery identity atomically", "only now parse the body, and read the trigger that says why this delivery exists", "claim the occurrence identity atomically, for the run the signed body names — the occurrence for an automatic delivery, and the occurrence with the redelivery's attempt ordinal for one a person ordered", "run the application handler"];
|
|
848
|
+
readonly responses: {
|
|
849
|
+
readonly body_too_large: 413;
|
|
850
|
+
readonly duplicate: 200;
|
|
851
|
+
readonly handler_failed: 500;
|
|
852
|
+
readonly in_flight: 503;
|
|
853
|
+
readonly invalid_envelope: 400;
|
|
854
|
+
readonly invalid_headers: 401;
|
|
855
|
+
readonly invalid_signature: 401;
|
|
856
|
+
readonly processed: 200;
|
|
857
|
+
readonly replay: 200;
|
|
858
|
+
readonly stale_timestamp: 408;
|
|
859
|
+
};
|
|
860
|
+
readonly sink: {
|
|
861
|
+
readonly behaviors: {
|
|
862
|
+
readonly client_error: {
|
|
863
|
+
readonly defaultStatus: 422;
|
|
864
|
+
readonly deferredReport: undefined;
|
|
865
|
+
readonly receiverActed: false;
|
|
866
|
+
readonly senderOutcome: "terminal";
|
|
867
|
+
readonly summary: "Answers a selected 4xx. The sender records a customer refusal, and retries it only when the status is one the receiver may recover from — 401, 403 and 404, listed as `clientErrorStatusesRetried`. The default, 422, is not one of those, so it settles on the first attempt.";
|
|
868
|
+
};
|
|
869
|
+
readonly defer_abandon: {
|
|
870
|
+
readonly defaultStatus: 202;
|
|
871
|
+
readonly deferredReport: "never";
|
|
872
|
+
readonly receiverActed: true;
|
|
873
|
+
readonly senderOutcome: "ambiguous";
|
|
874
|
+
readonly summary: "Acknowledges the delivery and then never reports at all. The sender waits until the deadline in the envelope and records ambiguity, because a receiver that took the work and went quiet may well have done it. This is the case the completion deadline exists to bound.";
|
|
875
|
+
};
|
|
876
|
+
readonly defer_fail: {
|
|
877
|
+
readonly defaultStatus: 202;
|
|
878
|
+
readonly deferredReport: "failed";
|
|
879
|
+
readonly receiverActed: true;
|
|
880
|
+
readonly senderOutcome: "retryable";
|
|
881
|
+
readonly summary: "Acknowledges the delivery, then reports `failed` with the credential in its signed envelope. The occurrence goes back on the ordinary retry ladder — the same place a 500 puts it — which is how a receiver says its job died rather than that it vanished.";
|
|
882
|
+
};
|
|
883
|
+
readonly defer_succeed: {
|
|
884
|
+
readonly defaultStatus: 202;
|
|
885
|
+
readonly deferredReport: "succeeded";
|
|
886
|
+
readonly receiverActed: true;
|
|
887
|
+
readonly senderOutcome: "success";
|
|
888
|
+
readonly summary: "Acknowledges the delivery with 202 and the pending marker, keeps the work, then reports `succeeded` with the credential in its signed envelope. The whole deferred exchange, without writing a receiver.";
|
|
889
|
+
};
|
|
890
|
+
readonly delay: {
|
|
891
|
+
readonly defaultStatus: 200;
|
|
892
|
+
readonly deferredReport: undefined;
|
|
893
|
+
readonly receiverActed: true;
|
|
894
|
+
readonly senderOutcome: "success";
|
|
895
|
+
readonly summary: "Waits a bounded interval, then accepts. Exercises a slow receiver that still succeeds.";
|
|
896
|
+
};
|
|
897
|
+
readonly disconnect: {
|
|
898
|
+
readonly defaultStatus: undefined;
|
|
899
|
+
readonly deferredReport: undefined;
|
|
900
|
+
readonly receiverActed: false;
|
|
901
|
+
readonly senderOutcome: "ambiguous";
|
|
902
|
+
readonly summary: "Authenticates the delivery and then drops the connection without processing it, holding until the sender gives up. Nothing was claimed and nothing ran, but the sender cannot know that.";
|
|
903
|
+
};
|
|
904
|
+
readonly duplicate: {
|
|
905
|
+
readonly defaultStatus: 200;
|
|
906
|
+
readonly deferredReport: undefined;
|
|
907
|
+
readonly receiverActed: false;
|
|
908
|
+
readonly senderOutcome: "success";
|
|
909
|
+
readonly summary: "Accepts and reports the occurrence as already handled, without processing it again. What occurrence idempotency looks like from the sender's side.";
|
|
910
|
+
};
|
|
911
|
+
readonly invalid_signature: {
|
|
912
|
+
readonly defaultStatus: 401;
|
|
913
|
+
readonly deferredReport: undefined;
|
|
914
|
+
readonly receiverActed: false;
|
|
915
|
+
readonly senderOutcome: "retryable";
|
|
916
|
+
readonly summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The sender retries on the receiver's ordinary budget, because the usual cause is a credential the receiver is about to redeploy.";
|
|
917
|
+
};
|
|
918
|
+
readonly rate_limited: {
|
|
919
|
+
readonly defaultStatus: 429;
|
|
920
|
+
readonly deferredReport: undefined;
|
|
921
|
+
readonly receiverActed: false;
|
|
922
|
+
readonly senderOutcome: "retryable";
|
|
923
|
+
readonly summary: "Answers 429 with a bounded Retry-After. The sender honours the hint and does not spend its give-up budget.";
|
|
924
|
+
};
|
|
925
|
+
readonly server_error: {
|
|
926
|
+
readonly defaultStatus: 503;
|
|
927
|
+
readonly deferredReport: undefined;
|
|
928
|
+
readonly receiverActed: false;
|
|
929
|
+
readonly senderOutcome: "retryable";
|
|
930
|
+
readonly summary: "Answers a selected 5xx. The sender retries with backoff.";
|
|
931
|
+
};
|
|
932
|
+
readonly success: {
|
|
933
|
+
readonly defaultStatus: 200;
|
|
934
|
+
readonly deferredReport: undefined;
|
|
935
|
+
readonly receiverActed: true;
|
|
936
|
+
readonly senderOutcome: "success";
|
|
937
|
+
readonly summary: "Verifies, records, and accepts.";
|
|
938
|
+
};
|
|
939
|
+
readonly timeout: {
|
|
940
|
+
readonly defaultStatus: undefined;
|
|
941
|
+
readonly deferredReport: undefined;
|
|
942
|
+
readonly receiverActed: true;
|
|
943
|
+
readonly senderOutcome: "ambiguous";
|
|
944
|
+
readonly summary: "Verifies and records the delivery, then never answers. The receiver acted and the sender cannot know it.";
|
|
945
|
+
};
|
|
946
|
+
};
|
|
947
|
+
readonly bounds: {
|
|
948
|
+
/**
|
|
949
|
+
* Longest a sink will wait between acknowledging a deferral and reporting
|
|
950
|
+
* completion.
|
|
951
|
+
*
|
|
952
|
+
* Bounded for the same reason `maxDelayMs` is, and the reason survives the
|
|
953
|
+
* response having already been sent: the report is performed on the same
|
|
954
|
+
* invocation, so a sink asked to report an hour later would be asking the API
|
|
955
|
+
* Worker to stay alive for an hour on the product's own account. Smaller than
|
|
956
|
+
* `maxDelayMs` because nothing is waiting on this wait — a rehearsal of
|
|
957
|
+
* deferral is a rehearsal of the *shape* of the exchange, and any integrator
|
|
958
|
+
* who needs to watch a real deadline expire has `defer_abandon`, which costs
|
|
959
|
+
* nothing at all.
|
|
960
|
+
*/
|
|
961
|
+
readonly maxCompletionDelayMs: 5000;
|
|
962
|
+
/** Longest `delay` a sink will honour before answering. */
|
|
963
|
+
readonly maxDelayMs: 10000;
|
|
964
|
+
/** Longest a `timeout` or `disconnect` holds a request open. */
|
|
965
|
+
readonly maxHoldMs: 45000;
|
|
966
|
+
/** Retained delivery receipts per sink; the oldest are evicted. */
|
|
967
|
+
readonly maxRetainedDeliveries: 50;
|
|
968
|
+
/** Sinks per project/environment. */
|
|
969
|
+
readonly maxSinksPerEnvironment: 10;
|
|
970
|
+
/** Largest request body a sink will read. Larger is refused unread. */
|
|
971
|
+
readonly maxRequestBodyBytes: 65536;
|
|
972
|
+
/** Captured body bytes retained on a receipt; beyond this it is truncated. */
|
|
973
|
+
readonly maxRetainedBodyBytes: 4096;
|
|
974
|
+
};
|
|
975
|
+
readonly clientErrorStatuses: readonly [400, 401, 403, 404, 409, 410, 422];
|
|
976
|
+
readonly clientErrorStatusesRetried: readonly [401, 403, 404];
|
|
977
|
+
readonly deferral: {
|
|
978
|
+
readonly acknowledgement: string;
|
|
979
|
+
readonly note: "A deferring behavior answers this exact body with this status, then reports with the bearer credential from the delivery's own signed envelope — to the one completion address the stage is configured with, which the envelope's own URL must match. A delivery whose envelope carries no completion material, or names a different address, cannot defer, and the sink answers it as an ordinary success instead.";
|
|
980
|
+
readonly status: 202;
|
|
981
|
+
};
|
|
982
|
+
readonly egress: "A test sink has no destination field: no program, no control operation and no request header names anywhere for it to send. The one request it performs is a deferred-completion report for a delivery it has already received and authenticated, to the URL and credential carried inside that delivery's signed envelope. It cannot forward or redirect a delivery.";
|
|
983
|
+
readonly visibility: "Test environments only. Organization, project, environment, and credential kind come from the verified principal; no request may widen them.";
|
|
984
|
+
};
|
|
985
|
+
readonly version: "2026-08-23";
|
|
986
|
+
};
|