@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,115 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * What may change, and what that costs.
4
+ *
5
+ * The contract version is dated and moves independently of any deployment. A
6
+ * release that ships twice a day must not imply the contract changed twice a
7
+ * day, and a contract change must be visible even when no code deployed.
8
+ */
9
+ export const CONTRACT_VERSION = "2026-08-22";
10
+ export const contractVersionSchema = z.literal(CONTRACT_VERSION);
11
+ /**
12
+ * Three kinds of change, and only the first is free.
13
+ *
14
+ * - `additive`: existing clients keep working untouched. New optional request
15
+ * field, new response field, new enum member a client may *send*, new
16
+ * endpoint, new scope, new problem code, new member of a vocabulary the
17
+ * artifact publishes open. A new member a client must *receive* from a closed
18
+ * list is breaking — it can break exhaustive parsing.
19
+ * - `breaking`: some conforming client stops working. Removing or renaming a
20
+ * field, making an optional request field required, narrowing a type,
21
+ * removing an enum member a client may send, changing a problem code's
22
+ * status or category, changing what is signed.
23
+ * - `deployment-only`: no wire effect at all. Internal refactor, added index,
24
+ * new log field, prose in `title` or `detail`. Prose is `internal`: `title`
25
+ * and `detail` are explicitly not part of the contract, so editing them is
26
+ * not a change to a response field.
27
+ *
28
+ * The distinction is enforceable rather than advisory: `classifyChange` is
29
+ * exercised by fixtures, so "we thought it was additive" is a testable claim.
30
+ */
31
+ export const CHANGE_KINDS = [
32
+ "additive",
33
+ "breaking",
34
+ "deployment-only",
35
+ ];
36
+ export const changeKindSchema = z.enum(CHANGE_KINDS);
37
+ /**
38
+ * Declared as runtime arrays, not bare type unions, so a test can iterate the
39
+ * product and prove every combination is classified.
40
+ *
41
+ * A hand-written table of cases cannot do that: a combination nobody thought
42
+ * of is untested, and a subject or operation added later is untested by
43
+ * default — in a function whose whole job is to tell a caller whether a change
44
+ * will break customers.
45
+ */
46
+ export const CHANGE_SUBJECTS = [
47
+ "endpoint",
48
+ "enum-member",
49
+ "internal",
50
+ "problem-code",
51
+ "request-field",
52
+ "response-field",
53
+ "scope",
54
+ "signature-input",
55
+ ];
56
+ export const CHANGE_OPERATIONS = [
57
+ /** The meaning, status, or category of something existing was altered. */
58
+ "changed",
59
+ "added",
60
+ "narrowed",
61
+ "removed",
62
+ "required",
63
+ "widened",
64
+ ];
65
+ /**
66
+ * Classifies a proposed change. Deliberately conservative: anything not
67
+ * demonstrably safe is `breaking`, because the cost of wrongly calling a
68
+ * breaking change additive is borne by customers.
69
+ */
70
+ export function classifyChange(change) {
71
+ if (change.subject === "internal") {
72
+ return "deployment-only";
73
+ }
74
+ if (change.subject === "signature-input") {
75
+ // Any change to what is signed invalidates every receiver's verifier.
76
+ return "breaking";
77
+ }
78
+ if (change.operation === "removed" || change.operation === "changed") {
79
+ // "changed" covers altering a problem code's status or category, which the
80
+ // conventions document names as breaking.
81
+ return "breaking";
82
+ }
83
+ if (change.operation === "narrowed") {
84
+ // Accepting less than before breaks senders; returning less breaks readers.
85
+ return "breaking";
86
+ }
87
+ if (change.operation === "required") {
88
+ // Making a request field required breaks clients that omit it. Marking a
89
+ // response field always-present is merely a promise we now keep.
90
+ return change.subject === "request-field" ? "breaking" : "additive";
91
+ }
92
+ if (change.operation === "added") {
93
+ if (change.subject === "request-field") {
94
+ // Safe only while optional; `required` is handled above.
95
+ return "additive";
96
+ }
97
+ if (change.subject === "enum-member") {
98
+ // A new member a client may *send* is additive; a new member a client
99
+ // must *handle* on the wire can break exhaustive parsing — unless the
100
+ // artifact never published a list for it to parse exhaustively against.
101
+ return change.clientSupplied === true || change.openVocabulary === true
102
+ ? "additive"
103
+ : "breaking";
104
+ }
105
+ return "additive";
106
+ }
107
+ // `widened`. Accepting more on the way in never breaks a sender. Returning
108
+ // more on the way out does: a client parsing `string` breaks the day it
109
+ // starts receiving `string | number`.
110
+ return change.subject === "response-field" ? "breaking" : "additive";
111
+ }
112
+ export function assess(change) {
113
+ const kind = classifyChange(change);
114
+ return { kind, requiresNewContractVersion: kind === "breaking" };
115
+ }
@@ -0,0 +1,401 @@
1
+ import type { Authority } from "./authority.js";
2
+ import type { AttemptId, DeliveryId, EndpointId, OccurrenceId, ScheduleId } from "./identifiers.js";
3
+ import type { CallbackTrigger as AttemptTrigger } from "./signatures.js";
4
+ /**
5
+ * Versioned associated-data contract for encrypted schedule payloads.
6
+ *
7
+ * The value is persisted beside the envelope by the portable scheduling
8
+ * record contract. Changing it requires an explicit reader for the old value;
9
+ * silently trying another value would turn an authentication failure into a
10
+ * decryption oracle.
11
+ */
12
+ export declare const SCHEDULE_PAYLOAD_AAD_VERSION: "greatwait.schedule-payload.v1";
13
+ /**
14
+ * The encrypted customer data carried from its authoritative partition to the
15
+ * network boundary. This is an internal Durable Object RPC shape, not a public
16
+ * schema. Its application-encryption key is deliberately distinct from the key
17
+ * which wraps endpoint signing secrets.
18
+ */
19
+ export interface EncryptedSchedulePayload {
20
+ readonly ciphertext: string;
21
+ readonly keyId: string;
22
+ readonly nonce: string;
23
+ }
24
+ /**
25
+ * One immutable delivery attempt, minted by the Scheduler Partition from its
26
+ * verified authority. No member of this tuple comes from callback request
27
+ * input. A logical retry mints a new attempt and delivery identity; duplicate
28
+ * dispatch/recovery of this same attempt keeps both identities stable.
29
+ */
30
+ export interface CallbackAttemptDispatch {
31
+ readonly authority: Authority;
32
+ readonly attemptId: AttemptId;
33
+ readonly attemptNumber: number;
34
+ /**
35
+ * How this receiver says it is finished: by answering the POST, or by calling
36
+ * the completion URL afterwards.
37
+ *
38
+ * Carried for the same reason `maxDurationSeconds` is — the partition holds
39
+ * the schedule — and load-bearing twice over. It decides whether the delivery
40
+ * holds its socket open or is free to park on an acknowledgement, and
41
+ * together with the duration it decides whether this delivery occupies one of
42
+ * the partition's six in-flight slots at all.
43
+ */
44
+ readonly completion: "response" | "callback";
45
+ readonly deliveryId: DeliveryId;
46
+ /** Monotonic partition fence for duplicate/late dispatch and report RPCs. */
47
+ readonly dispatchEpoch: number;
48
+ readonly encryptedPayloadEnvelope: EncryptedSchedulePayload;
49
+ readonly endpointId: EndpointId;
50
+ readonly latencyClass: "realtime" | "bulk";
51
+ /**
52
+ * How long the executor may wait for this receiver, in seconds.
53
+ *
54
+ * **Carried on the dispatch rather than resolved by the executor**, which is
55
+ * the rule `trigger` follows a few fields down and for the same reason: the
56
+ * partition is what holds the schedule, and a value the executor looked up
57
+ * separately would be a second opinion about the customer's own setting. It is
58
+ * also what makes a recovery honest — a redispatch of an attempt admitted
59
+ * under a five-minute setting is still that attempt, even if the schedule has
60
+ * been replaced with a five-second one since.
61
+ */
62
+ readonly maxDurationSeconds: number;
63
+ /**
64
+ * Internal lost-result recovery policy. `recovery` may perform the original
65
+ * same-identity POST only when the executor has no ledger row;
66
+ * `recover_only` may read the ledger but must never perform network I/O.
67
+ */
68
+ readonly mode: "attempt" | "recovery" | "recover_only";
69
+ readonly occurrenceId: OccurrenceId;
70
+ readonly plannedForMs: number;
71
+ readonly routingUrl: string;
72
+ readonly scheduleId: ScheduleId;
73
+ /** The verified project/environment partition lane used for host fairness. */
74
+ readonly shard: string;
75
+ /**
76
+ * Why this attempt exists. Unlike `mode` and `dispatchEpoch`, this crosses
77
+ * the network: it is serialized into the signed callback envelope, and it is
78
+ * what lets a receiver run a customer-ordered redelivery again while still
79
+ * suppressing a retry nobody asked for.
80
+ */
81
+ readonly trigger: AttemptTrigger;
82
+ }
83
+ /**
84
+ * What a receiver says about work it acknowledged and then finished.
85
+ *
86
+ * Here rather than beside the envelope schema because the scheduler Worker's
87
+ * completion route reads it and that bundle deliberately carries no Zod — this
88
+ * module is the Zod-free subpath. `signatures.ts` builds its enum from this
89
+ * list, so the vocabulary exists once.
90
+ *
91
+ * `succeeded` is what an empty body means: the overwhelmingly common report is
92
+ * "done", and a receiver should not have to compose a document to say it.
93
+ * `failed` puts the occurrence back on the ordinary retry ladder — the same
94
+ * place a 500 puts it — so a receiver can distinguish "my job died" from "I
95
+ * vanished".
96
+ */
97
+ export declare const CALLBACK_COMPLETION_OUTCOMES: readonly ["succeeded", "failed"];
98
+ export type CallbackCompletionOutcome = (typeof CALLBACK_COMPLETION_OUTCOMES)[number];
99
+ export type CallbackAttemptClassification = "succeeded" | "retryable_failure" | "terminal_failure" | "ambiguous";
100
+ /**
101
+ * The one status that can mean "I have the work and I am not finished".
102
+ *
103
+ * Exactly `202`, and the narrowness is the point. Every other 2xx keeps the
104
+ * meaning it has had since the first delivery this product sent — success,
105
+ * now — so a receiver that has never heard of deferral cannot be parked by
106
+ * accident. A `200` carrying the marker is success, and so is a `204`, because
107
+ * the discriminator is not the whole rule: the status and the body have to
108
+ * agree, and only this pair opts in.
109
+ */
110
+ export declare const CALLBACK_DEFERRAL_STATUS = 202;
111
+ /**
112
+ * How much of an acknowledgement body is read before the answer is decided.
113
+ *
114
+ * One kibibyte. An acknowledgement is a fixed object of two or three short
115
+ * members, so the bound is roughly twenty times what a legitimate one needs
116
+ * and is not a limit a receiver can meet by accident. What it stops is a
117
+ * receiver — or something in front of one — answering `202` with a body that
118
+ * is expensive to read: the delivery path reads this many bytes, decides, and
119
+ * cancels the rest of the stream without buffering it.
120
+ *
121
+ * **Exceeding it is not pending.** A body over the bound cannot be shown to
122
+ * carry the discriminator, and the safe reading of "cannot be shown" is the
123
+ * one that settles the attempt now rather than the one that parks a customer's
124
+ * occurrence on a receiver whose answer we did not finish reading.
125
+ */
126
+ export declare const CALLBACK_ACKNOWLEDGEMENT_MAX_BYTES = 1024;
127
+ /**
128
+ * The media types an acknowledgement may be carried in.
129
+ *
130
+ * JSON, in the two spellings a receiver actually sends. A `202` whose body is
131
+ * HTML or plain text is an ordinary success, because whatever it says it is
132
+ * not this protocol.
133
+ */
134
+ export declare const CALLBACK_ACKNOWLEDGEMENT_CONTENT_TYPES: readonly ["application/json", "application/problem+json"];
135
+ /**
136
+ * What a receiver answers with to keep the work and report later.
137
+ *
138
+ * ```json
139
+ * { "greatwait": { "status": "pending", "completeBy": "2026-08-23T18:00:00Z" } }
140
+ * ```
141
+ *
142
+ * **Namespaced under one key on purpose.** A receiver's `202` body is its own
143
+ * document — a job id, a queue position, a status URL — and a bare
144
+ * `{"status":"pending"}` would collide with the most ordinary word in that
145
+ * vocabulary. Nesting it means an existing receiver has to have chosen the key
146
+ * `greatwait` for something else before this can misfire.
147
+ */
148
+ export declare const CALLBACK_PENDING_DISCRIMINATOR: {
149
+ readonly key: "greatwait";
150
+ readonly status: "pending";
151
+ };
152
+ /**
153
+ * Whether an acknowledgement asked to defer, and until when if it named an
154
+ * instant.
155
+ *
156
+ * `completeByMs` is what the receiver *asked for*, before clamping. Turning it
157
+ * into the deadline that is stored is
158
+ * {@link effectiveCompletionDeadlineMs}'s job, and the two are separate
159
+ * because only one of them is allowed to be an opinion.
160
+ */
161
+ export type CallbackAcknowledgement = {
162
+ readonly pending: false;
163
+ } | {
164
+ readonly completeByMs?: number;
165
+ readonly pending: true;
166
+ };
167
+ /**
168
+ * Decide whether one receiver answer is the deferral acknowledgement.
169
+ *
170
+ * Every term is required and each one is refused on its own: the status is
171
+ * exactly {@link CALLBACK_DEFERRAL_STATUS}, the media type is JSON, the body
172
+ * fits {@link CALLBACK_ACKNOWLEDGEMENT_MAX_BYTES}, it parses, it is a JSON
173
+ * object rather than an array or a scalar, and it carries the discriminator.
174
+ * Anything that fails one of them is not pending, which for a 2xx means
175
+ * ordinary immediate success.
176
+ *
177
+ * **It never decides a non-2xx.** The caller classifies the response first and
178
+ * only asks this about a success; a `500` carrying a perfectly formed pending
179
+ * marker is a `500`, because a receiver that is failing does not get to park
180
+ * the occurrence it is failing to handle.
181
+ *
182
+ * **There is no `Array.isArray` guard, and there was one.** Mutation testing
183
+ * showed both — over the parsed body and over the marker — surviving deletion
184
+ * with every case green, and the reason is that they cannot decide anything: a
185
+ * JSON array has no named members, so reading the discriminator key off one is
186
+ * `undefined` and the very next check refuses it. A rule no input can reach
187
+ * reads like protection and is not, so it is gone and this paragraph is what
188
+ * it left behind. The array cases in the suite stay — they pin the outcome a
189
+ * receiver sees, which is unchanged — but they hold by construction rather
190
+ * than by a guard.
191
+ */
192
+ export declare function readCallbackAcknowledgement(response: {
193
+ /** The decoded prefix that was read, at most the bound plus nothing. */
194
+ readonly body: string;
195
+ readonly contentType: string | null;
196
+ readonly status: number;
197
+ /** Whether the body had more bytes than the bound allowed reading. */
198
+ readonly truncated: boolean;
199
+ }): CallbackAcknowledgement;
200
+ /**
201
+ * The deadline this deferral actually gets, in epoch milliseconds.
202
+ *
203
+ * The receiver may bring it forward and may not push it out. `maximumMs` is
204
+ * the deadline already inside the signed envelope — the schedule's own
205
+ * duration allowance measured from the attempt — so a receiver asking for
206
+ * longer is asking to change what its customer bought, and a receiver asking
207
+ * for an instant that has already passed is asking for something no wait can
208
+ * satisfy.
209
+ *
210
+ * **All three refusals fall back to the maximum rather than to immediate
211
+ * success.** The acknowledgement itself is what took the work; a malformed
212
+ * hint about *when* does not unmake that, and settling such an attempt
213
+ * `succeeded` would record a result the receiver never reported.
214
+ */
215
+ export declare function effectiveCompletionDeadlineMs(requestedMs: number | undefined, maximumMs: number, nowMs: number): number;
216
+ /** Safe, bounded diagnostics. Never contains a URL, payload, or secret. */
217
+ /**
218
+ * Safe delivery diagnostics shared by the executor result and the public
219
+ * attempt timeline.
220
+ *
221
+ * Values are deliberately short categories. A URL, response body, payload,
222
+ * exception message, signing key, or transport trace has no representation in
223
+ * this vocabulary, so the durable history cannot accidentally turn private
224
+ * callback material into a public diagnostic.
225
+ */
226
+ export declare const CALLBACK_ATTEMPT_DIAGNOSTICS: readonly ["completion_timed_out", "delivered", "duplicate_inflight", "endpoint_unavailable", "endpoint_unsafe", "http_refused", "http_retryable", "http_terminal", "identity_conflict", "network_ambiguous", "payload_unavailable", "rate_limited"];
227
+ export type CallbackAttemptDiagnostic = (typeof CALLBACK_ATTEMPT_DIAGNOSTICS)[number];
228
+ /**
229
+ * Statuses where the receiver is refusing *this delivery, right now* rather
230
+ * than objecting to the message it carries.
231
+ *
232
+ * **The line is whether resending the identical bytes could ever get a
233
+ * different answer.** Every callback attempt re-sends the same signed envelope,
234
+ * so a receiver that rejects the message itself — malformed, too large, wrong
235
+ * media type, gone, refused for legal reasons — will reject it identically for
236
+ * as long as the occurrence lives, and retrying is pure cost on the population
237
+ * least likely to recover. A receiver that refuses *us* has a cause someone
238
+ * fixes: an expired or rotated credential, a WAF rule that started matching, a
239
+ * deploy window where the route is briefly absent.
240
+ *
241
+ * | Status | Why it is here |
242
+ * | ------ | -------------- |
243
+ * | 401 | credential expired, rotated, or not yet deployed |
244
+ * | 402 | receiver's own service suspended, restored on payment |
245
+ * | 403 | WAF or bot rule, revoked permission, IP block |
246
+ * | 404 | route absent during a deploy or a partial rollout |
247
+ * | 405 | method absent during a deploy or a partial rollout |
248
+ * | 407 | an egress proxy in front of the receiver wants credentials |
249
+ * | 421 | misdirected during a routing or certificate change |
250
+ * | 423 | the target resource is temporarily locked |
251
+ * | 424 | the receiver's own dependency failed |
252
+ * | 425 | the receiver asked us not to risk a replay yet — literally "retry later" |
253
+ *
254
+ * **409 is deliberately not here**, though it is a refusal in the plain
255
+ * reading. From a callback receiver a conflict overwhelmingly means "I already
256
+ * have this delivery", which the identical retry cannot resolve and which is
257
+ * the receiver's idempotency working rather than failing.
258
+ *
259
+ * **An unrecognised 4xx is not here either.** RFC 7231 says an unknown 4xx must
260
+ * be treated as a 400, so the default is terminal and this list is the whole of
261
+ * the exception. Adding to it is a product decision, not a lookup.
262
+ */
263
+ export declare const CALLBACK_REFUSAL_STATUSES: readonly [401, 402, 403, 404, 405, 407, 421, 423, 424, 425];
264
+ /** Whether a receiver refused this delivery in a way that may recover. */
265
+ export declare function isCallbackRefusalStatus(status: number): boolean;
266
+ /** Stable outcome of the one network attempt an executor invocation may run. */
267
+ export interface CallbackAttemptResult {
268
+ readonly classification: CallbackAttemptClassification;
269
+ readonly diagnostic: CallbackAttemptDiagnostic;
270
+ /** Receiver status when a response was obtained. */
271
+ readonly status?: number;
272
+ /** Bounded receiver hint, in milliseconds, for a retryable 429. */
273
+ readonly retryAfterMs?: number;
274
+ /** `tenant` applies only to this verified partition lane; `global` to host. */
275
+ readonly rateLimitScope?: "tenant" | "global";
276
+ /**
277
+ * Bounded, non-secret evidence about the response itself.
278
+ *
279
+ * Present whenever a response was obtained at all, including a success —
280
+ * status, duration and classification are kept for every attempt. The
281
+ * customer-content half of it, {@link CallbackResponseEvidence.snippet}, is
282
+ * present only where the policy allows one.
283
+ */
284
+ readonly evidence?: CallbackResponseEvidence;
285
+ }
286
+ /**
287
+ * Response headers worth keeping, in the order one is chosen.
288
+ *
289
+ * **A closed list, and short on purpose.** A response header is receiver-
290
+ * controlled text that lands in durable history and on a support screen, so
291
+ * the question is not "is this one dangerous" but "does this one earn its
292
+ * place". Content type explains what the snippet is, `Retry-After` explains a
293
+ * pacing decision the customer can see, and one receiver-side identifier is
294
+ * what turns "your receiver refused this" into something an integrator can
295
+ * look up in their own logs.
296
+ *
297
+ * Everything else is absent by construction rather than by filtering: cookies,
298
+ * `Authorization`, `WWW-Authenticate`, `Set-Cookie` and every vendor header
299
+ * have no entry here, so no future edit can retain one by forgetting to add a
300
+ * denial.
301
+ */
302
+ export declare const CALLBACK_RESPONSE_HEADER_ALLOWLIST: readonly ["content-type", "retry-after"];
303
+ /**
304
+ * Receiver-side identifiers, most specific first. **At most one is kept.**
305
+ *
306
+ * The order is which name a receiver most likely means as "look this up in my
307
+ * logs". Keeping several would be keeping a header set rather than an
308
+ * identifier, which is the thing the allowlist above exists to refuse.
309
+ */
310
+ export declare const CALLBACK_RESPONSE_REQUEST_ID_HEADERS: readonly ["x-request-id", "request-id", "x-correlation-id", "x-amzn-requestid", "cf-ray"];
311
+ /** How much of any retained header value is kept, in bytes. */
312
+ export declare const CALLBACK_RESPONSE_HEADER_MAX_BYTES = 256;
313
+ /**
314
+ * How much of a failed response body may be retained, in bytes.
315
+ *
316
+ * Four kibibytes of the *start* of the body, read as a bounded prefix and with
317
+ * the rest of the stream cancelled rather than buffered. It is enough for a
318
+ * stack trace's first frames or a validation error list, which is what makes a
319
+ * failing receiver diagnosable without turning this product into a log store.
320
+ */
321
+ export declare const CALLBACK_FAILURE_SNIPPET_MAX_BYTES = 4096;
322
+ /**
323
+ * Media types whose body may be retained as a failure snippet.
324
+ *
325
+ * Text and JSON only. A binary body has nothing a person reads in its first
326
+ * four kibibytes, and retaining one would mean storing an arbitrary fragment
327
+ * of an arbitrary file because a receiver set a header.
328
+ */
329
+ export declare const CALLBACK_FAILURE_SNIPPET_CONTENT_TYPES: readonly ["application/json", "application/problem+json", "application/xml", "text/"];
330
+ /** One retained response header, already bounded and lowercased by name. */
331
+ export interface CallbackResponseHeader {
332
+ readonly name: string;
333
+ readonly value: string;
334
+ }
335
+ /**
336
+ * What a failing receiver said, bounded and destined for encryption.
337
+ *
338
+ * `truncated` is carried rather than inferred from the length, because a body
339
+ * that is exactly the bound and a body that was cut at the bound are different
340
+ * facts and only one of them should show a customer an ellipsis.
341
+ */
342
+ export interface CallbackFailureSnippet {
343
+ readonly contentType: string;
344
+ readonly text: string;
345
+ readonly truncated: boolean;
346
+ }
347
+ /**
348
+ * Bounded evidence about one HTTP response.
349
+ *
350
+ * Deliberately not "the response": there is no body for a success, no header
351
+ * outside the allowlist, and no representation at all for a transport failure
352
+ * that produced no response.
353
+ */
354
+ export interface CallbackResponseEvidence {
355
+ readonly headers: readonly CallbackResponseHeader[];
356
+ /** Absent for a success, a pending acknowledgement, and a non-text body. */
357
+ readonly snippet?: CallbackFailureSnippet;
358
+ }
359
+ /**
360
+ * What one network attempt did, including the answer that is not an outcome.
361
+ *
362
+ * **`deferred` is a third thing beside success and failure**, and it is a
363
+ * union member rather than a flag on {@link CallbackAttemptResult} so that
364
+ * every caller has to say what it does about it. A boolean on the result would
365
+ * have let the existing carriers keep compiling while silently settling a
366
+ * deferred attempt `succeeded` — which is precisely the wrong answer, and the
367
+ * one nothing would have reported.
368
+ */
369
+ export type CallbackAttemptOutcome = CallbackAttemptResult | {
370
+ /** The instant the receiver asked for, before clamping. */
371
+ readonly completeByMs?: number;
372
+ readonly deferred: true;
373
+ readonly evidence: CallbackResponseEvidence;
374
+ readonly status: number;
375
+ };
376
+ /** Whether one network attempt asked to finish later. */
377
+ export declare function isDeferredAttempt(outcome: CallbackAttemptOutcome): outcome is Extract<CallbackAttemptOutcome, {
378
+ readonly deferred: true;
379
+ }>;
380
+ /**
381
+ * Host admission and delivery result. A reroute is not a delivery attempt: no
382
+ * request was sent and the partition may resolve the returned URL to the right
383
+ * host executor once, outside its transaction. Backpressure likewise consumes
384
+ * no receiver attempt.
385
+ */
386
+ export type HostExecutorAttemptResult = {
387
+ readonly accepted: false;
388
+ readonly disposition: "backpressure";
389
+ readonly reason: "paused" | "max_connections" | "partition_cap" | "recovery_probe" | "shard_transition" | "tokens" | "realtime_share";
390
+ readonly retryAfterMs: number;
391
+ } | {
392
+ readonly accepted: false;
393
+ readonly disposition: "reroute";
394
+ readonly routingUrl: string;
395
+ } | {
396
+ readonly accepted: true;
397
+ readonly durationMs: number;
398
+ readonly result: CallbackAttemptResult;
399
+ readonly shard: number;
400
+ readonly shardCount: number;
401
+ };