@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.
Files changed (241) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/authority.d.ts +217 -0
  4. package/dist/authority.js +261 -0
  5. package/dist/callback-ownership.d.ts +58 -0
  6. package/dist/callback-ownership.js +71 -0
  7. package/dist/callback-safety.d.ts +38 -0
  8. package/dist/callback-safety.js +403 -0
  9. package/dist/compatibility.d.ts +82 -0
  10. package/dist/compatibility.js +115 -0
  11. package/dist/delivery.d.ts +401 -0
  12. package/dist/delivery.js +311 -0
  13. package/dist/endpoints.d.ts +305 -0
  14. package/dist/endpoints.js +179 -0
  15. package/dist/events.d.ts +62 -0
  16. package/dist/events.js +38 -0
  17. package/dist/first-value-journey.d.ts +305 -0
  18. package/dist/first-value-journey.js +466 -0
  19. package/dist/forecast.d.ts +443 -0
  20. package/dist/forecast.js +729 -0
  21. package/dist/identifiers.d.ts +109 -0
  22. package/dist/identifiers.js +131 -0
  23. package/dist/identity.d.ts +196 -0
  24. package/dist/identity.js +182 -0
  25. package/dist/index.d.ts +27 -0
  26. package/dist/index.js +27 -0
  27. package/dist/mcp.d.ts +51 -0
  28. package/dist/mcp.js +56 -0
  29. package/dist/oauth.d.ts +796 -0
  30. package/dist/oauth.js +708 -0
  31. package/dist/operations.d.ts +3985 -0
  32. package/dist/operations.js +1837 -0
  33. package/dist/partition.d.ts +771 -0
  34. package/dist/partition.js +206 -0
  35. package/dist/policy.d.ts +93 -0
  36. package/dist/policy.js +180 -0
  37. package/dist/problems.d.ts +158 -0
  38. package/dist/problems.js +104 -0
  39. package/dist/protocol.d.ts +308 -0
  40. package/dist/protocol.js +515 -0
  41. package/dist/scheduling-records.d.ts +395 -0
  42. package/dist/scheduling-records.js +284 -0
  43. package/dist/scheduling.d.ts +1098 -0
  44. package/dist/scheduling.js +1116 -0
  45. package/dist/secrets.d.ts +383 -0
  46. package/dist/secrets.js +272 -0
  47. package/dist/signatures.d.ts +704 -0
  48. package/dist/signatures.js +867 -0
  49. package/dist/tenancy.d.ts +178 -0
  50. package/dist/tenancy.js +145 -0
  51. package/dist/test-sink.d.ts +986 -0
  52. package/dist/test-sink.js +755 -0
  53. package/dist/time.d.ts +45 -0
  54. package/dist/time.js +128 -0
  55. package/dist/usage.d.ts +121 -0
  56. package/dist/usage.js +200 -0
  57. package/dist/webhooks.d.ts +116 -0
  58. package/dist/webhooks.js +161 -0
  59. package/dist/workspace.d.ts +92 -0
  60. package/dist/workspace.js +59 -0
  61. package/generated/v1/callback-delivery-signature.json +467 -0
  62. package/generated/v1/callback-endpoint-verification.json +59 -0
  63. package/generated/v1/callback-receiver.json +241 -0
  64. package/generated/v1/examples/attempts.list.response.json +24 -0
  65. package/generated/v1/examples/attempts.read.response.json +20 -0
  66. package/generated/v1/examples/credentials.create.request.json +5 -0
  67. package/generated/v1/examples/credentials.create.response.json +10 -0
  68. package/generated/v1/examples/credentials.list.response.json +17 -0
  69. package/generated/v1/examples/credentials.revoke.response.json +11 -0
  70. package/generated/v1/examples/credentials.rotate.request.json +3 -0
  71. package/generated/v1/examples/credentials.rotate.response.json +22 -0
  72. package/generated/v1/examples/endpoints.create.request.json +3 -0
  73. package/generated/v1/examples/endpoints.create.response.json +16 -0
  74. package/generated/v1/examples/endpoints.disable.response.json +15 -0
  75. package/generated/v1/examples/endpoints.list.response.json +21 -0
  76. package/generated/v1/examples/endpoints.read.response.json +14 -0
  77. package/generated/v1/examples/endpoints.rotateSigningKey.request.json +3 -0
  78. package/generated/v1/examples/endpoints.rotateSigningKey.response.json +23 -0
  79. package/generated/v1/examples/endpoints.update.request.json +3 -0
  80. package/generated/v1/examples/endpoints.update.response.json +13 -0
  81. package/generated/v1/examples/endpoints.verify.response.json +15 -0
  82. package/generated/v1/examples/forecast.read.response.json +28 -0
  83. package/generated/v1/examples/forecast.windows.response.json +21 -0
  84. package/generated/v1/examples/grants.list.response.json +17 -0
  85. package/generated/v1/examples/grants.revoke.response.json +16 -0
  86. package/generated/v1/examples/meta.scaffold.response.json +5 -0
  87. package/generated/v1/examples/meta.version.response.json +5 -0
  88. package/generated/v1/examples/occurrences.list.response.json +17 -0
  89. package/generated/v1/examples/occurrences.read.response.json +15 -0
  90. package/generated/v1/examples/occurrences.redeliver.response.json +16 -0
  91. package/generated/v1/examples/projects.create.request.json +3 -0
  92. package/generated/v1/examples/projects.create.response.json +5 -0
  93. package/generated/v1/examples/projects.list.response.json +9 -0
  94. package/generated/v1/examples/schedules.archive.response.json +25 -0
  95. package/generated/v1/examples/schedules.cancel.response.json +24 -0
  96. package/generated/v1/examples/schedules.create.request.json +9 -0
  97. package/generated/v1/examples/schedules.create.response.json +25 -0
  98. package/generated/v1/examples/schedules.list.response.json +23 -0
  99. package/generated/v1/examples/schedules.pause.response.json +23 -0
  100. package/generated/v1/examples/schedules.read.response.json +25 -0
  101. package/generated/v1/examples/schedules.replace.request.json +8 -0
  102. package/generated/v1/examples/schedules.replace.response.json +25 -0
  103. package/generated/v1/examples/schedules.resume.response.json +23 -0
  104. package/generated/v1/examples/sinkdeliveries.list.response.json +19 -0
  105. package/generated/v1/examples/sinkdeliveries.read.response.json +16 -0
  106. package/generated/v1/examples/testsinks.configure.request.json +3 -0
  107. package/generated/v1/examples/testsinks.configure.response.json +13 -0
  108. package/generated/v1/examples/testsinks.create.request.json +8 -0
  109. package/generated/v1/examples/testsinks.create.response.json +13 -0
  110. package/generated/v1/examples/testsinks.delete.response.json +13 -0
  111. package/generated/v1/examples/testsinks.list.response.json +20 -0
  112. package/generated/v1/examples/testsinks.read.response.json +14 -0
  113. package/generated/v1/examples/usage.read.response.json +20 -0
  114. package/generated/v1/first-value-journey.json +436 -0
  115. package/generated/v1/manifest.json +1037 -0
  116. package/generated/v1/openapi.json +23665 -0
  117. package/generated/v1/policy-matrix.json +305 -0
  118. package/generated/v1/schemas/attempts.list.query.json +16 -0
  119. package/generated/v1/schemas/attempts.list.response.json +175 -0
  120. package/generated/v1/schemas/attempts.read.response.json +168 -0
  121. package/generated/v1/schemas/credentials.create.request.json +39 -0
  122. package/generated/v1/schemas/credentials.create.response.json +88 -0
  123. package/generated/v1/schemas/credentials.list.query.json +16 -0
  124. package/generated/v1/schemas/credentials.list.response.json +124 -0
  125. package/generated/v1/schemas/credentials.revoke.response.json +84 -0
  126. package/generated/v1/schemas/credentials.rotate.request.json +11 -0
  127. package/generated/v1/schemas/credentials.rotate.response.json +171 -0
  128. package/generated/v1/schemas/endpoints.create.request.json +12 -0
  129. package/generated/v1/schemas/endpoints.create.response.json +115 -0
  130. package/generated/v1/schemas/endpoints.disable.response-headers.json +11 -0
  131. package/generated/v1/schemas/endpoints.disable.response.json +100 -0
  132. package/generated/v1/schemas/endpoints.list.query.json +16 -0
  133. package/generated/v1/schemas/endpoints.list.response.json +146 -0
  134. package/generated/v1/schemas/endpoints.read.response-headers.json +11 -0
  135. package/generated/v1/schemas/endpoints.read.response.json +100 -0
  136. package/generated/v1/schemas/endpoints.rotateSigningKey.request-headers.json +10 -0
  137. package/generated/v1/schemas/endpoints.rotateSigningKey.request.json +11 -0
  138. package/generated/v1/schemas/endpoints.rotateSigningKey.response-headers.json +11 -0
  139. package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +118 -0
  140. package/generated/v1/schemas/endpoints.update.request-headers.json +10 -0
  141. package/generated/v1/schemas/endpoints.update.request.json +12 -0
  142. package/generated/v1/schemas/endpoints.update.response-headers.json +11 -0
  143. package/generated/v1/schemas/endpoints.update.response.json +100 -0
  144. package/generated/v1/schemas/endpoints.verify.request-headers.json +10 -0
  145. package/generated/v1/schemas/endpoints.verify.response-headers.json +11 -0
  146. package/generated/v1/schemas/endpoints.verify.response.json +100 -0
  147. package/generated/v1/schemas/forecast.read.query.json +26 -0
  148. package/generated/v1/schemas/forecast.read.response.json +100 -0
  149. package/generated/v1/schemas/forecast.windows.query.json +26 -0
  150. package/generated/v1/schemas/forecast.windows.response.json +94 -0
  151. package/generated/v1/schemas/grants.list.response.json +98 -0
  152. package/generated/v1/schemas/grants.revoke.response.json +95 -0
  153. package/generated/v1/schemas/meta.scaffold.response.json +36 -0
  154. package/generated/v1/schemas/meta.version.response.json +19 -0
  155. package/generated/v1/schemas/occurrences.list.query.json +16 -0
  156. package/generated/v1/schemas/occurrences.list.response.json +83 -0
  157. package/generated/v1/schemas/occurrences.read.response.json +76 -0
  158. package/generated/v1/schemas/occurrences.redeliver.request-headers.json +13 -0
  159. package/generated/v1/schemas/occurrences.redeliver.response-headers.json +10 -0
  160. package/generated/v1/schemas/occurrences.redeliver.response.json +168 -0
  161. package/generated/v1/schemas/projects.create.request.json +12 -0
  162. package/generated/v1/schemas/projects.create.response.json +25 -0
  163. package/generated/v1/schemas/projects.list.response.json +35 -0
  164. package/generated/v1/schemas/schedules.archive.request-headers.json +10 -0
  165. package/generated/v1/schemas/schedules.archive.response-headers.json +11 -0
  166. package/generated/v1/schemas/schedules.archive.response.json +189 -0
  167. package/generated/v1/schemas/schedules.cancel.request-headers.json +10 -0
  168. package/generated/v1/schemas/schedules.cancel.response-headers.json +11 -0
  169. package/generated/v1/schemas/schedules.cancel.response.json +189 -0
  170. package/generated/v1/schemas/schedules.create.request-headers.json +12 -0
  171. package/generated/v1/schemas/schedules.create.request.json +99 -0
  172. package/generated/v1/schemas/schedules.create.response-headers.json +15 -0
  173. package/generated/v1/schemas/schedules.create.response.json +189 -0
  174. package/generated/v1/schemas/schedules.list.query.json +16 -0
  175. package/generated/v1/schemas/schedules.list.response.json +170 -0
  176. package/generated/v1/schemas/schedules.pause.request-headers.json +10 -0
  177. package/generated/v1/schemas/schedules.pause.response-headers.json +11 -0
  178. package/generated/v1/schemas/schedules.pause.response.json +189 -0
  179. package/generated/v1/schemas/schedules.read.response-headers.json +11 -0
  180. package/generated/v1/schemas/schedules.read.response.json +189 -0
  181. package/generated/v1/schemas/schedules.replace.request-headers.json +10 -0
  182. package/generated/v1/schemas/schedules.replace.request.json +99 -0
  183. package/generated/v1/schemas/schedules.replace.response-headers.json +11 -0
  184. package/generated/v1/schemas/schedules.replace.response.json +189 -0
  185. package/generated/v1/schemas/schedules.resume.request-headers.json +10 -0
  186. package/generated/v1/schemas/schedules.resume.response-headers.json +11 -0
  187. package/generated/v1/schemas/schedules.resume.response.json +189 -0
  188. package/generated/v1/schemas/sinkdeliveries.list.query.json +16 -0
  189. package/generated/v1/schemas/sinkdeliveries.list.response.json +126 -0
  190. package/generated/v1/schemas/sinkdeliveries.read.response.json +98 -0
  191. package/generated/v1/schemas/testsinks.configure.request.json +66 -0
  192. package/generated/v1/schemas/testsinks.configure.response.json +115 -0
  193. package/generated/v1/schemas/testsinks.create.request.json +76 -0
  194. package/generated/v1/schemas/testsinks.create.response.json +115 -0
  195. package/generated/v1/schemas/testsinks.delete.response.json +115 -0
  196. package/generated/v1/schemas/testsinks.list.query.json +16 -0
  197. package/generated/v1/schemas/testsinks.list.response.json +161 -0
  198. package/generated/v1/schemas/testsinks.read.response.json +115 -0
  199. package/generated/v1/schemas/usage.read.query.json +15 -0
  200. package/generated/v1/schemas/usage.read.response.json +71 -0
  201. package/generated/v1/snippets/attempts.list.json +5 -0
  202. package/generated/v1/snippets/attempts.read.json +5 -0
  203. package/generated/v1/snippets/credentials.create.json +5 -0
  204. package/generated/v1/snippets/credentials.list.json +5 -0
  205. package/generated/v1/snippets/credentials.revoke.json +5 -0
  206. package/generated/v1/snippets/credentials.rotate.json +5 -0
  207. package/generated/v1/snippets/endpoints.create.json +5 -0
  208. package/generated/v1/snippets/endpoints.disable.json +5 -0
  209. package/generated/v1/snippets/endpoints.list.json +5 -0
  210. package/generated/v1/snippets/endpoints.read.json +5 -0
  211. package/generated/v1/snippets/endpoints.rotateSigningKey.json +5 -0
  212. package/generated/v1/snippets/endpoints.update.json +5 -0
  213. package/generated/v1/snippets/endpoints.verify.json +5 -0
  214. package/generated/v1/snippets/forecast.read.json +5 -0
  215. package/generated/v1/snippets/forecast.windows.json +5 -0
  216. package/generated/v1/snippets/grants.list.json +5 -0
  217. package/generated/v1/snippets/grants.revoke.json +5 -0
  218. package/generated/v1/snippets/meta.scaffold.json +5 -0
  219. package/generated/v1/snippets/meta.version.json +5 -0
  220. package/generated/v1/snippets/occurrences.list.json +5 -0
  221. package/generated/v1/snippets/occurrences.read.json +5 -0
  222. package/generated/v1/snippets/occurrences.redeliver.json +5 -0
  223. package/generated/v1/snippets/projects.create.json +5 -0
  224. package/generated/v1/snippets/projects.list.json +5 -0
  225. package/generated/v1/snippets/schedules.archive.json +5 -0
  226. package/generated/v1/snippets/schedules.cancel.json +5 -0
  227. package/generated/v1/snippets/schedules.create.json +5 -0
  228. package/generated/v1/snippets/schedules.list.json +5 -0
  229. package/generated/v1/snippets/schedules.pause.json +5 -0
  230. package/generated/v1/snippets/schedules.read.json +5 -0
  231. package/generated/v1/snippets/schedules.replace.json +5 -0
  232. package/generated/v1/snippets/schedules.resume.json +5 -0
  233. package/generated/v1/snippets/sinkdeliveries.list.json +5 -0
  234. package/generated/v1/snippets/sinkdeliveries.read.json +5 -0
  235. package/generated/v1/snippets/testsinks.configure.json +5 -0
  236. package/generated/v1/snippets/testsinks.create.json +5 -0
  237. package/generated/v1/snippets/testsinks.delete.json +5 -0
  238. package/generated/v1/snippets/testsinks.list.json +5 -0
  239. package/generated/v1/snippets/testsinks.read.json +5 -0
  240. package/generated/v1/snippets/usage.read.json +5 -0
  241. package/package.json +153 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Versioned associated-data contract for encrypted schedule payloads.
3
+ *
4
+ * The value is persisted beside the envelope by the portable scheduling
5
+ * record contract. Changing it requires an explicit reader for the old value;
6
+ * silently trying another value would turn an authentication failure into a
7
+ * decryption oracle.
8
+ */
9
+ export const SCHEDULE_PAYLOAD_AAD_VERSION = "greatwait.schedule-payload.v1";
10
+ /**
11
+ * What a receiver says about work it acknowledged and then finished.
12
+ *
13
+ * Here rather than beside the envelope schema because the scheduler Worker's
14
+ * completion route reads it and that bundle deliberately carries no Zod — this
15
+ * module is the Zod-free subpath. `signatures.ts` builds its enum from this
16
+ * list, so the vocabulary exists once.
17
+ *
18
+ * `succeeded` is what an empty body means: the overwhelmingly common report is
19
+ * "done", and a receiver should not have to compose a document to say it.
20
+ * `failed` puts the occurrence back on the ordinary retry ladder — the same
21
+ * place a 500 puts it — so a receiver can distinguish "my job died" from "I
22
+ * vanished".
23
+ */
24
+ export const CALLBACK_COMPLETION_OUTCOMES = ["succeeded", "failed"];
25
+ /**
26
+ * The one status that can mean "I have the work and I am not finished".
27
+ *
28
+ * Exactly `202`, and the narrowness is the point. Every other 2xx keeps the
29
+ * meaning it has had since the first delivery this product sent — success,
30
+ * now — so a receiver that has never heard of deferral cannot be parked by
31
+ * accident. A `200` carrying the marker is success, and so is a `204`, because
32
+ * the discriminator is not the whole rule: the status and the body have to
33
+ * agree, and only this pair opts in.
34
+ */
35
+ export const CALLBACK_DEFERRAL_STATUS = 202;
36
+ /**
37
+ * How much of an acknowledgement body is read before the answer is decided.
38
+ *
39
+ * One kibibyte. An acknowledgement is a fixed object of two or three short
40
+ * members, so the bound is roughly twenty times what a legitimate one needs
41
+ * and is not a limit a receiver can meet by accident. What it stops is a
42
+ * receiver — or something in front of one — answering `202` with a body that
43
+ * is expensive to read: the delivery path reads this many bytes, decides, and
44
+ * cancels the rest of the stream without buffering it.
45
+ *
46
+ * **Exceeding it is not pending.** A body over the bound cannot be shown to
47
+ * carry the discriminator, and the safe reading of "cannot be shown" is the
48
+ * one that settles the attempt now rather than the one that parks a customer's
49
+ * occurrence on a receiver whose answer we did not finish reading.
50
+ */
51
+ export const CALLBACK_ACKNOWLEDGEMENT_MAX_BYTES = 1_024;
52
+ /**
53
+ * The media types an acknowledgement may be carried in.
54
+ *
55
+ * JSON, in the two spellings a receiver actually sends. A `202` whose body is
56
+ * HTML or plain text is an ordinary success, because whatever it says it is
57
+ * not this protocol.
58
+ */
59
+ export const CALLBACK_ACKNOWLEDGEMENT_CONTENT_TYPES = [
60
+ "application/json",
61
+ "application/problem+json",
62
+ ];
63
+ /**
64
+ * What a receiver answers with to keep the work and report later.
65
+ *
66
+ * ```json
67
+ * { "greatwait": { "status": "pending", "completeBy": "2026-08-23T18:00:00Z" } }
68
+ * ```
69
+ *
70
+ * **Namespaced under one key on purpose.** A receiver's `202` body is its own
71
+ * document — a job id, a queue position, a status URL — and a bare
72
+ * `{"status":"pending"}` would collide with the most ordinary word in that
73
+ * vocabulary. Nesting it means an existing receiver has to have chosen the key
74
+ * `greatwait` for something else before this can misfire.
75
+ */
76
+ export const CALLBACK_PENDING_DISCRIMINATOR = {
77
+ key: "greatwait",
78
+ status: "pending",
79
+ };
80
+ const NOT_PENDING = { pending: false };
81
+ /**
82
+ * RFC 3339 with an explicit offset, fractional seconds optional.
83
+ *
84
+ * **Deliberately wider than `timestampSchema` and deliberately narrower than
85
+ * `Date.parse`.** This value crosses in from a receiver rather than out from
86
+ * this service, so demanding the product's own millisecond-and-`Z` spelling
87
+ * would silently discard a correct instant written the ordinary way. What is
88
+ * still refused is anything whose meaning depends on where it is read: a bare
89
+ * date, a local wall clock with no offset, an epoch number, and every other
90
+ * shape `Date.parse` is willing to guess at.
91
+ */
92
+ const RFC3339_INSTANT = /^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[Zz]|[+-]\d{2}:\d{2})$/u;
93
+ function readCompleteBy(value) {
94
+ if (typeof value !== "string" || !RFC3339_INSTANT.test(value)) {
95
+ return undefined;
96
+ }
97
+ const parsed = Date.parse(value);
98
+ return Number.isFinite(parsed) ? parsed : undefined;
99
+ }
100
+ function isAcknowledgementContentType(value) {
101
+ if (value === null)
102
+ return false;
103
+ // Parameters are the receiver's business; `application/json; charset=utf-8`
104
+ // is the same media type as `application/json`.
105
+ const media = value.split(";")[0]?.trim().toLowerCase() ?? "";
106
+ return CALLBACK_ACKNOWLEDGEMENT_CONTENT_TYPES.includes(media);
107
+ }
108
+ /**
109
+ * Decide whether one receiver answer is the deferral acknowledgement.
110
+ *
111
+ * Every term is required and each one is refused on its own: the status is
112
+ * exactly {@link CALLBACK_DEFERRAL_STATUS}, the media type is JSON, the body
113
+ * fits {@link CALLBACK_ACKNOWLEDGEMENT_MAX_BYTES}, it parses, it is a JSON
114
+ * object rather than an array or a scalar, and it carries the discriminator.
115
+ * Anything that fails one of them is not pending, which for a 2xx means
116
+ * ordinary immediate success.
117
+ *
118
+ * **It never decides a non-2xx.** The caller classifies the response first and
119
+ * only asks this about a success; a `500` carrying a perfectly formed pending
120
+ * marker is a `500`, because a receiver that is failing does not get to park
121
+ * the occurrence it is failing to handle.
122
+ *
123
+ * **There is no `Array.isArray` guard, and there was one.** Mutation testing
124
+ * showed both — over the parsed body and over the marker — surviving deletion
125
+ * with every case green, and the reason is that they cannot decide anything: a
126
+ * JSON array has no named members, so reading the discriminator key off one is
127
+ * `undefined` and the very next check refuses it. A rule no input can reach
128
+ * reads like protection and is not, so it is gone and this paragraph is what
129
+ * it left behind. The array cases in the suite stay — they pin the outcome a
130
+ * receiver sees, which is unchanged — but they hold by construction rather
131
+ * than by a guard.
132
+ */
133
+ export function readCallbackAcknowledgement(response) {
134
+ if (response.status !== CALLBACK_DEFERRAL_STATUS)
135
+ return NOT_PENDING;
136
+ if (response.truncated)
137
+ return NOT_PENDING;
138
+ if (!isAcknowledgementContentType(response.contentType))
139
+ return NOT_PENDING;
140
+ let parsed;
141
+ try {
142
+ parsed = JSON.parse(response.body);
143
+ }
144
+ catch {
145
+ return NOT_PENDING;
146
+ }
147
+ if (typeof parsed !== "object" || parsed === null)
148
+ return NOT_PENDING;
149
+ const marker = parsed[CALLBACK_PENDING_DISCRIMINATOR.key];
150
+ if (typeof marker !== "object" || marker === null)
151
+ return NOT_PENDING;
152
+ const fields = marker;
153
+ if (fields["status"] !== CALLBACK_PENDING_DISCRIMINATOR.status) {
154
+ return NOT_PENDING;
155
+ }
156
+ const completeByMs = readCompleteBy(fields["completeBy"]);
157
+ return completeByMs === undefined
158
+ ? { pending: true }
159
+ : { completeByMs, pending: true };
160
+ }
161
+ /**
162
+ * The deadline this deferral actually gets, in epoch milliseconds.
163
+ *
164
+ * The receiver may bring it forward and may not push it out. `maximumMs` is
165
+ * the deadline already inside the signed envelope — the schedule's own
166
+ * duration allowance measured from the attempt — so a receiver asking for
167
+ * longer is asking to change what its customer bought, and a receiver asking
168
+ * for an instant that has already passed is asking for something no wait can
169
+ * satisfy.
170
+ *
171
+ * **All three refusals fall back to the maximum rather than to immediate
172
+ * success.** The acknowledgement itself is what took the work; a malformed
173
+ * hint about *when* does not unmake that, and settling such an attempt
174
+ * `succeeded` would record a result the receiver never reported.
175
+ */
176
+ export function effectiveCompletionDeadlineMs(requestedMs, maximumMs, nowMs) {
177
+ if (requestedMs === undefined || !Number.isFinite(requestedMs)) {
178
+ return maximumMs;
179
+ }
180
+ if (requestedMs <= nowMs)
181
+ return maximumMs;
182
+ return Math.min(requestedMs, maximumMs);
183
+ }
184
+ /** Safe, bounded diagnostics. Never contains a URL, payload, or secret. */
185
+ /**
186
+ * Safe delivery diagnostics shared by the executor result and the public
187
+ * attempt timeline.
188
+ *
189
+ * Values are deliberately short categories. A URL, response body, payload,
190
+ * exception message, signing key, or transport trace has no representation in
191
+ * this vocabulary, so the durable history cannot accidentally turn private
192
+ * callback material into a public diagnostic.
193
+ */
194
+ export const CALLBACK_ATTEMPT_DIAGNOSTICS = [
195
+ "completion_timed_out",
196
+ "delivered",
197
+ "duplicate_inflight",
198
+ "endpoint_unavailable",
199
+ "endpoint_unsafe",
200
+ "http_refused",
201
+ "http_retryable",
202
+ "http_terminal",
203
+ "identity_conflict",
204
+ "network_ambiguous",
205
+ "payload_unavailable",
206
+ "rate_limited",
207
+ ];
208
+ /**
209
+ * Statuses where the receiver is refusing *this delivery, right now* rather
210
+ * than objecting to the message it carries.
211
+ *
212
+ * **The line is whether resending the identical bytes could ever get a
213
+ * different answer.** Every callback attempt re-sends the same signed envelope,
214
+ * so a receiver that rejects the message itself — malformed, too large, wrong
215
+ * media type, gone, refused for legal reasons — will reject it identically for
216
+ * as long as the occurrence lives, and retrying is pure cost on the population
217
+ * least likely to recover. A receiver that refuses *us* has a cause someone
218
+ * fixes: an expired or rotated credential, a WAF rule that started matching, a
219
+ * deploy window where the route is briefly absent.
220
+ *
221
+ * | Status | Why it is here |
222
+ * | ------ | -------------- |
223
+ * | 401 | credential expired, rotated, or not yet deployed |
224
+ * | 402 | receiver's own service suspended, restored on payment |
225
+ * | 403 | WAF or bot rule, revoked permission, IP block |
226
+ * | 404 | route absent during a deploy or a partial rollout |
227
+ * | 405 | method absent during a deploy or a partial rollout |
228
+ * | 407 | an egress proxy in front of the receiver wants credentials |
229
+ * | 421 | misdirected during a routing or certificate change |
230
+ * | 423 | the target resource is temporarily locked |
231
+ * | 424 | the receiver's own dependency failed |
232
+ * | 425 | the receiver asked us not to risk a replay yet — literally "retry later" |
233
+ *
234
+ * **409 is deliberately not here**, though it is a refusal in the plain
235
+ * reading. From a callback receiver a conflict overwhelmingly means "I already
236
+ * have this delivery", which the identical retry cannot resolve and which is
237
+ * the receiver's idempotency working rather than failing.
238
+ *
239
+ * **An unrecognised 4xx is not here either.** RFC 7231 says an unknown 4xx must
240
+ * be treated as a 400, so the default is terminal and this list is the whole of
241
+ * the exception. Adding to it is a product decision, not a lookup.
242
+ */
243
+ export const CALLBACK_REFUSAL_STATUSES = [
244
+ 401, 402, 403, 404, 405, 407, 421, 423, 424, 425,
245
+ ];
246
+ /** Whether a receiver refused this delivery in a way that may recover. */
247
+ export function isCallbackRefusalStatus(status) {
248
+ return CALLBACK_REFUSAL_STATUSES.includes(status);
249
+ }
250
+ /**
251
+ * Response headers worth keeping, in the order one is chosen.
252
+ *
253
+ * **A closed list, and short on purpose.** A response header is receiver-
254
+ * controlled text that lands in durable history and on a support screen, so
255
+ * the question is not "is this one dangerous" but "does this one earn its
256
+ * place". Content type explains what the snippet is, `Retry-After` explains a
257
+ * pacing decision the customer can see, and one receiver-side identifier is
258
+ * what turns "your receiver refused this" into something an integrator can
259
+ * look up in their own logs.
260
+ *
261
+ * Everything else is absent by construction rather than by filtering: cookies,
262
+ * `Authorization`, `WWW-Authenticate`, `Set-Cookie` and every vendor header
263
+ * have no entry here, so no future edit can retain one by forgetting to add a
264
+ * denial.
265
+ */
266
+ export const CALLBACK_RESPONSE_HEADER_ALLOWLIST = [
267
+ "content-type",
268
+ "retry-after",
269
+ ];
270
+ /**
271
+ * Receiver-side identifiers, most specific first. **At most one is kept.**
272
+ *
273
+ * The order is which name a receiver most likely means as "look this up in my
274
+ * logs". Keeping several would be keeping a header set rather than an
275
+ * identifier, which is the thing the allowlist above exists to refuse.
276
+ */
277
+ export const CALLBACK_RESPONSE_REQUEST_ID_HEADERS = [
278
+ "x-request-id",
279
+ "request-id",
280
+ "x-correlation-id",
281
+ "x-amzn-requestid",
282
+ "cf-ray",
283
+ ];
284
+ /** How much of any retained header value is kept, in bytes. */
285
+ export const CALLBACK_RESPONSE_HEADER_MAX_BYTES = 256;
286
+ /**
287
+ * How much of a failed response body may be retained, in bytes.
288
+ *
289
+ * Four kibibytes of the *start* of the body, read as a bounded prefix and with
290
+ * the rest of the stream cancelled rather than buffered. It is enough for a
291
+ * stack trace's first frames or a validation error list, which is what makes a
292
+ * failing receiver diagnosable without turning this product into a log store.
293
+ */
294
+ export const CALLBACK_FAILURE_SNIPPET_MAX_BYTES = 4_096;
295
+ /**
296
+ * Media types whose body may be retained as a failure snippet.
297
+ *
298
+ * Text and JSON only. A binary body has nothing a person reads in its first
299
+ * four kibibytes, and retaining one would mean storing an arbitrary fragment
300
+ * of an arbitrary file because a receiver set a header.
301
+ */
302
+ export const CALLBACK_FAILURE_SNIPPET_CONTENT_TYPES = [
303
+ "application/json",
304
+ "application/problem+json",
305
+ "application/xml",
306
+ "text/",
307
+ ];
308
+ /** Whether one network attempt asked to finish later. */
309
+ export function isDeferredAttempt(outcome) {
310
+ return "deferred" in outcome;
311
+ }
@@ -0,0 +1,305 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Reusable callback endpoints.
4
+ *
5
+ * The URL belongs to one verified authority tuple for its whole life. The
6
+ * public resource deliberately has no organization or environment identifier
7
+ * a caller can use to widen that tuple: `project` and `environmentKind` are
8
+ * display metadata, while every lookup is scoped by the verified principal.
9
+ */
10
+ export declare const CALLBACK_ENDPOINT_STATUSES: readonly ["pending", "verified", "disabled"];
11
+ export declare const callbackEndpointStatusSchema: z.ZodEnum<{
12
+ verified: "verified";
13
+ pending: "pending";
14
+ disabled: "disabled";
15
+ }>;
16
+ export type CallbackEndpointStatus = z.infer<typeof callbackEndpointStatusSchema>;
17
+ export declare const CALLBACK_VERIFICATION_FAILURES: readonly ["address_forbidden", "challenge_failed", "connection_failed", "dns_unresolved", "rate_limited", "redirect_refused", "response_too_large", "timed_out", "unexpected_status"];
18
+ export declare const callbackVerificationFailureSchema: z.ZodEnum<{
19
+ rate_limited: "rate_limited";
20
+ address_forbidden: "address_forbidden";
21
+ challenge_failed: "challenge_failed";
22
+ connection_failed: "connection_failed";
23
+ dns_unresolved: "dns_unresolved";
24
+ redirect_refused: "redirect_refused";
25
+ response_too_large: "response_too_large";
26
+ timed_out: "timed_out";
27
+ unexpected_status: "unexpected_status";
28
+ }>;
29
+ export type CallbackVerificationFailure = z.infer<typeof callbackVerificationFailureSchema>;
30
+ /**
31
+ * A safety ceiling on retained endpoint records, independent of later
32
+ * commercial plan limits. Disabled records remain readable for audit and count
33
+ * toward it; changing that lifetime semantic needs a product decision.
34
+ */
35
+ export declare const CALLBACK_ENDPOINT_LIMIT_PER_ENVIRONMENT = 50;
36
+ /** Verification egress is admitted through both buckets before DNS or fetch. */
37
+ export declare const CALLBACK_VERIFICATION_RATE_LIMITS: {
38
+ readonly destinationPerMinute: 5;
39
+ readonly projectPerMinute: 20;
40
+ readonly windowSeconds: 60;
41
+ };
42
+ /** Total bytes a verification response may return before it is refused. */
43
+ export declare const CALLBACK_VERIFICATION_RESPONSE_BYTES = 1024;
44
+ /** Total DNS + connect + response bound for one verification attempt. */
45
+ export declare const CALLBACK_VERIFICATION_TIMEOUT_MILLISECONDS = 5000;
46
+ /** Longest period during which a receiver may accept current and next keys. */
47
+ export declare const CALLBACK_SIGNING_KEY_ROTATION_OVERLAP_SECONDS = 86400;
48
+ export declare const callbackSigningKeysSchema: z.ZodObject<{
49
+ current: z.ZodString;
50
+ next: z.ZodOptional<z.ZodObject<{
51
+ id: z.ZodString;
52
+ overlapEndsAt: z.ZodString;
53
+ }, z.core.$strip>>;
54
+ }, z.core.$strip>;
55
+ export type CallbackSigningKeys = z.infer<typeof callbackSigningKeysSchema>;
56
+ /**
57
+ * A metadata-only endpoint shape. The sealed signing secret has no public
58
+ * field, so list/get/update/verify/disable cannot accidentally recover it.
59
+ */
60
+ export declare const callbackEndpointSchema: z.ZodObject<{
61
+ createdAt: z.ZodString;
62
+ disabledAt: z.ZodOptional<z.ZodString>;
63
+ environmentKind: z.ZodEnum<{
64
+ test: "test";
65
+ live: "live";
66
+ }>;
67
+ id: z.ZodString;
68
+ lastVerificationAttemptAt: z.ZodOptional<z.ZodString>;
69
+ lastVerificationFailure: z.ZodOptional<z.ZodEnum<{
70
+ rate_limited: "rate_limited";
71
+ address_forbidden: "address_forbidden";
72
+ challenge_failed: "challenge_failed";
73
+ connection_failed: "connection_failed";
74
+ dns_unresolved: "dns_unresolved";
75
+ redirect_refused: "redirect_refused";
76
+ response_too_large: "response_too_large";
77
+ timed_out: "timed_out";
78
+ unexpected_status: "unexpected_status";
79
+ }>>;
80
+ project: z.ZodString;
81
+ signingKeys: z.ZodObject<{
82
+ current: z.ZodString;
83
+ next: z.ZodOptional<z.ZodObject<{
84
+ id: z.ZodString;
85
+ overlapEndsAt: z.ZodString;
86
+ }, z.core.$strip>>;
87
+ }, z.core.$strip>;
88
+ status: z.ZodEnum<{
89
+ verified: "verified";
90
+ pending: "pending";
91
+ disabled: "disabled";
92
+ }>;
93
+ updatedAt: z.ZodString;
94
+ url: z.ZodString;
95
+ verifiedAt: z.ZodOptional<z.ZodString>;
96
+ version: z.ZodNumber;
97
+ }, z.core.$strip>;
98
+ export type CallbackEndpoint = z.infer<typeof callbackEndpointSchema>;
99
+ export declare const callbackEndpointPageSchema: z.ZodObject<{
100
+ data: z.ZodArray<z.ZodObject<{
101
+ createdAt: z.ZodString;
102
+ disabledAt: z.ZodOptional<z.ZodString>;
103
+ environmentKind: z.ZodEnum<{
104
+ test: "test";
105
+ live: "live";
106
+ }>;
107
+ id: z.ZodString;
108
+ lastVerificationAttemptAt: z.ZodOptional<z.ZodString>;
109
+ lastVerificationFailure: z.ZodOptional<z.ZodEnum<{
110
+ rate_limited: "rate_limited";
111
+ address_forbidden: "address_forbidden";
112
+ challenge_failed: "challenge_failed";
113
+ connection_failed: "connection_failed";
114
+ dns_unresolved: "dns_unresolved";
115
+ redirect_refused: "redirect_refused";
116
+ response_too_large: "response_too_large";
117
+ timed_out: "timed_out";
118
+ unexpected_status: "unexpected_status";
119
+ }>>;
120
+ project: z.ZodString;
121
+ signingKeys: z.ZodObject<{
122
+ current: z.ZodString;
123
+ next: z.ZodOptional<z.ZodObject<{
124
+ id: z.ZodString;
125
+ overlapEndsAt: z.ZodString;
126
+ }, z.core.$strip>>;
127
+ }, z.core.$strip>;
128
+ status: z.ZodEnum<{
129
+ verified: "verified";
130
+ pending: "pending";
131
+ disabled: "disabled";
132
+ }>;
133
+ updatedAt: z.ZodString;
134
+ url: z.ZodString;
135
+ verifiedAt: z.ZodOptional<z.ZodString>;
136
+ version: z.ZodNumber;
137
+ }, z.core.$strip>>;
138
+ freshness: z.ZodDiscriminatedUnion<[z.ZodObject<{
139
+ kind: z.ZodLiteral<"authoritative">;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ asOf: z.ZodString;
142
+ kind: z.ZodLiteral<"projected">;
143
+ lagSeconds: z.ZodNumber;
144
+ }, z.core.$strip>], "kind">;
145
+ nextCursor: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strip>;
147
+ export type CallbackEndpointPage = z.infer<typeof callbackEndpointPageSchema>;
148
+ /** The one response that carries the per-endpoint secret. */
149
+ export declare const callbackEndpointCreatedSchema: z.ZodObject<{
150
+ createdAt: z.ZodString;
151
+ disabledAt: z.ZodOptional<z.ZodString>;
152
+ environmentKind: z.ZodEnum<{
153
+ test: "test";
154
+ live: "live";
155
+ }>;
156
+ id: z.ZodString;
157
+ lastVerificationAttemptAt: z.ZodOptional<z.ZodString>;
158
+ lastVerificationFailure: z.ZodOptional<z.ZodEnum<{
159
+ rate_limited: "rate_limited";
160
+ address_forbidden: "address_forbidden";
161
+ challenge_failed: "challenge_failed";
162
+ connection_failed: "connection_failed";
163
+ dns_unresolved: "dns_unresolved";
164
+ redirect_refused: "redirect_refused";
165
+ response_too_large: "response_too_large";
166
+ timed_out: "timed_out";
167
+ unexpected_status: "unexpected_status";
168
+ }>>;
169
+ project: z.ZodString;
170
+ signingKeys: z.ZodObject<{
171
+ current: z.ZodString;
172
+ next: z.ZodOptional<z.ZodObject<{
173
+ id: z.ZodString;
174
+ overlapEndsAt: z.ZodString;
175
+ }, z.core.$strip>>;
176
+ }, z.core.$strip>;
177
+ status: z.ZodEnum<{
178
+ verified: "verified";
179
+ pending: "pending";
180
+ disabled: "disabled";
181
+ }>;
182
+ updatedAt: z.ZodString;
183
+ url: z.ZodString;
184
+ verifiedAt: z.ZodOptional<z.ZodString>;
185
+ version: z.ZodNumber;
186
+ keyId: z.ZodString;
187
+ secret: z.ZodString;
188
+ shownOnce: z.ZodLiteral<true>;
189
+ }, z.core.$strip>;
190
+ export type CallbackEndpointCreated = z.infer<typeof callbackEndpointCreatedSchema>;
191
+ export declare const callbackSigningKeyRotationRequestSchema: z.ZodObject<{
192
+ overlapSeconds: z.ZodOptional<z.ZodNumber>;
193
+ }, z.core.$strip>;
194
+ export type CallbackSigningKeyRotationRequest = z.infer<typeof callbackSigningKeyRotationRequestSchema>;
195
+ export declare const callbackEndpointRotatedSchema: z.ZodObject<{
196
+ endpoint: z.ZodObject<{
197
+ createdAt: z.ZodString;
198
+ disabledAt: z.ZodOptional<z.ZodString>;
199
+ environmentKind: z.ZodEnum<{
200
+ test: "test";
201
+ live: "live";
202
+ }>;
203
+ id: z.ZodString;
204
+ lastVerificationAttemptAt: z.ZodOptional<z.ZodString>;
205
+ lastVerificationFailure: z.ZodOptional<z.ZodEnum<{
206
+ rate_limited: "rate_limited";
207
+ address_forbidden: "address_forbidden";
208
+ challenge_failed: "challenge_failed";
209
+ connection_failed: "connection_failed";
210
+ dns_unresolved: "dns_unresolved";
211
+ redirect_refused: "redirect_refused";
212
+ response_too_large: "response_too_large";
213
+ timed_out: "timed_out";
214
+ unexpected_status: "unexpected_status";
215
+ }>>;
216
+ project: z.ZodString;
217
+ signingKeys: z.ZodObject<{
218
+ current: z.ZodString;
219
+ next: z.ZodOptional<z.ZodObject<{
220
+ id: z.ZodString;
221
+ overlapEndsAt: z.ZodString;
222
+ }, z.core.$strip>>;
223
+ }, z.core.$strip>;
224
+ status: z.ZodEnum<{
225
+ verified: "verified";
226
+ pending: "pending";
227
+ disabled: "disabled";
228
+ }>;
229
+ updatedAt: z.ZodString;
230
+ url: z.ZodString;
231
+ verifiedAt: z.ZodOptional<z.ZodString>;
232
+ version: z.ZodNumber;
233
+ }, z.core.$strip>;
234
+ keyId: z.ZodString;
235
+ secret: z.ZodString;
236
+ shownOnce: z.ZodLiteral<true>;
237
+ }, z.core.$strip>;
238
+ export type CallbackEndpointRotated = z.infer<typeof callbackEndpointRotatedSchema>;
239
+ export declare const callbackEndpointRequestSchema: z.ZodObject<{
240
+ url: z.ZodString;
241
+ }, z.core.$strip>;
242
+ export type CallbackEndpointRequest = z.infer<typeof callbackEndpointRequestSchema>;
243
+ export declare const CALLBACK_VERIFICATION_PROTOCOL_VERSION = "2026-08-05";
244
+ export declare const CALLBACK_VERIFICATION_TYPE = "callback.endpoint.verification";
245
+ /**
246
+ * Exact verification bytes sent to the receiver. The receiver validates the
247
+ * request signature and proves possession of the one-time secret by returning
248
+ * an HMAC of `challenge` in `Callback-Verification-Response`.
249
+ */
250
+ export declare const callbackVerificationChallengeSchema: z.ZodObject<{
251
+ challenge: z.ZodString;
252
+ endpointId: z.ZodString;
253
+ type: z.ZodLiteral<"callback.endpoint.verification">;
254
+ version: z.ZodLiteral<"2026-08-05">;
255
+ }, z.core.$strip>;
256
+ export type CallbackVerificationChallenge = z.infer<typeof callbackVerificationChallengeSchema>;
257
+ export declare const CALLBACK_VERIFICATION_HEADERS: {
258
+ readonly challenge: "Callback-Verification-Challenge";
259
+ readonly requestSignature: "Callback-Verification-Signature";
260
+ readonly responseSignature: "Callback-Verification-Response";
261
+ };
262
+ /**
263
+ * Machine-readable receiver contract for endpoint ownership verification.
264
+ *
265
+ * This is deliberately separate from W3-02's occurrence-delivery signature:
266
+ * the request proves control of a receiver before the endpoint may be used,
267
+ * while a delivery signature authenticates an occurrence body later. The
268
+ * generator publishes this object beside OpenAPI and the JSON Schema.
269
+ */
270
+ export declare const CALLBACK_VERIFICATION_PROTOCOL: {
271
+ readonly purpose: "Prove that the receiver possesses the one-time endpoint secret before Greatwait marks the endpoint verified.";
272
+ readonly request: {
273
+ readonly bodyEncoding: "UTF-8 bytes of the compact JSON object in schema property order: challenge, endpointId, type, version.";
274
+ readonly contentType: "application/json";
275
+ readonly method: "POST";
276
+ readonly redirectPolicy: "manual; every 3xx response is refused";
277
+ readonly signature: {
278
+ readonly algorithm: "HMAC-SHA-256";
279
+ readonly format: "v1=<64 lowercase hexadecimal characters>";
280
+ readonly header: "Callback-Verification-Signature";
281
+ readonly input: "the exact request body bytes";
282
+ readonly key: "the one-time endpoint secret returned by REST endpoint creation";
283
+ };
284
+ };
285
+ readonly response: {
286
+ readonly body: "ignored after enforcing the 1024-byte response limit; return an empty body";
287
+ readonly proof: {
288
+ readonly algorithm: "HMAC-SHA-256";
289
+ readonly format: "v1=<64 lowercase hexadecimal characters>";
290
+ readonly header: "Callback-Verification-Response";
291
+ readonly input: "the UTF-8 bytes of the challenge string";
292
+ readonly key: "the same endpoint secret";
293
+ };
294
+ readonly status: "any 2xx status";
295
+ };
296
+ readonly testVector: {
297
+ readonly challenge: "cvc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
298
+ readonly endpointId: "ep_00000000000000000000000000";
299
+ readonly exactBodyBase64: "eyJjaGFsbGVuZ2UiOiJjdmNfQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsImVuZHBvaW50SWQiOiJlcF8wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInR5cGUiOiJjYWxsYmFjay5lbmRwb2ludC52ZXJpZmljYXRpb24iLCJ2ZXJzaW9uIjoiMjAyNi0wOC0wNSJ9";
300
+ readonly requestSignature: "v1=0d24c7fed4d752411b88025a9923627b329e0eb9d53978a6ebcf92c0678c731e";
301
+ readonly responseSignature: "v1=efc690a23f1870930ff3388313162c0e06406902a7a4d3f9273ffe0abcef86bd";
302
+ readonly secret: "verification-vector-secret";
303
+ };
304
+ readonly version: "2026-08-05";
305
+ };