@greatwait/contracts 0.1.0-alpha.5 → 0.1.0-alpha.7

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 (135) hide show
  1. package/README.md +1 -1
  2. package/dist/attempt-diagnostics.d.ts +8 -0
  3. package/dist/attempt-diagnostics.js +68 -0
  4. package/dist/authority.d.ts +4 -1
  5. package/dist/authority.js +1 -0
  6. package/dist/billing.d.ts +11 -10
  7. package/dist/billing.js +7 -4
  8. package/dist/data-export.d.ts +2 -0
  9. package/dist/delivery.d.ts +43 -40
  10. package/dist/delivery.js +14 -37
  11. package/dist/effective-limits.d.ts +4 -4
  12. package/dist/endpoints.d.ts +173 -5
  13. package/dist/endpoints.js +72 -1
  14. package/dist/first-value-journey.js +4 -4
  15. package/dist/http-request.d.ts +193 -0
  16. package/dist/http-request.js +305 -0
  17. package/dist/identity.d.ts +7 -7
  18. package/dist/index.d.ts +6 -3
  19. package/dist/index.js +5 -2
  20. package/dist/mcp.d.ts +2 -1
  21. package/dist/members.d.ts +141 -0
  22. package/dist/members.js +223 -0
  23. package/dist/oauth.d.ts +7 -0
  24. package/dist/operation-definitions.d.ts +20 -4
  25. package/dist/operation-definitions.js +9 -1
  26. package/dist/operations.d.ts +999 -135
  27. package/dist/operations.js +83 -10
  28. package/dist/organization-lifecycle.d.ts +10 -0
  29. package/dist/policy.js +5 -2
  30. package/dist/scheduling-records.d.ts +332 -8
  31. package/dist/scheduling-records.js +5 -0
  32. package/dist/scheduling.d.ts +485 -18
  33. package/dist/scheduling.js +77 -5
  34. package/dist/secrets.d.ts +7 -0
  35. package/dist/signatures.d.ts +1 -1
  36. package/dist/tenancy.d.ts +1 -1
  37. package/dist/tenancy.js +1 -1
  38. package/dist/test-sink.d.ts +27 -41
  39. package/dist/test-sink.js +12 -31
  40. package/dist/zod-values.d.ts +2 -2
  41. package/dist/zod-values.js +1 -1
  42. package/generated/v1/callback-receiver.json +4 -4
  43. package/generated/v1/examples/endpoints.ensure.request.json +3 -0
  44. package/generated/v1/examples/endpoints.ensure.response.json +20 -0
  45. package/generated/v1/examples/invitations.create.request.json +5 -0
  46. package/generated/v1/examples/invitations.create.response.json +7 -0
  47. package/generated/v1/examples/invitations.list.response.json +11 -0
  48. package/generated/v1/examples/invitations.resend.request.json +1 -0
  49. package/generated/v1/examples/invitations.resend.response.json +4 -0
  50. package/generated/v1/examples/invitations.revoke.request.json +3 -0
  51. package/generated/v1/examples/invitations.revoke.response.json +4 -0
  52. package/generated/v1/examples/members.list.response.json +13 -0
  53. package/generated/v1/examples/members.remove.request.json +3 -0
  54. package/generated/v1/examples/members.remove.response.json +4 -0
  55. package/generated/v1/examples/members.role.request.json +3 -0
  56. package/generated/v1/examples/members.role.response.json +4 -0
  57. package/generated/v1/examples/projectmembers.add.request.json +1 -0
  58. package/generated/v1/examples/projectmembers.add.response.json +4 -0
  59. package/generated/v1/examples/projectmembers.remove.request.json +3 -0
  60. package/generated/v1/examples/projectmembers.remove.response.json +4 -0
  61. package/generated/v1/first-value-journey.json +4 -4
  62. package/generated/v1/manifest.json +284 -0
  63. package/generated/v1/openapi.json +9387 -1021
  64. package/generated/v1/policy-matrix.json +72 -0
  65. package/generated/v1/schemas/attempts.list.response.json +105 -0
  66. package/generated/v1/schemas/attempts.read.response.json +105 -0
  67. package/generated/v1/schemas/billing.notices.response.json +16 -6
  68. package/generated/v1/schemas/context.read.response.json +1 -0
  69. package/generated/v1/schemas/credentials.connection.response.json +1 -0
  70. package/generated/v1/schemas/credentials.create.request.json +1 -0
  71. package/generated/v1/schemas/credentials.create.response.json +1 -0
  72. package/generated/v1/schemas/credentials.list.response.json +1 -0
  73. package/generated/v1/schemas/credentials.revoke.response.json +1 -0
  74. package/generated/v1/schemas/credentials.rotate.response.json +2 -0
  75. package/generated/v1/schemas/endpoints.acknowledgeSigningKeyRotation.response.json +59 -0
  76. package/generated/v1/schemas/endpoints.create.request.json +74 -0
  77. package/generated/v1/schemas/endpoints.create.response.json +59 -0
  78. package/generated/v1/schemas/endpoints.disable.response.json +59 -0
  79. package/generated/v1/schemas/endpoints.enable.response.json +59 -0
  80. package/generated/v1/schemas/endpoints.ensure.request.json +91 -0
  81. package/generated/v1/schemas/endpoints.ensure.response.json +169 -0
  82. package/generated/v1/schemas/endpoints.list.response.json +59 -0
  83. package/generated/v1/schemas/endpoints.read.response.json +59 -0
  84. package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +59 -0
  85. package/generated/v1/schemas/endpoints.update.request.json +74 -0
  86. package/generated/v1/schemas/endpoints.update.response.json +59 -0
  87. package/generated/v1/schemas/grants.list.response.json +1 -0
  88. package/generated/v1/schemas/grants.revoke.response.json +1 -0
  89. package/generated/v1/schemas/invitations.create.request-headers.json +13 -0
  90. package/generated/v1/schemas/invitations.create.request.json +24 -0
  91. package/generated/v1/schemas/invitations.create.response.json +31 -0
  92. package/generated/v1/schemas/invitations.list.response.json +40 -0
  93. package/generated/v1/schemas/invitations.resend.request-headers.json +13 -0
  94. package/generated/v1/schemas/invitations.resend.request.json +6 -0
  95. package/generated/v1/schemas/invitations.resend.response.json +27 -0
  96. package/generated/v1/schemas/invitations.revoke.request-headers.json +13 -0
  97. package/generated/v1/schemas/invitations.revoke.request.json +12 -0
  98. package/generated/v1/schemas/invitations.revoke.response.json +27 -0
  99. package/generated/v1/schemas/members.list.response.json +53 -0
  100. package/generated/v1/schemas/members.remove.request-headers.json +13 -0
  101. package/generated/v1/schemas/members.remove.request.json +12 -0
  102. package/generated/v1/schemas/members.remove.response.json +27 -0
  103. package/generated/v1/schemas/members.role.request-headers.json +13 -0
  104. package/generated/v1/schemas/members.role.request.json +12 -0
  105. package/generated/v1/schemas/members.role.response.json +27 -0
  106. package/generated/v1/schemas/occurrences.redeliver.response.json +105 -0
  107. package/generated/v1/schemas/projectmembers.add.request-headers.json +13 -0
  108. package/generated/v1/schemas/projectmembers.add.request.json +6 -0
  109. package/generated/v1/schemas/projectmembers.add.response.json +27 -0
  110. package/generated/v1/schemas/projectmembers.remove.request-headers.json +13 -0
  111. package/generated/v1/schemas/projectmembers.remove.request.json +12 -0
  112. package/generated/v1/schemas/projectmembers.remove.response.json +27 -0
  113. package/generated/v1/schemas/schedules.archive.response.json +188 -1
  114. package/generated/v1/schemas/schedules.cancel.response.json +188 -1
  115. package/generated/v1/schemas/schedules.create.request.json +164 -1
  116. package/generated/v1/schemas/schedules.create.response.json +206 -1
  117. package/generated/v1/schemas/schedules.list.response.json +188 -1
  118. package/generated/v1/schemas/schedules.pause.response.json +188 -1
  119. package/generated/v1/schemas/schedules.read.response.json +188 -1
  120. package/generated/v1/schemas/schedules.replace.request.json +164 -1
  121. package/generated/v1/schemas/schedules.replace.response.json +206 -1
  122. package/generated/v1/schemas/schedules.resume.response.json +188 -1
  123. package/generated/v1/snippets/endpoints.create.json +1 -1
  124. package/generated/v1/snippets/endpoints.ensure.json +6 -0
  125. package/generated/v1/snippets/invitations.create.json +6 -0
  126. package/generated/v1/snippets/invitations.list.json +6 -0
  127. package/generated/v1/snippets/invitations.resend.json +6 -0
  128. package/generated/v1/snippets/invitations.revoke.json +6 -0
  129. package/generated/v1/snippets/journey/create-destination.json +1 -1
  130. package/generated/v1/snippets/members.list.json +6 -0
  131. package/generated/v1/snippets/members.remove.json +6 -0
  132. package/generated/v1/snippets/members.role.json +6 -0
  133. package/generated/v1/snippets/projectmembers.add.json +6 -0
  134. package/generated/v1/snippets/projectmembers.remove.json +6 -0
  135. package/package.json +1 -1
@@ -1,7 +1,9 @@
1
+ import { httpRequestSchema, publicHttpRequestSchema } from "./http-request.js";
1
2
  import { FIXED_INTERVAL_MIN_SECONDS, FIXED_INTERVAL_MAX_SECONDS, MAX_INLINE_PAYLOAD_BYTES, } from "./configuration-bounds.js";
2
3
  import { z } from "./zod.js";
3
4
  import { attemptIdSchema, deliveryIdSchema, endpointIdSchema, occurrenceIdSchema, scheduleIdSchema, } from "./identifiers.js";
4
5
  import { CALLBACK_ATTEMPT_DIAGNOSTICS } from "./delivery.js";
6
+ import { callbackUrlSchema, signingSecretNoticeSchema } from "./endpoints.js";
5
7
  import { pageRequestSchema, pageSchema, versionSchema, } from "./protocol.js";
6
8
  import { callbackTriggerSchema, } from "./signatures.js";
7
9
  import { durationSecondsSchema, timeZoneSchema, timestampSchema } from "./time.js";
@@ -47,6 +49,17 @@ export const SCHEDULE_STATUSES = [
47
49
  "completed",
48
50
  ];
49
51
  export const scheduleStatusSchema = z.enum(SCHEDULE_STATUSES);
52
+ /** A recurring schedule pauses after this many failed runs; "never" disables it. */
53
+ export const DEFAULT_AUTO_PAUSE_AFTER_FAILURES = 5;
54
+ export const autoPauseAfterFailuresSchema = z
55
+ .union([z.number().int().min(1).max(100), z.literal("never")])
56
+ .describe('Consecutive failed runs before automatic pause: 1–100 or "never". Defaults to 5 on create; omission on replace preserves the setting. One-shot schedules are unaffected.');
57
+ export const autoPauseReasonSchema = z.object({
58
+ failedRuns: z.number().int().positive(),
59
+ status: z.number().int().min(100).max(599).optional(),
60
+ diagnostic: z.string().max(500),
61
+ pausedAt: timestampSchema,
62
+ });
50
63
  /**
51
64
  * The statuses a schedule never leaves, and from which it never fires again on
52
65
  * its own.
@@ -818,6 +831,10 @@ export const scheduleNameSchema = z
818
831
  .min(1, "must not be blank")
819
832
  .max(SCHEDULE_NAME_MAX_LENGTH, `must be at most ${String(SCHEDULE_NAME_MAX_LENGTH)} characters`);
820
833
  export const scheduleSchema = z.object({
834
+ autoPauseAfterFailures: autoPauseAfterFailuresSchema.optional(),
835
+ consecutiveFailedRuns: z.number().int().nonnegative().optional(),
836
+ autoPauseReason: autoPauseReasonSchema.nullable().optional(),
837
+ http: publicHttpRequestSchema.optional(),
821
838
  /**
822
839
  * When the schedule was filed away, and absent until it is.
823
840
  *
@@ -1062,6 +1079,30 @@ export const attemptFailureSnippetSchema = z.object({
1062
1079
  text: z.string().max(ATTEMPT_FAILURE_SNIPPET_MAX_LENGTH),
1063
1080
  truncated: z.boolean(),
1064
1081
  });
1082
+ /** Stored diagnostic content, available only under the attempt's verified authority. */
1083
+ export const attemptCapturedBodySchema = z.object({
1084
+ contentType: z.string(),
1085
+ encoding: z.enum(["utf8", "base64"]),
1086
+ // Up to 10 KiB received bytes; credential masking can expand the representation.
1087
+ content: z.string().max(131_072),
1088
+ capturedBytes: z.int().min(1).max(10_240),
1089
+ originalBytes: z.int().nonnegative().optional(),
1090
+ truncated: z.boolean(),
1091
+ redacted: z.boolean().optional(),
1092
+ incomplete: z.boolean().optional(),
1093
+ });
1094
+ export const attemptResponseSchema = z.object({
1095
+ statusText: z.string(),
1096
+ headers: z.array(z.object({ name: z.string(), value: z.string() })),
1097
+ bodyState: z.enum(["captured", "empty", "expired", "not_captured"]),
1098
+ body: attemptCapturedBodySchema.optional(),
1099
+ });
1100
+ export const attemptRequestSchema = z.object({
1101
+ method: z.enum(["GET", "POST", "PUT"]),
1102
+ url: z.string(),
1103
+ headers: z.array(z.object({ name: z.string(), value: z.string() })),
1104
+ body: z.string(),
1105
+ });
1065
1106
  export const attemptSchema = z
1066
1107
  .object({
1067
1108
  attemptNumber: z.number().int().positive(),
@@ -1106,13 +1147,15 @@ export const attemptSchema = z
1106
1147
  /** Stable next-action code paired with a diagnostic when one is known. */
1107
1148
  remediation: attemptRemediationSchema.optional(),
1108
1149
  /**
1109
- * What a failing receiver said, where the plan retains it.
1110
- *
1111
- * Absent on a success, on an in-flight attempt, on a response whose media
1112
- * type is not text or JSON, and on a plan whose retention does not include
1113
- * one. Its absence is never evidence about the delivery.
1150
+ * Compatibility text prefix, up to 4 KiB, derived from the retained
1151
+ * response when its media type is supported by legacy clients. Use
1152
+ * response for all headers, media types and the full 10 KiB capture.
1153
+ * Absence is never evidence about the delivery outcome.
1114
1154
  */
1115
1155
  failureSnippet: attemptFailureSnippetSchema.optional(),
1156
+ response: attemptResponseSchema.optional(),
1157
+ request: attemptRequestSchema.optional(),
1158
+ requestState: z.enum(["captured", "expired", "not_captured"]).optional(),
1116
1159
  /** The fixed diagnostic-header allowlist, when a response carried any. */
1117
1160
  responseHeaders: z.array(attemptResponseHeaderSchema).max(8).optional(),
1118
1161
  /** Absent when no response was received at all. */
@@ -1194,6 +1237,15 @@ export const scheduleViewSchema = z.object({
1194
1237
  occurrence: occurrenceSchema,
1195
1238
  schedule: scheduleSchema,
1196
1239
  });
1240
+ /**
1241
+ * What a schedule create or replace answers: the schedule view, and — when the
1242
+ * request named its destination by `url` — where that endpoint's signing
1243
+ * secret is (W9-44). Present only then, because only then can the caller have
1244
+ * an endpoint whose secret it never held.
1245
+ */
1246
+ export const scheduleWrittenViewSchema = scheduleViewSchema.extend({
1247
+ endpointSigningSecret: signingSecretNoticeSchema.optional(),
1248
+ });
1197
1249
  /**
1198
1250
  * The four ways a caller may say when a schedule happens.
1199
1251
  *
@@ -1225,6 +1277,8 @@ export const RECURRENCE_REQUEST_FIELDS = [
1225
1277
  */
1226
1278
  export const scheduleRequestSchema = z
1227
1279
  .object({
1280
+ autoPauseAfterFailures: autoPauseAfterFailuresSchema.optional(),
1281
+ http: httpRequestSchema.optional(),
1228
1282
  /** The instant the first occurrence is planned for. */
1229
1283
  at: timestampSchema.optional(),
1230
1284
  /**
@@ -1313,6 +1367,15 @@ export const scheduleRequestSchema = z
1313
1367
  * valid only when the stored schedule already has an endpoint.
1314
1368
  */
1315
1369
  endpoint: endpointIdSchema.optional(),
1370
+ /**
1371
+ * A callback destination named by its address instead of by an endpoint
1372
+ * id (W9-44, FXO-2907). The service uses the project's existing endpoint
1373
+ * at exactly this address, or creates one silently and returns no signing
1374
+ * secret — the response's `endpointSigningSecret` says where a person
1375
+ * reveals it. Reusing needs only `schedules:write`; creating also needs
1376
+ * `endpoints:write`. Give this or `endpoint`, not both.
1377
+ */
1378
+ url: callbackUrlSchema().optional(),
1316
1379
  /**
1317
1380
  * How promptly this schedule's occurrences expect to be served.
1318
1381
  *
@@ -1400,6 +1463,15 @@ export const scheduleRequestSchema = z
1400
1463
  });
1401
1464
  }
1402
1465
  }
1466
+ if (request.url !== undefined && request.endpoint !== undefined) {
1467
+ for (const path of ["endpoint", "url"]) {
1468
+ context.addIssue({
1469
+ code: "custom",
1470
+ message: "give `endpoint` or `url`, not both; `url` finds or creates the endpoint at that address",
1471
+ path: [path],
1472
+ });
1473
+ }
1474
+ }
1403
1475
  if (request.timeZone !== undefined && request.cron === undefined) {
1404
1476
  context.addIssue({
1405
1477
  code: "custom",
package/dist/secrets.d.ts CHANGED
@@ -47,6 +47,7 @@ export declare const credentialMetadataSchema: import("zod").ZodObject<{
47
47
  revokedAt: import("zod").ZodOptional<import("zod").ZodString>;
48
48
  supersededBy: import("zod").ZodOptional<import("zod").ZodString>;
49
49
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
50
+ "members:manage": "members:manage";
50
51
  "credentials:administer": "credentials:administer";
51
52
  "billing:write": "billing:write";
52
53
  "credentials:read": "credentials:read";
@@ -95,6 +96,7 @@ export declare const credentialPageSchema: import("zod").ZodObject<{
95
96
  revokedAt: import("zod").ZodOptional<import("zod").ZodString>;
96
97
  supersededBy: import("zod").ZodOptional<import("zod").ZodString>;
97
98
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
99
+ "members:manage": "members:manage";
98
100
  "credentials:administer": "credentials:administer";
99
101
  "billing:write": "billing:write";
100
102
  "credentials:read": "credentials:read";
@@ -143,6 +145,7 @@ export declare const credentialCreatedSchema: import("zod").ZodObject<{
143
145
  revokedAt: import("zod").ZodOptional<import("zod").ZodString>;
144
146
  supersededBy: import("zod").ZodOptional<import("zod").ZodString>;
145
147
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
148
+ "members:manage": "members:manage";
146
149
  "credentials:administer": "credentials:administer";
147
150
  "billing:write": "billing:write";
148
151
  "credentials:read": "credentials:read";
@@ -181,6 +184,7 @@ export declare const credentialRequestSchema: import("zod").ZodObject<{
181
184
  }>;
182
185
  project: import("zod").ZodString;
183
186
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
187
+ "members:manage": "members:manage";
184
188
  "credentials:administer": "credentials:administer";
185
189
  "billing:write": "billing:write";
186
190
  "credentials:read": "credentials:read";
@@ -250,6 +254,7 @@ export declare const credentialRotatedSchema: import("zod").ZodObject<{
250
254
  revokedAt: import("zod").ZodOptional<import("zod").ZodString>;
251
255
  supersededBy: import("zod").ZodOptional<import("zod").ZodString>;
252
256
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
257
+ "members:manage": "members:manage";
253
258
  "credentials:administer": "credentials:administer";
254
259
  "billing:write": "billing:write";
255
260
  "credentials:read": "credentials:read";
@@ -286,6 +291,7 @@ export declare const credentialRotatedSchema: import("zod").ZodObject<{
286
291
  revokedAt: import("zod").ZodOptional<import("zod").ZodString>;
287
292
  supersededBy: import("zod").ZodOptional<import("zod").ZodString>;
288
293
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
294
+ "members:manage": "members:manage";
289
295
  "credentials:administer": "credentials:administer";
290
296
  "billing:write": "billing:write";
291
297
  "credentials:read": "credentials:read";
@@ -333,6 +339,7 @@ export declare const credentialRevokedSchema: import("zod").ZodObject<{
333
339
  revokedAt: import("zod").ZodOptional<import("zod").ZodString>;
334
340
  supersededBy: import("zod").ZodOptional<import("zod").ZodString>;
335
341
  scopes: import("zod").ZodArray<import("zod").ZodEnum<{
342
+ "members:manage": "members:manage";
336
343
  "credentials:administer": "credentials:administer";
337
344
  "billing:write": "billing:write";
338
345
  "credentials:read": "credentials:read";
@@ -182,8 +182,8 @@ export type CallbackDeferredCompletion = z.infer<typeof callbackDeferredCompleti
182
182
  */
183
183
  export declare const callbackCompletionReportSchema: import("zod").ZodObject<{
184
184
  outcome: import("zod").ZodDefault<import("zod").ZodEnum<{
185
- failed: "failed";
186
185
  succeeded: "succeeded";
186
+ failed: "failed";
187
187
  }>>;
188
188
  }, import("zod/v4/core").$strip>;
189
189
  export type CallbackCompletionReport = z.infer<typeof callbackCompletionReportSchema>;
package/dist/tenancy.d.ts CHANGED
@@ -177,7 +177,7 @@ export declare const projectDeletionCountsSchema: import("zod").ZodObject<{
177
177
  }, import("zod/v4/core").$strip>;
178
178
  export type ProjectDeletionCounts = z.infer<typeof projectDeletionCountsSchema>;
179
179
  /**
180
- * Every active or archived project in the member's organization.
180
+ * Every active or archived project the member can access. Administrators see all.
181
181
  *
182
182
  * **Unpaged, with a stated ceiling, because the store beneath it is.**
183
183
  * `listProjects` reads an organization's projects in one go; declaring a page
package/dist/tenancy.js CHANGED
@@ -149,7 +149,7 @@ export const projectSummarySchema = z.object({
149
149
  });
150
150
  export const projectDeletionCountsSchema = projectSummarySchema.shape.deletionCounts;
151
151
  /**
152
- * Every active or archived project in the member's organization.
152
+ * Every active or archived project the member can access. Administrators see all.
153
153
  *
154
154
  * **Unpaged, with a stated ceiling, because the store beneath it is.**
155
155
  * `listProjects` reads an organization's projects in one go; declaring a page
@@ -55,8 +55,8 @@ import { type CallbackCompletionOutcome } from "./delivery.js";
55
55
  */
56
56
  export declare const TEST_SINK_BEHAVIORS: readonly ["client_error", "defer_abandon", "defer_fail", "defer_succeed", "delay", "disconnect", "duplicate", "invalid_signature", "rate_limited", "server_error", "success", "timeout"];
57
57
  export declare const testSinkBehaviorSchema: import("zod").ZodEnum<{
58
- rate_limited: "rate_limited";
59
58
  success: "success";
59
+ rate_limited: "rate_limited";
60
60
  server_error: "server_error";
61
61
  client_error: "client_error";
62
62
  defer_abandon: "defer_abandon";
@@ -118,14 +118,9 @@ export declare const TEST_SINK_BOUNDS: {
118
118
  /**
119
119
  * Selected 4xx a sink may answer with.
120
120
  *
121
- * **They are no longer all terminal to the sender, and which are is not this
122
- * file's decision.** FXO-1596 moved the refusals — a receiver declining *this
123
- * delivery now* rather than objecting to the message — onto the receiver's
124
- * ordinary five-failure budget. `TEST_SINK_REFUSAL_STATUSES` below is the
125
- * subset of this list that the sender will retry, and `test-sink.test.ts`
126
- * holds it equal to what `classifyStatus` actually says, so a status moved in
127
- * `CALLBACK_REFUSAL_STATUSES` and not here fails a test rather than teaching an
128
- * integrator the wrong lesson.
121
+ * The sender settles these selected statuses on the first attempt. W9-42
122
+ * stops retrying 401/403/404/405 to avoid billing for a broken configuration.
123
+ * The broader classification is owned by CALLBACK_REFUSAL_STATUSES.
129
124
  *
130
125
  * Nothing is removed from what a caller may request: every status here was
131
126
  * accepted before and still is.
@@ -134,11 +129,9 @@ export declare const TEST_SINK_CLIENT_ERROR_STATUSES: readonly [400, 401, 403, 4
134
129
  /**
135
130
  * The statuses above that the sender retries rather than settling on.
136
131
  *
137
- * Exists so an integrator simulating a rotated credential (401) or a WAF rule
138
- * (403) can see five attempts arrive, and one simulating a malformed body (400)
139
- * or a duplicate (409) can see exactly one.
132
+ * Every selected client-error status is terminal under W9-42.
140
133
  */
141
- export declare const TEST_SINK_REFUSAL_STATUSES: readonly [401, 403, 404];
134
+ export declare const TEST_SINK_REFUSAL_STATUSES: readonly [];
142
135
  /** Selected 5xx a sink may answer with. Each is retryable to the sender. */
143
136
  export declare const TEST_SINK_SERVER_ERROR_STATUSES: readonly [500, 502, 503, 504];
144
137
  /**
@@ -156,8 +149,8 @@ export declare const TEST_SINK_SERVER_ERROR_STATUSES: readonly [500, 502, 503, 5
156
149
  */
157
150
  export declare const testSinkProgramSchema: import("zod").ZodObject<{
158
151
  behavior: import("zod").ZodEnum<{
159
- rate_limited: "rate_limited";
160
152
  success: "success";
153
+ rate_limited: "rate_limited";
161
154
  server_error: "server_error";
162
155
  client_error: "client_error";
163
156
  defer_abandon: "defer_abandon";
@@ -175,8 +168,8 @@ export declare const testSinkProgramSchema: import("zod").ZodObject<{
175
168
  retryAfterSeconds: import("zod").ZodOptional<import("zod").ZodNumber>;
176
169
  status: import("zod").ZodOptional<import("zod").ZodNumber>;
177
170
  then: import("zod").ZodOptional<import("zod").ZodEnum<{
178
- rate_limited: "rate_limited";
179
171
  success: "success";
172
+ rate_limited: "rate_limited";
180
173
  server_error: "server_error";
181
174
  client_error: "client_error";
182
175
  defer_abandon: "defer_abandon";
@@ -213,7 +206,7 @@ export declare const TEST_SINK_BEHAVIOR_FACTS: {
213
206
  readonly deferredReport: undefined;
214
207
  readonly receiverActed: false;
215
208
  readonly senderOutcome: "terminal";
216
- readonly summary: "Answers a selected 4xx. The sender records a customer refusal, and retries it only when the status is one the receiver may recover from — 401, 403 and 404, listed as `clientErrorStatusesRetried`. The default, 422, is not one of those, so it settles on the first attempt.";
209
+ readonly summary: "Answers a selected 4xx. Every selected status settles the run on the first attempt, including 401, 403 and 404. Fix the destination, then redeliver manually.";
217
210
  };
218
211
  readonly defer_abandon: {
219
212
  readonly defaultStatus: 202;
@@ -261,8 +254,8 @@ export declare const TEST_SINK_BEHAVIOR_FACTS: {
261
254
  readonly defaultStatus: 401;
262
255
  readonly deferredReport: undefined;
263
256
  readonly receiverActed: false;
264
- readonly senderOutcome: "retryable";
265
- readonly summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The sender retries on the receiver's ordinary budget, because the usual cause is a credential the receiver is about to redeploy.";
257
+ readonly senderOutcome: "terminal";
258
+ readonly summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The run stops after one attempt; fix the key and redeliver manually.";
266
259
  };
267
260
  readonly rate_limited: {
268
261
  readonly defaultStatus: 429;
@@ -439,16 +432,9 @@ export type TestSinkDisposition = z.infer<typeof testSinkDispositionSchema>;
439
432
  * the network for six minutes is not, and every attempt re-signs with a fresh
440
433
  * timestamp, so the transient case self-heals and the permanent case exhausts
441
434
  * the attempt bound honestly.
442
- * - `invalid_signature`, `invalid_headers` — 401, **retryable since
443
- * FXO-1596**, and this entry used to say the opposite. The reasoning it
444
- * replaces was that retrying with the same key cannot succeed and a retryable
445
- * answer would spend the give-up budget hiding a misconfiguration. That is
446
- * true of a mismatch in *our* signing and false of the population that
447
- * actually reaches a 401: a receiver holding a credential it has rotated,
448
- * expired, or not yet deployed. Those recover on the receiver's next release,
449
- * and the delivery after it carries a fresh signature. The misconfiguration
450
- * is not hidden either — five identical 401s settle the occurrence 82 seconds
451
- * later with the same `inspect_receiver` advice, five rows instead of one.
435
+ * - `invalid_signature`, `invalid_headers` — 401, terminal since W9-42.
436
+ * Repeating a broken credential spends paid attempts without fixing it.
437
+ * Correct the key or receiver configuration, then redeliver manually.
452
438
  * - `invalid_envelope` — 400, terminal. The bytes authenticated and did not
453
439
  * parse, so the sender and receiver disagree about the contract.
454
440
  * - `body_too_large` — 413, terminal, and matched to the `payload_too_large`
@@ -500,8 +486,8 @@ export declare const testSinkSchema: import("zod").ZodObject<{
500
486
  id: import("zod").ZodString;
501
487
  program: import("zod").ZodObject<{
502
488
  behavior: import("zod").ZodEnum<{
503
- rate_limited: "rate_limited";
504
489
  success: "success";
490
+ rate_limited: "rate_limited";
505
491
  server_error: "server_error";
506
492
  client_error: "client_error";
507
493
  defer_abandon: "defer_abandon";
@@ -519,8 +505,8 @@ export declare const testSinkSchema: import("zod").ZodObject<{
519
505
  retryAfterSeconds: import("zod").ZodOptional<import("zod").ZodNumber>;
520
506
  status: import("zod").ZodOptional<import("zod").ZodNumber>;
521
507
  then: import("zod").ZodOptional<import("zod").ZodEnum<{
522
- rate_limited: "rate_limited";
523
508
  success: "success";
509
+ rate_limited: "rate_limited";
524
510
  server_error: "server_error";
525
511
  client_error: "client_error";
526
512
  defer_abandon: "defer_abandon";
@@ -547,8 +533,8 @@ export declare const testSinkPageSchema: import("zod").ZodObject<{
547
533
  id: import("zod").ZodString;
548
534
  program: import("zod").ZodObject<{
549
535
  behavior: import("zod").ZodEnum<{
550
- rate_limited: "rate_limited";
551
536
  success: "success";
537
+ rate_limited: "rate_limited";
552
538
  server_error: "server_error";
553
539
  client_error: "client_error";
554
540
  defer_abandon: "defer_abandon";
@@ -566,8 +552,8 @@ export declare const testSinkPageSchema: import("zod").ZodObject<{
566
552
  retryAfterSeconds: import("zod").ZodOptional<import("zod").ZodNumber>;
567
553
  status: import("zod").ZodOptional<import("zod").ZodNumber>;
568
554
  then: import("zod").ZodOptional<import("zod").ZodEnum<{
569
- rate_limited: "rate_limited";
570
555
  success: "success";
556
+ rate_limited: "rate_limited";
571
557
  server_error: "server_error";
572
558
  client_error: "client_error";
573
559
  defer_abandon: "defer_abandon";
@@ -598,8 +584,8 @@ export declare const testSinkCreateRequestSchema: import("zod").ZodObject<{
598
584
  endpoint: import("zod").ZodString;
599
585
  program: import("zod").ZodOptional<import("zod").ZodObject<{
600
586
  behavior: import("zod").ZodEnum<{
601
- rate_limited: "rate_limited";
602
587
  success: "success";
588
+ rate_limited: "rate_limited";
603
589
  server_error: "server_error";
604
590
  client_error: "client_error";
605
591
  defer_abandon: "defer_abandon";
@@ -617,8 +603,8 @@ export declare const testSinkCreateRequestSchema: import("zod").ZodObject<{
617
603
  retryAfterSeconds: import("zod").ZodOptional<import("zod").ZodNumber>;
618
604
  status: import("zod").ZodOptional<import("zod").ZodNumber>;
619
605
  then: import("zod").ZodOptional<import("zod").ZodEnum<{
620
- rate_limited: "rate_limited";
621
606
  success: "success";
607
+ rate_limited: "rate_limited";
622
608
  server_error: "server_error";
623
609
  client_error: "client_error";
624
610
  defer_abandon: "defer_abandon";
@@ -642,8 +628,8 @@ export type TestSinkCreateRequest = z.infer<typeof testSinkCreateRequestSchema>;
642
628
  */
643
629
  export declare const testSinkDeliverySchema: import("zod").ZodObject<{
644
630
  behavior: import("zod").ZodEnum<{
645
- rate_limited: "rate_limited";
646
631
  success: "success";
632
+ rate_limited: "rate_limited";
647
633
  server_error: "server_error";
648
634
  client_error: "client_error";
649
635
  defer_abandon: "defer_abandon";
@@ -681,8 +667,8 @@ export type TestSinkDelivery = z.infer<typeof testSinkDeliverySchema>;
681
667
  export declare const testSinkDeliveryPageSchema: import("zod").ZodObject<{
682
668
  data: import("zod").ZodArray<import("zod").ZodObject<{
683
669
  behavior: import("zod").ZodEnum<{
684
- rate_limited: "rate_limited";
685
670
  success: "success";
671
+ rate_limited: "rate_limited";
686
672
  server_error: "server_error";
687
673
  client_error: "client_error";
688
674
  defer_abandon: "defer_abandon";
@@ -747,8 +733,8 @@ export type TestSinkDeliveryPage = z.infer<typeof testSinkDeliveryPageSchema>;
747
733
  */
748
734
  export declare const testSinkDeliveryBodySchema: import("zod").ZodObject<{
749
735
  behavior: import("zod").ZodEnum<{
750
- rate_limited: "rate_limited";
751
736
  success: "success";
737
+ rate_limited: "rate_limited";
752
738
  server_error: "server_error";
753
739
  client_error: "client_error";
754
740
  defer_abandon: "defer_abandon";
@@ -874,7 +860,7 @@ export declare const CALLBACK_RECEIVER_PROTOCOL: {
874
860
  readonly deferredReport: undefined;
875
861
  readonly receiverActed: false;
876
862
  readonly senderOutcome: "terminal";
877
- readonly summary: "Answers a selected 4xx. The sender records a customer refusal, and retries it only when the status is one the receiver may recover from — 401, 403 and 404, listed as `clientErrorStatusesRetried`. The default, 422, is not one of those, so it settles on the first attempt.";
863
+ readonly summary: "Answers a selected 4xx. Every selected status settles the run on the first attempt, including 401, 403 and 404. Fix the destination, then redeliver manually.";
878
864
  };
879
865
  readonly defer_abandon: {
880
866
  readonly defaultStatus: 202;
@@ -922,8 +908,8 @@ export declare const CALLBACK_RECEIVER_PROTOCOL: {
922
908
  readonly defaultStatus: 401;
923
909
  readonly deferredReport: undefined;
924
910
  readonly receiverActed: false;
925
- readonly senderOutcome: "retryable";
926
- readonly summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The sender retries on the receiver's ordinary budget, because the usual cause is a credential the receiver is about to redeploy.";
911
+ readonly senderOutcome: "terminal";
912
+ readonly summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The run stops after one attempt; fix the key and redeliver manually.";
927
913
  };
928
914
  readonly rate_limited: {
929
915
  readonly defaultStatus: 429;
@@ -993,7 +979,7 @@ export declare const CALLBACK_RECEIVER_PROTOCOL: {
993
979
  readonly maxRetainedBodyBytes: 4096;
994
980
  };
995
981
  readonly clientErrorStatuses: readonly [400, 401, 403, 404, 409, 410, 422];
996
- readonly clientErrorStatusesRetried: readonly [401, 403, 404];
982
+ readonly clientErrorStatusesRetried: readonly [];
997
983
  readonly deferral: {
998
984
  readonly acknowledgement: string;
999
985
  readonly note: "A deferring behavior answers this exact body with this status, then reports with the bearer credential from the delivery's own signed envelope — to the one completion address the stage is configured with, which the envelope's own URL must match. A delivery whose envelope carries no completion material, or names a different address, cannot defer, and the sink answers it as an ordinary success instead.";
package/dist/test-sink.js CHANGED
@@ -125,14 +125,9 @@ export const TEST_SINK_BOUNDS = {
125
125
  /**
126
126
  * Selected 4xx a sink may answer with.
127
127
  *
128
- * **They are no longer all terminal to the sender, and which are is not this
129
- * file's decision.** FXO-1596 moved the refusals — a receiver declining *this
130
- * delivery now* rather than objecting to the message — onto the receiver's
131
- * ordinary five-failure budget. `TEST_SINK_REFUSAL_STATUSES` below is the
132
- * subset of this list that the sender will retry, and `test-sink.test.ts`
133
- * holds it equal to what `classifyStatus` actually says, so a status moved in
134
- * `CALLBACK_REFUSAL_STATUSES` and not here fails a test rather than teaching an
135
- * integrator the wrong lesson.
128
+ * The sender settles these selected statuses on the first attempt. W9-42
129
+ * stops retrying 401/403/404/405 to avoid billing for a broken configuration.
130
+ * The broader classification is owned by CALLBACK_REFUSAL_STATUSES.
136
131
  *
137
132
  * Nothing is removed from what a caller may request: every status here was
138
133
  * accepted before and still is.
@@ -143,11 +138,9 @@ export const TEST_SINK_CLIENT_ERROR_STATUSES = [
143
138
  /**
144
139
  * The statuses above that the sender retries rather than settling on.
145
140
  *
146
- * Exists so an integrator simulating a rotated credential (401) or a WAF rule
147
- * (403) can see five attempts arrive, and one simulating a malformed body (400)
148
- * or a duplicate (409) can see exactly one.
141
+ * Every selected client-error status is terminal under W9-42.
149
142
  */
150
- export const TEST_SINK_REFUSAL_STATUSES = [401, 403, 404];
143
+ export const TEST_SINK_REFUSAL_STATUSES = [];
151
144
  /** Selected 5xx a sink may answer with. Each is retryable to the sender. */
152
145
  export const TEST_SINK_SERVER_ERROR_STATUSES = [500, 502, 503, 504];
153
146
  /**
@@ -275,7 +268,7 @@ export const TEST_SINK_BEHAVIOR_FACTS = {
275
268
  deferredReport: undefined,
276
269
  receiverActed: false,
277
270
  senderOutcome: "terminal",
278
- summary: "Answers a selected 4xx. The sender records a customer refusal, and retries it only when the status is one the receiver may recover from — 401, 403 and 404, listed as `clientErrorStatusesRetried`. The default, 422, is not one of those, so it settles on the first attempt.",
271
+ summary: "Answers a selected 4xx. Every selected status settles the run on the first attempt, including 401, 403 and 404. Fix the destination, then redeliver manually.",
279
272
  },
280
273
  defer_abandon: {
281
274
  defaultStatus: CALLBACK_DEFERRAL_STATUS,
@@ -327,14 +320,9 @@ export const TEST_SINK_BEHAVIOR_FACTS = {
327
320
  defaultStatus: 401,
328
321
  deferredReport: undefined,
329
322
  receiverActed: false,
330
- // Retryable since FXO-1596, and the reversal is deliberate. The reasoning
331
- // it replaces was that a key mismatch cannot self-heal the way a stale
332
- // timestamp does — true of a mismatch caused by our own signing, and wrong
333
- // about the population that actually occurs, which is a receiver holding a
334
- // credential it has rotated or not yet deployed. That fixes itself on their
335
- // next release, and the delivery that follows carries a fresh signature.
336
- senderOutcome: "retryable",
337
- summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The sender retries on the receiver's ordinary budget, because the usual cause is a credential the receiver is about to redeploy.",
323
+ // W9-42 stops a broken credential after one billed attempt.
324
+ senderOutcome: "terminal",
325
+ summary: "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The run stops after one attempt; fix the key and redeliver manually.",
338
326
  },
339
327
  rate_limited: {
340
328
  defaultStatus: 429,
@@ -514,16 +502,9 @@ export const testSinkDispositionSchema = z.enum(TEST_SINK_DISPOSITIONS);
514
502
  * the network for six minutes is not, and every attempt re-signs with a fresh
515
503
  * timestamp, so the transient case self-heals and the permanent case exhausts
516
504
  * the attempt bound honestly.
517
- * - `invalid_signature`, `invalid_headers` — 401, **retryable since
518
- * FXO-1596**, and this entry used to say the opposite. The reasoning it
519
- * replaces was that retrying with the same key cannot succeed and a retryable
520
- * answer would spend the give-up budget hiding a misconfiguration. That is
521
- * true of a mismatch in *our* signing and false of the population that
522
- * actually reaches a 401: a receiver holding a credential it has rotated,
523
- * expired, or not yet deployed. Those recover on the receiver's next release,
524
- * and the delivery after it carries a fresh signature. The misconfiguration
525
- * is not hidden either — five identical 401s settle the occurrence 82 seconds
526
- * later with the same `inspect_receiver` advice, five rows instead of one.
505
+ * - `invalid_signature`, `invalid_headers` — 401, terminal since W9-42.
506
+ * Repeating a broken credential spends paid attempts without fixing it.
507
+ * Correct the key or receiver configuration, then redeliver manually.
527
508
  * - `invalid_envelope` — 400, terminal. The bytes authenticated and did not
528
509
  * parse, so the sender and receiver disagree about the contract.
529
510
  * - `body_too_large` — 413, terminal, and matched to the `payload_too_large`
@@ -1,3 +1,3 @@
1
1
  /** The exact Zod constructors used by delivery-runtime contract modules. */
2
- export { ZodError, ZodIssueCode, array, boolean, discriminatedUnion, enum, int, json, literal, number, object, partialRecord, record, strictObject, string, toJSONSchema, tuple, union, unknown, url, } from "zod";
3
- export type { RefinementCtx, ZodType, ZodTypeAny, infer } from "zod";
2
+ export { ZodError, ZodIssueCode, array, boolean, discriminatedUnion, enum, int, json, literal, number, never, object, partialRecord, record, strictObject, string, toJSONSchema, tuple, union, unknown, url, } from "zod";
3
+ export type { RefinementCtx, ZodType, ZodTypeAny, infer, input } from "zod";
@@ -1,2 +1,2 @@
1
1
  /** The exact Zod constructors used by delivery-runtime contract modules. */
2
- export { ZodError, ZodIssueCode, array, boolean, discriminatedUnion, enum, int, json, literal, number, object, partialRecord, record, strictObject, string, toJSONSchema, tuple, union, unknown, url, } from "zod";
2
+ export { ZodError, ZodIssueCode, array, boolean, discriminatedUnion, enum, int, json, literal, number, never, object, partialRecord, record, strictObject, string, toJSONSchema, tuple, union, unknown, url, } from "zod";
@@ -56,7 +56,7 @@
56
56
  "defaultStatus": 422,
57
57
  "receiverActed": false,
58
58
  "senderOutcome": "terminal",
59
- "summary": "Answers a selected 4xx. The sender records a customer refusal, and retries it only when the status is one the receiver may recover from — 401, 403 and 404, listed as `clientErrorStatusesRetried`. The default, 422, is not one of those, so it settles on the first attempt."
59
+ "summary": "Answers a selected 4xx. Every selected status settles the run on the first attempt, including 401, 403 and 404. Fix the destination, then redeliver manually."
60
60
  },
61
61
  "defer_abandon": {
62
62
  "defaultStatus": 202,
@@ -99,8 +99,8 @@
99
99
  "invalid_signature": {
100
100
  "defaultStatus": 401,
101
101
  "receiverActed": false,
102
- "senderOutcome": "retryable",
103
- "summary": "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The sender retries on the receiver's ordinary budget, because the usual cause is a credential the receiver is about to redeploy."
102
+ "senderOutcome": "terminal",
103
+ "summary": "Rejects the delivery as unauthenticated regardless of whether the signature verified. Exercises the key-mismatch path without forging a signature. The run stops after one attempt; fix the key and redeliver manually."
104
104
  },
105
105
  "rate_limited": {
106
106
  "defaultStatus": 429,
@@ -136,7 +136,7 @@
136
136
  "maxRetainedBodyBytes": 4096
137
137
  },
138
138
  "clientErrorStatuses": [400, 401, 403, 404, 409, 410, 422],
139
- "clientErrorStatusesRetried": [401, 403, 404],
139
+ "clientErrorStatusesRetried": [],
140
140
  "deferral": {
141
141
  "acknowledgement": "{\"greatwait\":{\"status\":\"pending\"}}",
142
142
  "note": "A deferring behavior answers this exact body with this status, then reports with the bearer credential from the delivery's own signed envelope — to the one completion address the stage is configured with, which the envelope's own URL must match. A delivery whose envelope carries no completion material, or names a different address, cannot defer, and the sink answers it as an ordinary success instead.",
@@ -0,0 +1,3 @@
1
+ {
2
+ "url": "https://hooks.example.com/greatwait"
3
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "createdAt": "2026-08-05T15:00:00.000Z",
3
+ "environmentKind": "test",
4
+ "id": "ep_01h8xyz9k2m4n6p8r0t2v4w6y8",
5
+ "name": "https://hooks.example.com/greatwait",
6
+ "project": "proj_01h8xyz9k2m4n6p8r0t2v4w6y8",
7
+ "signingKeys": {
8
+ "current": "csk_01h8xyz9k2m4n6p8r0t2v4w6y8"
9
+ },
10
+ "status": "active",
11
+ "updatedAt": "2026-08-05T15:00:00.000Z",
12
+ "url": "https://hooks.example.com/greatwait",
13
+ "version": 1,
14
+ "created": true,
15
+ "signingSecret": {
16
+ "guidance": "GreatWait signs every callback to this endpoint. The signing secret is never returned here; a signed-in member of the project reveals it on the endpoint's page in the GreatWait dashboard, and your receiver uses it to verify that a callback came from GreatWait.",
17
+ "returned": false,
18
+ "revealUrl": "https://app.greatwait.dev/o/org_2example0organization0id/p/proj_01h8xyz9k2m4n6p8r0t2v4w6y8/test/settings/endpoints/ep_01h8xyz9k2m4n6p8r0t2v4w6y8"
19
+ }
20
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "email": "teammate@example.com",
3
+ "role": "member",
4
+ "projects": ["proj_01h8xyz9k2m4n6p8r0t2v4w6y8"]
5
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "id": "inv_0123456789abcdef0123456789",
3
+ "email": "teammate@example.com",
4
+ "role": "member",
5
+ "projects": ["proj_01h8xyz9k2m4n6p8r0t2v4w6y8"],
6
+ "status": "pending"
7
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "data": [
3
+ {
4
+ "id": "inv_0123456789abcdef0123456789",
5
+ "email": "teammate@example.com",
6
+ "role": "member",
7
+ "projects": ["proj_01h8xyz9k2m4n6p8r0t2v4w6y8"],
8
+ "status": "pending"
9
+ }
10
+ ]
11
+ }