@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,179 @@
1
+ import { z } from "zod";
2
+ import { environmentKindSchema } from "./authority.js";
3
+ import { evaluateCallbackUrl } from "./callback-safety.js";
4
+ import { callbackSigningKeyIdSchema, endpointIdSchema, projectIdSchema, } from "./identifiers.js";
5
+ import { pageSchema } from "./protocol.js";
6
+ import { timestampSchema } from "./time.js";
7
+ /**
8
+ * Reusable callback endpoints.
9
+ *
10
+ * The URL belongs to one verified authority tuple for its whole life. The
11
+ * public resource deliberately has no organization or environment identifier
12
+ * a caller can use to widen that tuple: `project` and `environmentKind` are
13
+ * display metadata, while every lookup is scoped by the verified principal.
14
+ */
15
+ export const CALLBACK_ENDPOINT_STATUSES = [
16
+ "pending",
17
+ "verified",
18
+ "disabled",
19
+ ];
20
+ export const callbackEndpointStatusSchema = z.enum(CALLBACK_ENDPOINT_STATUSES);
21
+ export const CALLBACK_VERIFICATION_FAILURES = [
22
+ "address_forbidden",
23
+ "challenge_failed",
24
+ "connection_failed",
25
+ "dns_unresolved",
26
+ "rate_limited",
27
+ "redirect_refused",
28
+ "response_too_large",
29
+ "timed_out",
30
+ "unexpected_status",
31
+ ];
32
+ export const callbackVerificationFailureSchema = z.enum(CALLBACK_VERIFICATION_FAILURES);
33
+ /**
34
+ * A safety ceiling on retained endpoint records, independent of later
35
+ * commercial plan limits. Disabled records remain readable for audit and count
36
+ * toward it; changing that lifetime semantic needs a product decision.
37
+ */
38
+ export const CALLBACK_ENDPOINT_LIMIT_PER_ENVIRONMENT = 50;
39
+ /** Verification egress is admitted through both buckets before DNS or fetch. */
40
+ export const CALLBACK_VERIFICATION_RATE_LIMITS = {
41
+ destinationPerMinute: 5,
42
+ projectPerMinute: 20,
43
+ windowSeconds: 60,
44
+ };
45
+ /** Total bytes a verification response may return before it is refused. */
46
+ export const CALLBACK_VERIFICATION_RESPONSE_BYTES = 1_024;
47
+ /** Total DNS + connect + response bound for one verification attempt. */
48
+ export const CALLBACK_VERIFICATION_TIMEOUT_MILLISECONDS = 5_000;
49
+ /** Longest period during which a receiver may accept current and next keys. */
50
+ export const CALLBACK_SIGNING_KEY_ROTATION_OVERLAP_SECONDS = 86_400;
51
+ export const callbackSigningKeysSchema = z.object({
52
+ current: callbackSigningKeyIdSchema,
53
+ next: z
54
+ .object({
55
+ id: callbackSigningKeyIdSchema,
56
+ overlapEndsAt: timestampSchema,
57
+ })
58
+ .optional(),
59
+ });
60
+ /**
61
+ * A metadata-only endpoint shape. The sealed signing secret has no public
62
+ * field, so list/get/update/verify/disable cannot accidentally recover it.
63
+ */
64
+ export const callbackEndpointSchema = z.object({
65
+ createdAt: timestampSchema,
66
+ disabledAt: timestampSchema.optional(),
67
+ environmentKind: environmentKindSchema,
68
+ id: endpointIdSchema,
69
+ lastVerificationAttemptAt: timestampSchema.optional(),
70
+ lastVerificationFailure: callbackVerificationFailureSchema.optional(),
71
+ project: projectIdSchema,
72
+ signingKeys: callbackSigningKeysSchema,
73
+ status: callbackEndpointStatusSchema,
74
+ updatedAt: timestampSchema,
75
+ url: z.string().url(),
76
+ verifiedAt: timestampSchema.optional(),
77
+ version: z.number().int().positive(),
78
+ });
79
+ export const callbackEndpointPageSchema = pageSchema(callbackEndpointSchema);
80
+ /** The one response that carries the per-endpoint secret. */
81
+ export const callbackEndpointCreatedSchema = callbackEndpointSchema.extend({
82
+ keyId: callbackSigningKeyIdSchema,
83
+ secret: z.string().min(1),
84
+ shownOnce: z.literal(true),
85
+ });
86
+ export const callbackSigningKeyRotationRequestSchema = z.object({
87
+ overlapSeconds: z
88
+ .number()
89
+ .int()
90
+ .min(0)
91
+ .max(CALLBACK_SIGNING_KEY_ROTATION_OVERLAP_SECONDS, "must not exceed the maximum endpoint-key overlap")
92
+ .optional(),
93
+ });
94
+ export const callbackEndpointRotatedSchema = z.object({
95
+ endpoint: callbackEndpointSchema,
96
+ keyId: callbackSigningKeyIdSchema,
97
+ secret: z.string().min(1),
98
+ shownOnce: z.literal(true),
99
+ });
100
+ function callbackUrlSchema() {
101
+ return z
102
+ .string()
103
+ .url()
104
+ .max(2_048)
105
+ .superRefine((value, context) => {
106
+ const verdict = evaluateCallbackUrl(value);
107
+ if (!verdict.ok) {
108
+ context.addIssue({
109
+ code: "custom",
110
+ message: verdict.reason,
111
+ });
112
+ }
113
+ });
114
+ }
115
+ export const callbackEndpointRequestSchema = z.object({
116
+ url: callbackUrlSchema(),
117
+ });
118
+ export const CALLBACK_VERIFICATION_PROTOCOL_VERSION = "2026-08-05";
119
+ export const CALLBACK_VERIFICATION_TYPE = "callback.endpoint.verification";
120
+ /**
121
+ * Exact verification bytes sent to the receiver. The receiver validates the
122
+ * request signature and proves possession of the one-time secret by returning
123
+ * an HMAC of `challenge` in `Callback-Verification-Response`.
124
+ */
125
+ export const callbackVerificationChallengeSchema = z.object({
126
+ challenge: z.string().regex(/^cvc_[A-Za-z0-9_-]{43}$/u),
127
+ endpointId: endpointIdSchema,
128
+ type: z.literal(CALLBACK_VERIFICATION_TYPE),
129
+ version: z.literal(CALLBACK_VERIFICATION_PROTOCOL_VERSION),
130
+ });
131
+ export const CALLBACK_VERIFICATION_HEADERS = {
132
+ challenge: "Callback-Verification-Challenge",
133
+ requestSignature: "Callback-Verification-Signature",
134
+ responseSignature: "Callback-Verification-Response",
135
+ };
136
+ /**
137
+ * Machine-readable receiver contract for endpoint ownership verification.
138
+ *
139
+ * This is deliberately separate from W3-02's occurrence-delivery signature:
140
+ * the request proves control of a receiver before the endpoint may be used,
141
+ * while a delivery signature authenticates an occurrence body later. The
142
+ * generator publishes this object beside OpenAPI and the JSON Schema.
143
+ */
144
+ export const CALLBACK_VERIFICATION_PROTOCOL = {
145
+ purpose: "Prove that the receiver possesses the one-time endpoint secret before Greatwait marks the endpoint verified.",
146
+ request: {
147
+ bodyEncoding: "UTF-8 bytes of the compact JSON object in schema property order: challenge, endpointId, type, version.",
148
+ contentType: "application/json",
149
+ method: "POST",
150
+ redirectPolicy: "manual; every 3xx response is refused",
151
+ signature: {
152
+ algorithm: "HMAC-SHA-256",
153
+ format: "v1=<64 lowercase hexadecimal characters>",
154
+ header: CALLBACK_VERIFICATION_HEADERS.requestSignature,
155
+ input: "the exact request body bytes",
156
+ key: "the one-time endpoint secret returned by REST endpoint creation",
157
+ },
158
+ },
159
+ response: {
160
+ body: "ignored after enforcing the 1024-byte response limit; return an empty body",
161
+ proof: {
162
+ algorithm: "HMAC-SHA-256",
163
+ format: "v1=<64 lowercase hexadecimal characters>",
164
+ header: CALLBACK_VERIFICATION_HEADERS.responseSignature,
165
+ input: "the UTF-8 bytes of the challenge string",
166
+ key: "the same endpoint secret",
167
+ },
168
+ status: "any 2xx status",
169
+ },
170
+ testVector: {
171
+ challenge: "cvc_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
172
+ endpointId: "ep_00000000000000000000000000",
173
+ exactBodyBase64: "eyJjaGFsbGVuZ2UiOiJjdmNfQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQSIsImVuZHBvaW50SWQiOiJlcF8wMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInR5cGUiOiJjYWxsYmFjay5lbmRwb2ludC52ZXJpZmljYXRpb24iLCJ2ZXJzaW9uIjoiMjAyNi0wOC0wNSJ9",
174
+ requestSignature: "v1=0d24c7fed4d752411b88025a9923627b329e0eb9d53978a6ebcf92c0678c731e",
175
+ responseSignature: "v1=efc690a23f1870930ff3388313162c0e06406902a7a4d3f9273ffe0abcef86bd",
176
+ secret: "verification-vector-secret",
177
+ },
178
+ version: CALLBACK_VERIFICATION_PROTOCOL_VERSION,
179
+ };
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Product events.
4
+ *
5
+ * One envelope for every event the product emits, whether it reaches a customer
6
+ * or only an internal projection. Delivery is at-least-once everywhere, so
7
+ * `id` is the deduplication key and consumers must be idempotent on it.
8
+ *
9
+ * `occurredAt` is when the thing happened; `recordedAt` is when we learned. A
10
+ * projection that lags can be reasoned about only if both are present, and
11
+ * collapsing them is how "why is this event from the future" bugs start.
12
+ */
13
+ export declare const EVENT_TYPES: readonly ["endpoint.verified", "occurrence.attempt.recorded", "occurrence.due", "occurrence.succeeded", "occurrence.terminal_failed", "schedule.cancelled", "schedule.completed", "schedule.created", "schedule.replaced"];
14
+ export declare const eventTypeSchema: z.ZodEnum<{
15
+ "endpoint.verified": "endpoint.verified";
16
+ "occurrence.attempt.recorded": "occurrence.attempt.recorded";
17
+ "occurrence.due": "occurrence.due";
18
+ "occurrence.succeeded": "occurrence.succeeded";
19
+ "occurrence.terminal_failed": "occurrence.terminal_failed";
20
+ "schedule.cancelled": "schedule.cancelled";
21
+ "schedule.completed": "schedule.completed";
22
+ "schedule.created": "schedule.created";
23
+ "schedule.replaced": "schedule.replaced";
24
+ }>;
25
+ export type EventType = z.infer<typeof eventTypeSchema>;
26
+ export declare function eventEnvelopeSchema<TData extends z.ZodTypeAny>(data: TData): z.ZodObject<{
27
+ data: TData;
28
+ id: z.ZodString;
29
+ occurredAt: z.ZodString;
30
+ recordedAt: z.ZodString;
31
+ schemaVersion: z.ZodNumber;
32
+ type: z.ZodEnum<{
33
+ "endpoint.verified": "endpoint.verified";
34
+ "occurrence.attempt.recorded": "occurrence.attempt.recorded";
35
+ "occurrence.due": "occurrence.due";
36
+ "occurrence.succeeded": "occurrence.succeeded";
37
+ "occurrence.terminal_failed": "occurrence.terminal_failed";
38
+ "schedule.cancelled": "schedule.cancelled";
39
+ "schedule.completed": "schedule.completed";
40
+ "schedule.created": "schedule.created";
41
+ "schedule.replaced": "schedule.replaced";
42
+ }>;
43
+ }, z.core.$strip>;
44
+ export declare const genericEventSchema: z.ZodObject<{
45
+ data: z.ZodRecord<z.ZodString, z.ZodUnknown>;
46
+ id: z.ZodString;
47
+ occurredAt: z.ZodString;
48
+ recordedAt: z.ZodString;
49
+ schemaVersion: z.ZodNumber;
50
+ type: z.ZodEnum<{
51
+ "endpoint.verified": "endpoint.verified";
52
+ "occurrence.attempt.recorded": "occurrence.attempt.recorded";
53
+ "occurrence.due": "occurrence.due";
54
+ "occurrence.succeeded": "occurrence.succeeded";
55
+ "occurrence.terminal_failed": "occurrence.terminal_failed";
56
+ "schedule.cancelled": "schedule.cancelled";
57
+ "schedule.completed": "schedule.completed";
58
+ "schedule.created": "schedule.created";
59
+ "schedule.replaced": "schedule.replaced";
60
+ }>;
61
+ }, z.core.$strip>;
62
+ export type GenericEvent = z.infer<typeof genericEventSchema>;
package/dist/events.js ADDED
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { eventIdSchema } from "./identifiers.js";
3
+ import { timestampSchema } from "./time.js";
4
+ /**
5
+ * Product events.
6
+ *
7
+ * One envelope for every event the product emits, whether it reaches a customer
8
+ * or only an internal projection. Delivery is at-least-once everywhere, so
9
+ * `id` is the deduplication key and consumers must be idempotent on it.
10
+ *
11
+ * `occurredAt` is when the thing happened; `recordedAt` is when we learned. A
12
+ * projection that lags can be reasoned about only if both are present, and
13
+ * collapsing them is how "why is this event from the future" bugs start.
14
+ */
15
+ export const EVENT_TYPES = [
16
+ "endpoint.verified",
17
+ "occurrence.attempt.recorded",
18
+ "occurrence.due",
19
+ "occurrence.succeeded",
20
+ "occurrence.terminal_failed",
21
+ "schedule.cancelled",
22
+ "schedule.completed",
23
+ "schedule.created",
24
+ "schedule.replaced",
25
+ ];
26
+ export const eventTypeSchema = z.enum(EVENT_TYPES);
27
+ export function eventEnvelopeSchema(data) {
28
+ return z.object({
29
+ data,
30
+ id: eventIdSchema,
31
+ occurredAt: timestampSchema,
32
+ recordedAt: timestampSchema,
33
+ /** Bumped only when this event's `data` changes incompatibly. */
34
+ schemaVersion: z.number().int().positive(),
35
+ type: eventTypeSchema,
36
+ });
37
+ }
38
+ export const genericEventSchema = eventEnvelopeSchema(z.record(z.string(), z.unknown()));
@@ -0,0 +1,305 @@
1
+ import { z } from "zod";
2
+ import type { ProblemCode } from "./problems.js";
3
+ /**
4
+ * The one bounded sequence that takes a developer from a credential to an
5
+ * observed callback.
6
+ *
7
+ * **Declared once, here, because W9-03 is not the only story that has to tell
8
+ * it.** The curl quickstart, the JavaScript quickstart, the website journey
9
+ * W9-01 owns, the DocsAlot pages W9-04 publishes, and the command-line
10
+ * ergonomics W10 owns are all the *same* sequence rendered for a different
11
+ * reader. Written separately they diverge silently — the legacy scheduler's
12
+ * hand-maintained client and documentation are the worked example, and the
13
+ * thing that made them wrong was never a typo but a step somebody added in one
14
+ * place.
15
+ *
16
+ * So a step names an operation in `OPERATIONS` and nothing else. The method,
17
+ * the path, the scopes, the refusals, the request shape and the response shape
18
+ * come from the registry, and `scripts/contract-projection.ts` renders them.
19
+ * What lives here is the part that genuinely is not derivable from a single
20
+ * operation: **what order the calls go in, why each one is there, what a reader
21
+ * should see, and what to do when they do not see it.**
22
+ *
23
+ * The three things it deliberately does *not* contain:
24
+ *
25
+ * - **A wire shape.** `request` bodies here are validated against the owning
26
+ * operation's `request` schema when artifacts are generated, so a body that
27
+ * the service would refuse cannot be published.
28
+ * - **A credential.** Every value a reader supplies is a declared variable with
29
+ * a placeholder, and `journeyVariable` refuses one whose placeholder looks
30
+ * like a real credential.
31
+ * - **A receiver we chose for them.** `GREATWAIT_RECEIVER_URL` is a
32
+ * prerequisite the reader brings. See {@link FIRST_VALUE_PREREQUISITES}.
33
+ */
34
+ /**
35
+ * Something a reader must already have, that no API call in this journey can
36
+ * produce for them.
37
+ *
38
+ * **This list exists because the alternative is an invented shortcut.** Two of
39
+ * the three entries name a step that legitimately requires a signed-in human:
40
+ * a project and an API credential are created through the dashboard, because
41
+ * `projects.create` and `credentials.create` accept only a session principal.
42
+ * A quickstart that papered over that would either publish a call nobody can
43
+ * make or quietly hand out a privileged path. The third is the receiver, and
44
+ * the reason it is the reader's is recorded in
45
+ * `docs/projects/w9-invited-alpha-journeys/implementation-guide.md`: until
46
+ * FXO-1746 delivers an accepted first-party receiver, sending a sample payload
47
+ * to an arbitrary third party is not something this product does on somebody's
48
+ * behalf.
49
+ */
50
+ export interface JourneyPrerequisite {
51
+ /** What the reader has to do, in one imperative sentence. */
52
+ readonly action: string;
53
+ /** Why an API call cannot do it for them. */
54
+ readonly because: string;
55
+ readonly id: string;
56
+ readonly title: string;
57
+ }
58
+ /**
59
+ * A value the reader supplies, named identically for curl and for JavaScript.
60
+ *
61
+ * One derivation for both surfaces, so a reader who exported the shell
62
+ * variables can see at a glance which `process.env` name is the same value.
63
+ * Before W9-03 the two disagreed — curl read `$API` and `$TEST_KEY` while the
64
+ * JavaScript read `GREATWAIT_API_URL` and `GREATWAIT_TEST_KEY` — and one of
65
+ * those four named a *test* credential in examples published for a product
66
+ * whose test environment is not offered to customers.
67
+ */
68
+ export interface JourneyVariable {
69
+ /**
70
+ * How a rendering invents a value when the reader supplies none.
71
+ *
72
+ * Declared for exactly one variable and deliberately not for the others.
73
+ * An idempotency key is a value the *caller* chooses and nothing else can
74
+ * choose for them, so it has to be nameable — but a quickstart that demanded
75
+ * one before the reader had learned what it was for would be asking them to
76
+ * make a decision they cannot yet make. The API origin, the credential and
77
+ * the receiver have no honest default at all: inventing one is how a
78
+ * quickstart ends up sending somebody's payload somewhere they did not
79
+ * choose.
80
+ */
81
+ readonly defaultExpression?: {
82
+ readonly javascript: string;
83
+ readonly shell: string;
84
+ };
85
+ /** Why it is needed, and where the reader gets it. */
86
+ readonly detail: string;
87
+ readonly name: string;
88
+ /**
89
+ * What to show in place of a value.
90
+ *
91
+ * Never a credential, and `journeyVariableSchema` refuses one that looks
92
+ * like it might be: a placeholder is copied into generated pages, and a
93
+ * generated page is exactly where a real credential must never appear.
94
+ */
95
+ readonly placeholder: string;
96
+ /** Whether the value is a secret, which decides how it is handled and shown. */
97
+ readonly secret: boolean;
98
+ readonly title: string;
99
+ }
100
+ /**
101
+ * One thing that can go wrong at a step, and the next move.
102
+ *
103
+ * `when` is a `ProblemCode` when the service refuses, and one of the named
104
+ * conditions below when the failure has no problem document — a timeout, a
105
+ * receiver that answered badly, an outcome that never settled. Both kinds
106
+ * exist because an integrator hits both, and a recovery table that only covers
107
+ * refusals leaves the reader alone at exactly the point they are stuck.
108
+ */
109
+ export declare const JOURNEY_CONDITIONS: readonly ["attempt-failed", "no-response", "not-settled", "receiver-unreachable", "unexpected-answer"];
110
+ export type JourneyCondition = (typeof JOURNEY_CONDITIONS)[number];
111
+ export interface JourneyRecovery {
112
+ /** What the reader does next. Imperative, and specific enough to act on. */
113
+ readonly nextMove: string;
114
+ readonly when: JourneyCondition | ProblemCode;
115
+ }
116
+ /**
117
+ * A value carried from one step's response into a later step's request.
118
+ *
119
+ * `from` is a dotted path into the parsed response body, so the same
120
+ * declaration drives `jq` in the shell rendering and property access in the
121
+ * JavaScript one. `header` reads a response header instead, which is how the
122
+ * replay step observes `Idempotency-Replayed`.
123
+ */
124
+ export interface JourneyCapture {
125
+ readonly from: string;
126
+ /** True when `from` names a response header rather than a body path. */
127
+ readonly header?: boolean;
128
+ /** Whether the captured value is a secret, and must never be printed. */
129
+ readonly secret?: boolean;
130
+ /** The variable it becomes, which a later step may reference. */
131
+ readonly variable: string;
132
+ }
133
+ /**
134
+ * Something a step checks about what it captured.
135
+ *
136
+ * **Added because a linter noticed the gap the step was written to close.**
137
+ * The replay step captured `Idempotency-Replayed` and then did nothing with
138
+ * it, so the published quickstart *demonstrated* idempotency without ever
139
+ * *checking* it: a service that quietly created a second schedule would have
140
+ * produced a transcript that read exactly the same, and a reader following the
141
+ * page would have two schedules and two callbacks.
142
+ *
143
+ * `equals` may name another variable with `{{…}}`, which is how the replay
144
+ * proves it got the same schedule back rather than merely a schedule.
145
+ */
146
+ export interface JourneyExpectation {
147
+ /** The value it must have — a literal, or a `{{VARIABLE}}` reference. */
148
+ readonly equals: string;
149
+ /** What it means when it does not, in the reader's terms. */
150
+ readonly message: string;
151
+ /** The captured variable being checked. */
152
+ readonly subject: string;
153
+ }
154
+ /** A bounded wait, for the one step whose answer arrives when it arrives. */
155
+ export interface JourneyPoll {
156
+ /** Human sentence describing the condition, for the generated prose. */
157
+ readonly describe: string;
158
+ readonly intervalSeconds: number;
159
+ readonly timeoutSeconds: number;
160
+ }
161
+ export interface JourneyStep {
162
+ readonly capture?: readonly JourneyCapture[];
163
+ /** What this step checks about what it captured. */
164
+ readonly expect?: readonly JourneyExpectation[];
165
+ /** Stable machine name for this step, used by every consumer. */
166
+ readonly key: string;
167
+ /** The operation this step calls, by registry id. */
168
+ readonly operationId: string;
169
+ /** What the reader should see when it worked. */
170
+ readonly observe: string;
171
+ readonly poll?: JourneyPoll;
172
+ /** Why this step is in the journey at all. */
173
+ readonly purpose: string;
174
+ /**
175
+ * Query parameters, as literal strings. Values may carry `{{VARIABLE}}`.
176
+ */
177
+ readonly query?: Readonly<Record<string, string>>;
178
+ readonly recovery: readonly JourneyRecovery[];
179
+ /**
180
+ * The request body. String values may carry `{{VARIABLE}}` references, which
181
+ * are resolved against the declared placeholders before the body is
182
+ * validated against the operation's own request schema.
183
+ */
184
+ readonly request?: unknown;
185
+ /** Request headers this step sends, with the same `{{VARIABLE}}` support. */
186
+ readonly requestHeaders?: Readonly<Record<string, string>>;
187
+ readonly title: string;
188
+ }
189
+ /**
190
+ * A variable name, and a placeholder that is provably not a credential.
191
+ *
192
+ * The three patterns are the product's own credential shapes, taken from
193
+ * `scripts/secret-patterns.ts` in spirit rather than by import — this package
194
+ * is the contract and may not depend on a repository script. A generated page
195
+ * carrying a real one would be a leak that no reviewer reads carefully enough
196
+ * to catch, so the refusal is here rather than in a convention.
197
+ */
198
+ export declare const journeyVariableSchema: z.ZodObject<{
199
+ defaultExpression: z.ZodOptional<z.ZodObject<{
200
+ javascript: z.ZodString;
201
+ shell: z.ZodString;
202
+ }, z.core.$strip>>;
203
+ detail: z.ZodString;
204
+ name: z.ZodString;
205
+ placeholder: z.ZodString;
206
+ secret: z.ZodBoolean;
207
+ title: z.ZodString;
208
+ }, z.core.$strip>;
209
+ export declare const journeyStepSchema: z.ZodObject<{
210
+ capture: z.ZodOptional<z.ZodArray<z.ZodObject<{
211
+ from: z.ZodString;
212
+ header: z.ZodOptional<z.ZodBoolean>;
213
+ secret: z.ZodOptional<z.ZodBoolean>;
214
+ variable: z.ZodString;
215
+ }, z.core.$strip>>>;
216
+ expect: z.ZodOptional<z.ZodArray<z.ZodObject<{
217
+ equals: z.ZodString;
218
+ message: z.ZodString;
219
+ subject: z.ZodString;
220
+ }, z.core.$strip>>>;
221
+ key: z.ZodString;
222
+ observe: z.ZodString;
223
+ operationId: z.ZodString;
224
+ poll: z.ZodOptional<z.ZodObject<{
225
+ describe: z.ZodString;
226
+ intervalSeconds: z.ZodNumber;
227
+ timeoutSeconds: z.ZodNumber;
228
+ }, z.core.$strip>>;
229
+ purpose: z.ZodString;
230
+ query: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
231
+ recovery: z.ZodArray<z.ZodObject<{
232
+ nextMove: z.ZodString;
233
+ when: z.ZodString;
234
+ }, z.core.$strip>>;
235
+ request: z.ZodOptional<z.ZodUnknown>;
236
+ requestHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
237
+ title: z.ZodString;
238
+ }, z.core.$strip>;
239
+ /**
240
+ * The lowest curl that runs the generated commands.
241
+ *
242
+ * `--variable` and `--expand-header` arrived in curl 8.3.0, and they are what
243
+ * keeps the credential out of the process arguments: the command line names
244
+ * `%GREATWAIT_API_KEY`, and curl reads the value out of its own environment.
245
+ * The ordinary `-H "Authorization: Bearer $GREATWAIT_API_KEY"` spelling puts
246
+ * the secret in `argv`, where any other user on the machine can read it out of
247
+ * `ps`.
248
+ *
249
+ * Stated as a constant because the requirement is published in three places —
250
+ * both quickstarts and the journey artifact — and a version number written
251
+ * three times is a version number that will disagree with itself.
252
+ */
253
+ export declare const MINIMUM_CURL_VERSION = "8.3.0";
254
+ /** The tools the shell rendering of the journey needs on the reader's PATH. */
255
+ export declare const CURL_JOURNEY_TOOLS: readonly [{
256
+ readonly name: "curl";
257
+ readonly version: "8.3.0";
258
+ }, {
259
+ readonly name: "jq";
260
+ readonly version: "1.6";
261
+ }];
262
+ export declare const FIRST_VALUE_PREREQUISITES: readonly JourneyPrerequisite[];
263
+ export declare const FIRST_VALUE_VARIABLES: readonly JourneyVariable[];
264
+ /**
265
+ * The sequence.
266
+ *
267
+ * Nine steps, and the shape of them is the acceptance criterion: verify the
268
+ * context and the credential, create a destination, create an idempotent
269
+ * one-shot, prove the idempotency, read the schedule back, wait for the
270
+ * occurrence to settle, read the delivery attempt, and clean up. Creating a
271
+ * resource is not first value; **step `read-attempt` is**, because it is the
272
+ * first point at which the customer can read what happened at their own
273
+ * receiver.
274
+ *
275
+ * `delaySeconds` rather than `at`, deliberately. An absolute instant published
276
+ * in an example is stale the day after it is written, and a reader who pastes
277
+ * it is refused for a reason that has nothing to do with what they were
278
+ * learning. A delay is converted to an instant when the request is accepted,
279
+ * so the schedule reads back afterwards exactly as an `at` schedule would.
280
+ */
281
+ export declare const FIRST_VALUE_STEPS: readonly JourneyStep[];
282
+ export interface FirstValueJourney {
283
+ readonly prerequisites: readonly JourneyPrerequisite[];
284
+ readonly steps: readonly JourneyStep[];
285
+ readonly variables: readonly JourneyVariable[];
286
+ }
287
+ export declare const FIRST_VALUE_JOURNEY: FirstValueJourney;
288
+ /** The declared variable of that name, or `undefined`. */
289
+ export declare function journeyVariable(name: string): JourneyVariable | undefined;
290
+ /** Every `{{VARIABLE}}` reference inside a value, in first-appearance order. */
291
+ export declare function journeyReferences(value: unknown): readonly string[];
292
+ /**
293
+ * Replaces every `{{VARIABLE}}` reference using `resolve`.
294
+ *
295
+ * A whole-string reference becomes the resolved value; an embedded one is
296
+ * substituted in place. An unresolved name throws rather than leaving the
297
+ * braces behind, because a `{{…}}` that reached a published page is a page
298
+ * that teaches a reader to send a literal brace.
299
+ */
300
+ export declare function resolveJourneyReferences<T>(value: T, resolve: (name: string) => string | undefined): T;
301
+ /**
302
+ * The variables a step's own capture list produces, so a consumer can tell a
303
+ * value the reader supplies from one an earlier call handed them.
304
+ */
305
+ export declare function journeyCapturedVariables(): readonly string[];