@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,383 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Secret handling and redaction.
4
+ *
5
+ * A secret is shown exactly once, at creation. After that only its non-secret
6
+ * prefix and metadata are readable — by the API, by MCP tools, by the
7
+ * dashboard, and by support. There is no "reveal" endpoint, because a store
8
+ * that can reveal a secret is a store that can leak one.
9
+ */
10
+ /** `pt_` + environment + `_` + public prefix. The secret half is never stored. */
11
+ export declare const CREDENTIAL_PREFIX_PATTERN: RegExp;
12
+ /** How many characters of a credential are the non-secret display prefix. */
13
+ export declare const CREDENTIAL_PREFIX_LENGTH = 16;
14
+ /** How many characters of high-entropy secret follow the display prefix. */
15
+ export declare const CREDENTIAL_SECRET_LENGTH = 32;
16
+ /**
17
+ * A whole credential: the display prefix, followed immediately by the secret
18
+ * half. `pt_test_a1b2c3d4` is the prefix of `pt_test_a1b2c3d4<32 more>`, which
19
+ * is what lets a presented credential be looked up by prefix before anything is
20
+ * compared — a store that had to scan every row to find a candidate would leak
21
+ * timing, and a separator would be one more thing for a client to get wrong.
22
+ *
23
+ * **This pattern is enforced where credentials are issued and verified, and
24
+ * deliberately not by `credentialCreatedSchema` below.** A response schema is
25
+ * projected into a committed example, and an example matching this pattern is
26
+ * indistinguishable from a real credential to `npm run check:secrets`, which
27
+ * would refuse it — correctly. So the wire schema states the length floor, the
28
+ * issuer states the format, and the published example is visibly a placeholder.
29
+ */
30
+ export declare const CREDENTIAL_SECRET_PATTERN: RegExp;
31
+ /**
32
+ * A credential as any read shape shows it: what it can do, where it can do it,
33
+ * and enough to recognise which one it is. Never the secret.
34
+ */
35
+ export declare const credentialMetadataSchema: z.ZodObject<{
36
+ createdAt: z.ZodString;
37
+ displayPrefix: z.ZodString;
38
+ environmentKind: z.ZodEnum<{
39
+ test: "test";
40
+ live: "live";
41
+ }>;
42
+ expiresAt: z.ZodOptional<z.ZodString>;
43
+ id: z.ZodString;
44
+ issuedBy: z.ZodOptional<z.ZodString>;
45
+ lastUsedAt: z.ZodOptional<z.ZodString>;
46
+ project: z.ZodString;
47
+ revokedAt: z.ZodOptional<z.ZodString>;
48
+ supersededBy: z.ZodOptional<z.ZodString>;
49
+ scopes: z.ZodArray<z.ZodEnum<{
50
+ "credentials:administer": "credentials:administer";
51
+ "credentials:read": "credentials:read";
52
+ "credentials:write": "credentials:write";
53
+ "endpoints:read": "endpoints:read";
54
+ "endpoints:write": "endpoints:write";
55
+ "forecast:read": "forecast:read";
56
+ "grants:read": "grants:read";
57
+ "grants:write": "grants:write";
58
+ "occurrences:read": "occurrences:read";
59
+ "occurrences:redeliver": "occurrences:redeliver";
60
+ "projects:read": "projects:read";
61
+ "projects:write": "projects:write";
62
+ "schedules:read": "schedules:read";
63
+ "schedules:write": "schedules:write";
64
+ "usage:read": "usage:read";
65
+ }>>;
66
+ }, z.core.$strip>;
67
+ export type CredentialMetadata = z.infer<typeof credentialMetadataSchema>;
68
+ /**
69
+ * A page of credentials.
70
+ *
71
+ * Built from `pageSchema` rather than written out, so paging is defined once:
72
+ * the cursor, the bounded limit and the freshness discriminator on this
73
+ * collection are the same ones every later collection gets, and a client that
74
+ * can page one can page all of them.
75
+ *
76
+ * The item is `credentialMetadataSchema` — the read shape — so there is no
77
+ * shape in the contract in which a list could carry a secret. That is a
78
+ * property of the type rather than of the query behind it.
79
+ */
80
+ export declare const credentialPageSchema: z.ZodObject<{
81
+ data: z.ZodArray<z.ZodObject<{
82
+ createdAt: z.ZodString;
83
+ displayPrefix: z.ZodString;
84
+ environmentKind: z.ZodEnum<{
85
+ test: "test";
86
+ live: "live";
87
+ }>;
88
+ expiresAt: z.ZodOptional<z.ZodString>;
89
+ id: z.ZodString;
90
+ issuedBy: z.ZodOptional<z.ZodString>;
91
+ lastUsedAt: z.ZodOptional<z.ZodString>;
92
+ project: z.ZodString;
93
+ revokedAt: z.ZodOptional<z.ZodString>;
94
+ supersededBy: z.ZodOptional<z.ZodString>;
95
+ scopes: z.ZodArray<z.ZodEnum<{
96
+ "credentials:administer": "credentials:administer";
97
+ "credentials:read": "credentials:read";
98
+ "credentials:write": "credentials:write";
99
+ "endpoints:read": "endpoints:read";
100
+ "endpoints:write": "endpoints:write";
101
+ "forecast:read": "forecast:read";
102
+ "grants:read": "grants:read";
103
+ "grants:write": "grants:write";
104
+ "occurrences:read": "occurrences:read";
105
+ "occurrences:redeliver": "occurrences:redeliver";
106
+ "projects:read": "projects:read";
107
+ "projects:write": "projects:write";
108
+ "schedules:read": "schedules:read";
109
+ "schedules:write": "schedules:write";
110
+ "usage:read": "usage:read";
111
+ }>>;
112
+ }, z.core.$strip>>;
113
+ freshness: z.ZodDiscriminatedUnion<[z.ZodObject<{
114
+ kind: z.ZodLiteral<"authoritative">;
115
+ }, z.core.$strip>, z.ZodObject<{
116
+ asOf: z.ZodString;
117
+ kind: z.ZodLiteral<"projected">;
118
+ lagSeconds: z.ZodNumber;
119
+ }, z.core.$strip>], "kind">;
120
+ nextCursor: z.ZodOptional<z.ZodString>;
121
+ }, z.core.$strip>;
122
+ export type CredentialPage = z.infer<typeof credentialPageSchema>;
123
+ /**
124
+ * The one-time creation response. `secret` is present here and nowhere else in
125
+ * the contract; every read shape carries metadata only.
126
+ */
127
+ export declare const credentialCreatedSchema: z.ZodObject<{
128
+ createdAt: z.ZodString;
129
+ displayPrefix: z.ZodString;
130
+ environmentKind: z.ZodEnum<{
131
+ test: "test";
132
+ live: "live";
133
+ }>;
134
+ expiresAt: z.ZodOptional<z.ZodString>;
135
+ id: z.ZodString;
136
+ issuedBy: z.ZodOptional<z.ZodString>;
137
+ lastUsedAt: z.ZodOptional<z.ZodString>;
138
+ project: z.ZodString;
139
+ revokedAt: z.ZodOptional<z.ZodString>;
140
+ supersededBy: z.ZodOptional<z.ZodString>;
141
+ scopes: z.ZodArray<z.ZodEnum<{
142
+ "credentials:administer": "credentials:administer";
143
+ "credentials:read": "credentials:read";
144
+ "credentials:write": "credentials:write";
145
+ "endpoints:read": "endpoints:read";
146
+ "endpoints:write": "endpoints:write";
147
+ "forecast:read": "forecast:read";
148
+ "grants:read": "grants:read";
149
+ "grants:write": "grants:write";
150
+ "occurrences:read": "occurrences:read";
151
+ "occurrences:redeliver": "occurrences:redeliver";
152
+ "projects:read": "projects:read";
153
+ "projects:write": "projects:write";
154
+ "schedules:read": "schedules:read";
155
+ "schedules:write": "schedules:write";
156
+ "usage:read": "usage:read";
157
+ }>>;
158
+ secret: z.ZodString;
159
+ shownOnce: z.ZodLiteral<true>;
160
+ }, z.core.$strip>;
161
+ export type CredentialCreated = z.infer<typeof credentialCreatedSchema>;
162
+ /**
163
+ * What a caller asks for when issuing a credential.
164
+ *
165
+ * `project` and `environmentKind` are caller-supplied and then *validated
166
+ * against the principal's organization* — they select within the caller's own
167
+ * tenancy rather than widening it, which is the same shape `resolveAuthority`
168
+ * already has. The organization is never here: that would be selecting a
169
+ * tenant, which `docs/contracts/v1-conventions.md` forbids outright.
170
+ */
171
+ export declare const credentialRequestSchema: z.ZodObject<{
172
+ environmentKind: z.ZodEnum<{
173
+ test: "test";
174
+ live: "live";
175
+ }>;
176
+ project: z.ZodString;
177
+ scopes: z.ZodArray<z.ZodEnum<{
178
+ "credentials:administer": "credentials:administer";
179
+ "credentials:read": "credentials:read";
180
+ "credentials:write": "credentials:write";
181
+ "endpoints:read": "endpoints:read";
182
+ "endpoints:write": "endpoints:write";
183
+ "forecast:read": "forecast:read";
184
+ "grants:read": "grants:read";
185
+ "grants:write": "grants:write";
186
+ "occurrences:read": "occurrences:read";
187
+ "occurrences:redeliver": "occurrences:redeliver";
188
+ "projects:read": "projects:read";
189
+ "projects:write": "projects:write";
190
+ "schedules:read": "schedules:read";
191
+ "schedules:write": "schedules:write";
192
+ "usage:read": "usage:read";
193
+ }>>;
194
+ }, z.core.$strip>;
195
+ export type CredentialRequest = z.infer<typeof credentialRequestSchema>;
196
+ /**
197
+ * The longest a rotated credential may keep working, and its default.
198
+ *
199
+ * The number is a judgement rather than a derivation: long enough that a deploy
200
+ * can roll a new secret through every consumer, short enough that a leaked old
201
+ * secret stops being useful within a day. It is stated here rather than left in
202
+ * a route so that changing it is a contract change somebody reviews.
203
+ */
204
+ export declare const CREDENTIAL_ROTATION_OVERLAP_SECONDS = 86400;
205
+ /**
206
+ * What a caller asks for when rotating.
207
+ *
208
+ * `overlapSeconds` may only *shorten* the window — the default is also the
209
+ * maximum. A caller who knows their consumers have already picked up the new
210
+ * secret can retire the old one immediately; nobody can ask for a longer tail
211
+ * than the contract allows, because the tail is the part that is dangerous.
212
+ *
213
+ * Zero is permitted and means the old credential stops verifying at once, which
214
+ * is deliberately not the same as revoking it: the audit trail records a
215
+ * rotation, and the retired credential names its replacement.
216
+ */
217
+ export declare const credentialRotationRequestSchema: z.ZodObject<{
218
+ overlapSeconds: z.ZodOptional<z.ZodNumber>;
219
+ }, z.core.$strip>;
220
+ export type CredentialRotationRequest = z.infer<typeof credentialRotationRequestSchema>;
221
+ /**
222
+ * The rotation response: the replacement, and what became of the original.
223
+ *
224
+ * Both halves are here because a caller that only learned the new secret would
225
+ * have no way to know when the old one stops working, and the deadline is the
226
+ * whole point of an overlap. `retired` is metadata only — rotation never
227
+ * reveals a secret it did not just mint.
228
+ */
229
+ export declare const credentialRotatedSchema: z.ZodObject<{
230
+ credential: z.ZodObject<{
231
+ createdAt: z.ZodString;
232
+ displayPrefix: z.ZodString;
233
+ environmentKind: z.ZodEnum<{
234
+ test: "test";
235
+ live: "live";
236
+ }>;
237
+ expiresAt: z.ZodOptional<z.ZodString>;
238
+ id: z.ZodString;
239
+ issuedBy: z.ZodOptional<z.ZodString>;
240
+ lastUsedAt: z.ZodOptional<z.ZodString>;
241
+ project: z.ZodString;
242
+ revokedAt: z.ZodOptional<z.ZodString>;
243
+ supersededBy: z.ZodOptional<z.ZodString>;
244
+ scopes: z.ZodArray<z.ZodEnum<{
245
+ "credentials:administer": "credentials:administer";
246
+ "credentials:read": "credentials:read";
247
+ "credentials:write": "credentials:write";
248
+ "endpoints:read": "endpoints:read";
249
+ "endpoints:write": "endpoints:write";
250
+ "forecast:read": "forecast:read";
251
+ "grants:read": "grants:read";
252
+ "grants:write": "grants:write";
253
+ "occurrences:read": "occurrences:read";
254
+ "occurrences:redeliver": "occurrences:redeliver";
255
+ "projects:read": "projects:read";
256
+ "projects:write": "projects:write";
257
+ "schedules:read": "schedules:read";
258
+ "schedules:write": "schedules:write";
259
+ "usage:read": "usage:read";
260
+ }>>;
261
+ secret: z.ZodString;
262
+ shownOnce: z.ZodLiteral<true>;
263
+ }, z.core.$strip>;
264
+ retired: z.ZodObject<{
265
+ createdAt: z.ZodString;
266
+ displayPrefix: z.ZodString;
267
+ environmentKind: z.ZodEnum<{
268
+ test: "test";
269
+ live: "live";
270
+ }>;
271
+ expiresAt: z.ZodOptional<z.ZodString>;
272
+ id: z.ZodString;
273
+ issuedBy: z.ZodOptional<z.ZodString>;
274
+ lastUsedAt: z.ZodOptional<z.ZodString>;
275
+ project: z.ZodString;
276
+ revokedAt: z.ZodOptional<z.ZodString>;
277
+ supersededBy: z.ZodOptional<z.ZodString>;
278
+ scopes: z.ZodArray<z.ZodEnum<{
279
+ "credentials:administer": "credentials:administer";
280
+ "credentials:read": "credentials:read";
281
+ "credentials:write": "credentials:write";
282
+ "endpoints:read": "endpoints:read";
283
+ "endpoints:write": "endpoints:write";
284
+ "forecast:read": "forecast:read";
285
+ "grants:read": "grants:read";
286
+ "grants:write": "grants:write";
287
+ "occurrences:read": "occurrences:read";
288
+ "occurrences:redeliver": "occurrences:redeliver";
289
+ "projects:read": "projects:read";
290
+ "projects:write": "projects:write";
291
+ "schedules:read": "schedules:read";
292
+ "schedules:write": "schedules:write";
293
+ "usage:read": "usage:read";
294
+ }>>;
295
+ }, z.core.$strip>;
296
+ }, z.core.$strip>;
297
+ export type CredentialRotated = z.infer<typeof credentialRotatedSchema>;
298
+ /**
299
+ * The revocation response.
300
+ *
301
+ * Metadata rather than an empty body, because the contract has no
302
+ * representation for one — `successStatusFor` yields 200 or 201 and every
303
+ * operation must declare a response schema. That constraint turns out to be the
304
+ * better answer anyway: returning the revoked credential lets a caller confirm
305
+ * *which* credential stopped working and when, which an empty 204 would make
306
+ * them ask for separately.
307
+ */
308
+ export declare const credentialRevokedSchema: z.ZodObject<{
309
+ credential: z.ZodObject<{
310
+ createdAt: z.ZodString;
311
+ displayPrefix: z.ZodString;
312
+ environmentKind: z.ZodEnum<{
313
+ test: "test";
314
+ live: "live";
315
+ }>;
316
+ expiresAt: z.ZodOptional<z.ZodString>;
317
+ id: z.ZodString;
318
+ issuedBy: z.ZodOptional<z.ZodString>;
319
+ lastUsedAt: z.ZodOptional<z.ZodString>;
320
+ project: z.ZodString;
321
+ revokedAt: z.ZodOptional<z.ZodString>;
322
+ supersededBy: z.ZodOptional<z.ZodString>;
323
+ scopes: z.ZodArray<z.ZodEnum<{
324
+ "credentials:administer": "credentials:administer";
325
+ "credentials:read": "credentials:read";
326
+ "credentials:write": "credentials:write";
327
+ "endpoints:read": "endpoints:read";
328
+ "endpoints:write": "endpoints:write";
329
+ "forecast:read": "forecast:read";
330
+ "grants:read": "grants:read";
331
+ "grants:write": "grants:write";
332
+ "occurrences:read": "occurrences:read";
333
+ "occurrences:redeliver": "occurrences:redeliver";
334
+ "projects:read": "projects:read";
335
+ "projects:write": "projects:write";
336
+ "schedules:read": "schedules:read";
337
+ "schedules:write": "schedules:write";
338
+ "usage:read": "usage:read";
339
+ }>>;
340
+ }, z.core.$strip>;
341
+ }, z.core.$strip>;
342
+ export type CredentialRevokedResponse = z.infer<typeof credentialRevokedSchema>;
343
+ /**
344
+ * Field-name fragments whose values must never reach a log, metric, or support
345
+ * index.
346
+ *
347
+ * Matched as substrings against a normalized key rather than by exact name.
348
+ * Exact matching caught `token` and missed `refreshToken`, `access_token`,
349
+ * `apiKey`, `client_secret` and `password` — which is most of what
350
+ * architecture section 10 actually requires be kept out of logs. Naming
351
+ * conventions vary across the codebase and across the libraries we log from,
352
+ * so the rule has to be insensitive to them.
353
+ */
354
+ export declare const REDACTED_FIELD_FRAGMENTS: readonly ["authorization", "cookie", "credential", "key", "password", "secret", "signature", "token"];
355
+ /**
356
+ * Fields redacted wholesale because they are content, not metadata.
357
+ *
358
+ * Architecture section 10 names callback bodies and prompts alongside
359
+ * payloads. Matched exactly rather than as fragments, so derived metadata like
360
+ * `bodyLength` still reaches a log. Note that a name containing a redacted
361
+ * fragment is still caught: `promptTokens` is redacted because it contains
362
+ * "token". Over-redacting a counter is the right side to err on.
363
+ */
364
+ export declare const REDACTED_FIELDS_EXACT: readonly ["body", "data", "payload", "prompt"];
365
+ /**
366
+ * Fields that contain "key" but are deliberately non-secret, and which
367
+ * observability depends on.
368
+ *
369
+ * `Callback-Key-Id` names *which* signing key was used and is the one thing
370
+ * needed to diagnose a rotation failure; `partitionKey` is a customer-facing
371
+ * forecast dimension; `idempotencyKey` is how a replay is traced. Redacting
372
+ * these would satisfy the letter of "hide secrets" while breaking the
373
+ * observability the architecture requires.
374
+ */
375
+ export declare const NEVER_REDACTED: readonly ["callbackkeyid", "idempotencykey", "keyid", "partitionkey"];
376
+ /**
377
+ * Redacts by field name, recursively, for structured logging.
378
+ *
379
+ * Customer callback payloads are content, not metadata: `data` and `payload`
380
+ * are redacted wholesale rather than sampled, because a scheduling payload is
381
+ * exactly the kind of thing that carries a customer's own secrets.
382
+ */
383
+ export declare function redact(value: unknown): unknown;
@@ -0,0 +1,272 @@
1
+ import { z } from "zod";
2
+ import { environmentKindSchema, scopeSchema } from "./authority.js";
3
+ import { apiCredentialIdSchema, projectIdSchema } from "./identifiers.js";
4
+ import { pageSchema } from "./protocol.js";
5
+ import { durationSecondsSchema, timestampSchema } from "./time.js";
6
+ /**
7
+ * Secret handling and redaction.
8
+ *
9
+ * A secret is shown exactly once, at creation. After that only its non-secret
10
+ * prefix and metadata are readable — by the API, by MCP tools, by the
11
+ * dashboard, and by support. There is no "reveal" endpoint, because a store
12
+ * that can reveal a secret is a store that can leak one.
13
+ */
14
+ /** `pt_` + environment + `_` + public prefix. The secret half is never stored. */
15
+ export const CREDENTIAL_PREFIX_PATTERN = /^pt_(?:live|test)_[0-9a-z]{8}$/u;
16
+ /** How many characters of a credential are the non-secret display prefix. */
17
+ export const CREDENTIAL_PREFIX_LENGTH = 16;
18
+ /** How many characters of high-entropy secret follow the display prefix. */
19
+ export const CREDENTIAL_SECRET_LENGTH = 32;
20
+ /**
21
+ * A whole credential: the display prefix, followed immediately by the secret
22
+ * half. `pt_test_a1b2c3d4` is the prefix of `pt_test_a1b2c3d4<32 more>`, which
23
+ * is what lets a presented credential be looked up by prefix before anything is
24
+ * compared — a store that had to scan every row to find a candidate would leak
25
+ * timing, and a separator would be one more thing for a client to get wrong.
26
+ *
27
+ * **This pattern is enforced where credentials are issued and verified, and
28
+ * deliberately not by `credentialCreatedSchema` below.** A response schema is
29
+ * projected into a committed example, and an example matching this pattern is
30
+ * indistinguishable from a real credential to `npm run check:secrets`, which
31
+ * would refuse it — correctly. So the wire schema states the length floor, the
32
+ * issuer states the format, and the published example is visibly a placeholder.
33
+ */
34
+ export const CREDENTIAL_SECRET_PATTERN = /^pt_(?:live|test)_[0-9a-z]{40}$/u;
35
+ /**
36
+ * A credential as any read shape shows it: what it can do, where it can do it,
37
+ * and enough to recognise which one it is. Never the secret.
38
+ */
39
+ export const credentialMetadataSchema = z.object({
40
+ createdAt: timestampSchema,
41
+ /** The non-secret leading segment, safe to display and log. */
42
+ displayPrefix: z.string().regex(CREDENTIAL_PREFIX_PATTERN),
43
+ /** Which environment of that project. Part of the credential's authority. */
44
+ environmentKind: environmentKindSchema,
45
+ /**
46
+ * When this credential stops verifying, present only after it is rotated.
47
+ *
48
+ * Distinct from `revokedAt` because the two say different things to whoever
49
+ * is debugging: an expiry is self-inflicted and scheduled — you rotated, and
50
+ * this is the deadline you were given — where a revocation is somebody
51
+ * deciding this credential should stop working now. Collapsing them would
52
+ * make a missed rotation window look like a security event.
53
+ */
54
+ expiresAt: timestampSchema.optional(),
55
+ id: apiCredentialIdSchema,
56
+ /**
57
+ * The member who issued it, when one is recorded.
58
+ *
59
+ * **Published because acting on somebody else's credential needs a wider
60
+ * scope, and a surface that could not say whose a credential is would have to
61
+ * discover that by being refused.** The value is the identifier the identity
62
+ * provider uses for that person; it names nobody outside the organization
63
+ * that already shares the credential's project.
64
+ *
65
+ * Absent on a credential issued before the issuer was recorded, and on one
66
+ * issued by anything that is not a signed-in member. An absent issuer is
67
+ * *nobody's* — it matches no member, so only a caller holding
68
+ * `credentials:administer` may rotate or revoke it.
69
+ */
70
+ issuedBy: z.string().min(1).optional(),
71
+ lastUsedAt: timestampSchema.optional(),
72
+ /** The project this credential is bound to for its whole life. */
73
+ project: projectIdSchema,
74
+ revokedAt: timestampSchema.optional(),
75
+ /**
76
+ * The credential that replaced this one, present only on a rotated
77
+ * credential. Read shapes carry it so a holder of the old credential can tell
78
+ * that a replacement exists without being able to use it — the id is not a
79
+ * secret, and it is what makes "why did this stop working" answerable.
80
+ */
81
+ supersededBy: apiCredentialIdSchema.optional(),
82
+ /**
83
+ * Exactly what it may do. Present on the read shape because a credential
84
+ * nobody can see the scopes of is a credential nobody can audit, and the
85
+ * scope list is not secret — holding it grants nothing.
86
+ */
87
+ scopes: z.array(scopeSchema).min(1),
88
+ });
89
+ /**
90
+ * A page of credentials.
91
+ *
92
+ * Built from `pageSchema` rather than written out, so paging is defined once:
93
+ * the cursor, the bounded limit and the freshness discriminator on this
94
+ * collection are the same ones every later collection gets, and a client that
95
+ * can page one can page all of them.
96
+ *
97
+ * The item is `credentialMetadataSchema` — the read shape — so there is no
98
+ * shape in the contract in which a list could carry a secret. That is a
99
+ * property of the type rather than of the query behind it.
100
+ */
101
+ export const credentialPageSchema = pageSchema(credentialMetadataSchema);
102
+ /**
103
+ * The one-time creation response. `secret` is present here and nowhere else in
104
+ * the contract; every read shape carries metadata only.
105
+ */
106
+ export const credentialCreatedSchema = credentialMetadataSchema.extend({
107
+ secret: z.string().min(CREDENTIAL_PREFIX_LENGTH + CREDENTIAL_SECRET_LENGTH),
108
+ /** Always true, so a client cannot mistake this for a readable field. */
109
+ shownOnce: z.literal(true),
110
+ });
111
+ /**
112
+ * What a caller asks for when issuing a credential.
113
+ *
114
+ * `project` and `environmentKind` are caller-supplied and then *validated
115
+ * against the principal's organization* — they select within the caller's own
116
+ * tenancy rather than widening it, which is the same shape `resolveAuthority`
117
+ * already has. The organization is never here: that would be selecting a
118
+ * tenant, which `docs/contracts/v1-conventions.md` forbids outright.
119
+ */
120
+ export const credentialRequestSchema = z.object({
121
+ environmentKind: environmentKindSchema,
122
+ project: projectIdSchema,
123
+ /**
124
+ * Requested scopes. The grant is the intersection of these, the credential
125
+ * policy, and the issuing member's current role — so asking for more than the
126
+ * role allows narrows the result rather than refusing it.
127
+ */
128
+ scopes: z.array(scopeSchema).min(1),
129
+ });
130
+ /**
131
+ * The longest a rotated credential may keep working, and its default.
132
+ *
133
+ * The number is a judgement rather than a derivation: long enough that a deploy
134
+ * can roll a new secret through every consumer, short enough that a leaked old
135
+ * secret stops being useful within a day. It is stated here rather than left in
136
+ * a route so that changing it is a contract change somebody reviews.
137
+ */
138
+ export const CREDENTIAL_ROTATION_OVERLAP_SECONDS = 86_400;
139
+ /**
140
+ * What a caller asks for when rotating.
141
+ *
142
+ * `overlapSeconds` may only *shorten* the window — the default is also the
143
+ * maximum. A caller who knows their consumers have already picked up the new
144
+ * secret can retire the old one immediately; nobody can ask for a longer tail
145
+ * than the contract allows, because the tail is the part that is dangerous.
146
+ *
147
+ * Zero is permitted and means the old credential stops verifying at once, which
148
+ * is deliberately not the same as revoking it: the audit trail records a
149
+ * rotation, and the retired credential names its replacement.
150
+ */
151
+ export const credentialRotationRequestSchema = z.object({
152
+ overlapSeconds: durationSecondsSchema
153
+ .max(CREDENTIAL_ROTATION_OVERLAP_SECONDS, "must not exceed the maximum rotation overlap")
154
+ .optional(),
155
+ });
156
+ /**
157
+ * The rotation response: the replacement, and what became of the original.
158
+ *
159
+ * Both halves are here because a caller that only learned the new secret would
160
+ * have no way to know when the old one stops working, and the deadline is the
161
+ * whole point of an overlap. `retired` is metadata only — rotation never
162
+ * reveals a secret it did not just mint.
163
+ */
164
+ export const credentialRotatedSchema = z.object({
165
+ credential: credentialCreatedSchema,
166
+ retired: credentialMetadataSchema,
167
+ });
168
+ /**
169
+ * The revocation response.
170
+ *
171
+ * Metadata rather than an empty body, because the contract has no
172
+ * representation for one — `successStatusFor` yields 200 or 201 and every
173
+ * operation must declare a response schema. That constraint turns out to be the
174
+ * better answer anyway: returning the revoked credential lets a caller confirm
175
+ * *which* credential stopped working and when, which an empty 204 would make
176
+ * them ask for separately.
177
+ */
178
+ export const credentialRevokedSchema = z.object({
179
+ credential: credentialMetadataSchema,
180
+ });
181
+ /**
182
+ * Field-name fragments whose values must never reach a log, metric, or support
183
+ * index.
184
+ *
185
+ * Matched as substrings against a normalized key rather than by exact name.
186
+ * Exact matching caught `token` and missed `refreshToken`, `access_token`,
187
+ * `apiKey`, `client_secret` and `password` — which is most of what
188
+ * architecture section 10 actually requires be kept out of logs. Naming
189
+ * conventions vary across the codebase and across the libraries we log from,
190
+ * so the rule has to be insensitive to them.
191
+ */
192
+ export const REDACTED_FIELD_FRAGMENTS = [
193
+ "authorization",
194
+ "cookie",
195
+ "credential",
196
+ "key",
197
+ "password",
198
+ "secret",
199
+ "signature",
200
+ "token",
201
+ ];
202
+ /**
203
+ * Fields redacted wholesale because they are content, not metadata.
204
+ *
205
+ * Architecture section 10 names callback bodies and prompts alongside
206
+ * payloads. Matched exactly rather than as fragments, so derived metadata like
207
+ * `bodyLength` still reaches a log. Note that a name containing a redacted
208
+ * fragment is still caught: `promptTokens` is redacted because it contains
209
+ * "token". Over-redacting a counter is the right side to err on.
210
+ */
211
+ export const REDACTED_FIELDS_EXACT = [
212
+ "body",
213
+ "data",
214
+ "payload",
215
+ "prompt",
216
+ ];
217
+ /**
218
+ * Fields that contain "key" but are deliberately non-secret, and which
219
+ * observability depends on.
220
+ *
221
+ * `Callback-Key-Id` names *which* signing key was used and is the one thing
222
+ * needed to diagnose a rotation failure; `partitionKey` is a customer-facing
223
+ * forecast dimension; `idempotencyKey` is how a replay is traced. Redacting
224
+ * these would satisfy the letter of "hide secrets" while breaking the
225
+ * observability the architecture requires.
226
+ */
227
+ export const NEVER_REDACTED = [
228
+ // The header spelling matters as much as the bare one: a log line carries
229
+ // `Callback-Key-Id` under its real name, which normalizes to
230
+ // `callbackkeyid` and would otherwise miss an exact-match allowlist and be
231
+ // caught by the `key` fragment — leaving rotation diagnosis blind, which is
232
+ // the exact failure this list exists to prevent.
233
+ "callbackkeyid",
234
+ "idempotencykey",
235
+ "keyid",
236
+ "partitionkey",
237
+ ];
238
+ const REDACTION = "[redacted]";
239
+ /** Lowercased with separators removed, so `access_token` and `accessToken` agree. */
240
+ function normalizeKey(key) {
241
+ return key.toLowerCase().replace(/[_-]/gu, "");
242
+ }
243
+ function mustRedact(key) {
244
+ const normalized = normalizeKey(key);
245
+ // The allowlist wins, so broad fragment matching cannot swallow a field
246
+ // whose whole purpose is to be readable in a log. Matched whole, not as a
247
+ // suffix: `apiKeyId` ends with `keyid` and must still be redacted.
248
+ if (NEVER_REDACTED.some((allowed) => normalized === allowed)) {
249
+ return false;
250
+ }
251
+ return (REDACTED_FIELDS_EXACT.some((exact) => normalized === exact) ||
252
+ REDACTED_FIELD_FRAGMENTS.some((fragment) => normalized.includes(normalizeKey(fragment))));
253
+ }
254
+ /**
255
+ * Redacts by field name, recursively, for structured logging.
256
+ *
257
+ * Customer callback payloads are content, not metadata: `data` and `payload`
258
+ * are redacted wholesale rather than sampled, because a scheduling payload is
259
+ * exactly the kind of thing that carries a customer's own secrets.
260
+ */
261
+ export function redact(value) {
262
+ if (Array.isArray(value)) {
263
+ return value.map(redact);
264
+ }
265
+ if (typeof value !== "object" || value === null) {
266
+ return value;
267
+ }
268
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [
269
+ key,
270
+ mustRedact(key) ? REDACTION : redact(nested),
271
+ ]));
272
+ }