@atlasauth/emulator-core 0.1.0 → 0.3.0

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 (300) hide show
  1. package/dist/adopt.d.ts +50 -0
  2. package/dist/adopt.js +57 -0
  3. package/dist/adopt.js.map +1 -0
  4. package/dist/coverage.d.ts +15 -0
  5. package/dist/coverage.js +31 -0
  6. package/dist/coverage.js.map +1 -0
  7. package/dist/crypto-util.d.ts +10 -0
  8. package/dist/crypto-util.js +76 -0
  9. package/dist/crypto-util.js.map +1 -0
  10. package/dist/custom-html.d.ts +57 -0
  11. package/dist/custom-html.js +491 -0
  12. package/dist/custom-html.js.map +1 -0
  13. package/dist/engine.d.ts +34 -3
  14. package/dist/engine.js +57 -8
  15. package/dist/engine.js.map +1 -1
  16. package/dist/error-catalog.js +16 -0
  17. package/dist/error-catalog.js.map +1 -1
  18. package/dist/event-gate.d.ts +3 -0
  19. package/dist/event-gate.js +430 -0
  20. package/dist/event-gate.js.map +1 -0
  21. package/dist/generic.js +14 -2
  22. package/dist/generic.js.map +1 -1
  23. package/dist/handlers/access-signals.d.ts +48 -0
  24. package/dist/handlers/access-signals.js +76 -0
  25. package/dist/handlers/access-signals.js.map +1 -0
  26. package/dist/handlers/approvals.d.ts +2 -0
  27. package/dist/handlers/approvals.js +418 -0
  28. package/dist/handlers/approvals.js.map +1 -0
  29. package/dist/handlers/authz-data.d.ts +2 -0
  30. package/dist/handlers/authz-data.js +627 -0
  31. package/dist/handlers/authz-data.js.map +1 -0
  32. package/dist/handlers/authz-layers.d.ts +229 -0
  33. package/dist/handlers/authz-layers.js +786 -0
  34. package/dist/handlers/authz-layers.js.map +1 -0
  35. package/dist/handlers/authz-state.d.ts +80 -0
  36. package/dist/handlers/authz-state.js +175 -0
  37. package/dist/handlers/authz-state.js.map +1 -0
  38. package/dist/handlers/authz.d.ts +2 -0
  39. package/dist/handlers/authz.js +410 -0
  40. package/dist/handlers/authz.js.map +1 -0
  41. package/dist/handlers/billing.d.ts +100 -0
  42. package/dist/handlers/billing.js +877 -0
  43. package/dist/handlers/billing.js.map +1 -0
  44. package/dist/handlers/cedar-ext.d.ts +35 -0
  45. package/dist/handlers/cedar-ext.js +174 -0
  46. package/dist/handlers/cedar-ext.js.map +1 -0
  47. package/dist/handlers/cedar.d.ts +146 -0
  48. package/dist/handlers/cedar.js +689 -0
  49. package/dist/handlers/cedar.js.map +1 -0
  50. package/dist/handlers/connections.d.ts +5 -0
  51. package/dist/handlers/connections.js +561 -0
  52. package/dist/handlers/connections.js.map +1 -0
  53. package/dist/handlers/control.js +6 -1
  54. package/dist/handlers/control.js.map +1 -1
  55. package/dist/handlers/device.d.ts +47 -0
  56. package/dist/handlers/device.js +665 -0
  57. package/dist/handlers/device.js.map +1 -0
  58. package/dist/handlers/emu3-misc.d.ts +38 -0
  59. package/dist/handlers/emu3-misc.js +1056 -0
  60. package/dist/handlers/emu3-misc.js.map +1 -0
  61. package/dist/handlers/emu4-actions-access.d.ts +46 -0
  62. package/dist/handlers/emu4-actions-access.js +107 -0
  63. package/dist/handlers/emu4-actions-access.js.map +1 -0
  64. package/dist/handlers/emu4-actions-catalog-extra.d.ts +3 -0
  65. package/dist/handlers/emu4-actions-catalog-extra.js +204 -0
  66. package/dist/handlers/emu4-actions-catalog-extra.js.map +1 -0
  67. package/dist/handlers/emu4-actions-catalog.d.ts +3 -0
  68. package/dist/handlers/emu4-actions-catalog.js +143 -0
  69. package/dist/handlers/emu4-actions-catalog.js.map +1 -0
  70. package/dist/handlers/emu4-actions-def.d.ts +50 -0
  71. package/dist/handlers/emu4-actions-def.js +250 -0
  72. package/dist/handlers/emu4-actions-def.js.map +1 -0
  73. package/dist/handlers/emu4-actions-sandbox.d.ts +36 -0
  74. package/dist/handlers/emu4-actions-sandbox.js +318 -0
  75. package/dist/handlers/emu4-actions-sandbox.js.map +1 -0
  76. package/dist/handlers/emu4-actions-typegen.d.ts +5 -0
  77. package/dist/handlers/emu4-actions-typegen.js +73 -0
  78. package/dist/handlers/emu4-actions-typegen.js.map +1 -0
  79. package/dist/handlers/emu4-actions.d.ts +2 -0
  80. package/dist/handlers/emu4-actions.js +506 -0
  81. package/dist/handlers/emu4-actions.js.map +1 -0
  82. package/dist/handlers/emu4-authz-trust.d.ts +40 -0
  83. package/dist/handlers/emu4-authz-trust.js +488 -0
  84. package/dist/handlers/emu4-authz-trust.js.map +1 -0
  85. package/dist/handlers/emu4-authz.d.ts +2 -0
  86. package/dist/handlers/emu4-authz.js +752 -0
  87. package/dist/handlers/emu4-authz.js.map +1 -0
  88. package/dist/handlers/emu4-billing-core.d.ts +23 -0
  89. package/dist/handlers/emu4-billing-core.js +22 -0
  90. package/dist/handlers/emu4-billing-core.js.map +1 -0
  91. package/dist/handlers/emu4-billing-x.d.ts +2 -0
  92. package/dist/handlers/emu4-billing-x.js +489 -0
  93. package/dist/handlers/emu4-billing-x.js.map +1 -0
  94. package/dist/handlers/emu4-billing.d.ts +14 -0
  95. package/dist/handlers/emu4-billing.js +846 -0
  96. package/dist/handlers/emu4-billing.js.map +1 -0
  97. package/dist/handlers/emu4-clones.d.ts +2 -0
  98. package/dist/handlers/emu4-clones.js +322 -0
  99. package/dist/handlers/emu4-clones.js.map +1 -0
  100. package/dist/handlers/emu4-compliance.d.ts +8 -0
  101. package/dist/handlers/emu4-compliance.js +39 -0
  102. package/dist/handlers/emu4-compliance.js.map +1 -0
  103. package/dist/handlers/emu4-config.d.ts +14 -0
  104. package/dist/handlers/emu4-config.js +148 -0
  105. package/dist/handlers/emu4-config.js.map +1 -0
  106. package/dist/handlers/emu4-cookies.d.ts +8 -0
  107. package/dist/handlers/emu4-cookies.js +248 -0
  108. package/dist/handlers/emu4-cookies.js.map +1 -0
  109. package/dist/handlers/emu4-erasure.d.ts +2 -0
  110. package/dist/handlers/emu4-erasure.js +587 -0
  111. package/dist/handlers/emu4-erasure.js.map +1 -0
  112. package/dist/handlers/emu4-federation.d.ts +8 -0
  113. package/dist/handlers/emu4-federation.js +18 -0
  114. package/dist/handlers/emu4-federation.js.map +1 -0
  115. package/dist/handlers/emu4-journey-math.d.ts +42 -0
  116. package/dist/handlers/emu4-journey-math.js +99 -0
  117. package/dist/handlers/emu4-journey-math.js.map +1 -0
  118. package/dist/handlers/emu4-messaging.d.ts +21 -0
  119. package/dist/handlers/emu4-messaging.js +595 -0
  120. package/dist/handlers/emu4-messaging.js.map +1 -0
  121. package/dist/handlers/emu4-minors.d.ts +7 -0
  122. package/dist/handlers/emu4-minors.js +408 -0
  123. package/dist/handlers/emu4-minors.js.map +1 -0
  124. package/dist/handlers/emu4-oauth-audit.d.ts +5 -0
  125. package/dist/handlers/emu4-oauth-audit.js +17 -0
  126. package/dist/handlers/emu4-oauth-audit.js.map +1 -0
  127. package/dist/handlers/emu4-oauth-auth.d.ts +83 -0
  128. package/dist/handlers/emu4-oauth-auth.js +363 -0
  129. package/dist/handlers/emu4-oauth-auth.js.map +1 -0
  130. package/dist/handlers/emu4-oauth-delivery.d.ts +68 -0
  131. package/dist/handlers/emu4-oauth-delivery.js +207 -0
  132. package/dist/handlers/emu4-oauth-delivery.js.map +1 -0
  133. package/dist/handlers/emu4-oauth-keys.d.ts +11 -0
  134. package/dist/handlers/emu4-oauth-keys.js +268 -0
  135. package/dist/handlers/emu4-oauth-keys.js.map +1 -0
  136. package/dist/handlers/emu4-oauth-pub.d.ts +36 -0
  137. package/dist/handlers/emu4-oauth-pub.js +420 -0
  138. package/dist/handlers/emu4-oauth-pub.js.map +1 -0
  139. package/dist/handlers/emu4-oauth.d.ts +10 -0
  140. package/dist/handlers/emu4-oauth.js +23 -0
  141. package/dist/handlers/emu4-oauth.js.map +1 -0
  142. package/dist/handlers/emu4-policies.d.ts +2 -0
  143. package/dist/handlers/emu4-policies.js +408 -0
  144. package/dist/handlers/emu4-policies.js.map +1 -0
  145. package/dist/handlers/emu4-region.d.ts +2 -0
  146. package/dist/handlers/emu4-region.js +189 -0
  147. package/dist/handlers/emu4-region.js.map +1 -0
  148. package/dist/handlers/emu4-saml.d.ts +12 -0
  149. package/dist/handlers/emu4-saml.js +532 -0
  150. package/dist/handlers/emu4-saml.js.map +1 -0
  151. package/dist/handlers/emu4-shared.d.ts +44 -0
  152. package/dist/handlers/emu4-shared.js +90 -0
  153. package/dist/handlers/emu4-shared.js.map +1 -0
  154. package/dist/handlers/emu4-snapshots.d.ts +38 -0
  155. package/dist/handlers/emu4-snapshots.js +231 -0
  156. package/dist/handlers/emu4-snapshots.js.map +1 -0
  157. package/dist/handlers/emu4-sns.d.ts +9 -0
  158. package/dist/handlers/emu4-sns.js +21 -0
  159. package/dist/handlers/emu4-sns.js.map +1 -0
  160. package/dist/handlers/emu4-trust-binding.d.ts +27 -0
  161. package/dist/handlers/emu4-trust-binding.js +344 -0
  162. package/dist/handlers/emu4-trust-binding.js.map +1 -0
  163. package/dist/handlers/emu4-trust.d.ts +35 -0
  164. package/dist/handlers/emu4-trust.js +543 -0
  165. package/dist/handlers/emu4-trust.js.map +1 -0
  166. package/dist/handlers/federation.d.ts +2 -0
  167. package/dist/handlers/federation.js +481 -0
  168. package/dist/handlers/federation.js.map +1 -0
  169. package/dist/handlers/fga-engine.d.ts +78 -0
  170. package/dist/handlers/fga-engine.js +663 -0
  171. package/dist/handlers/fga-engine.js.map +1 -0
  172. package/dist/handlers/governance.d.ts +2 -0
  173. package/dist/handlers/governance.js +460 -0
  174. package/dist/handlers/governance.js.map +1 -0
  175. package/dist/handlers/hooks.js +17 -1
  176. package/dist/handlers/hooks.js.map +1 -1
  177. package/dist/handlers/index.d.ts +5 -1
  178. package/dist/handlers/index.js +45 -3
  179. package/dist/handlers/index.js.map +1 -1
  180. package/dist/handlers/keys.js +6 -1
  181. package/dist/handlers/keys.js.map +1 -1
  182. package/dist/handlers/machines.js +61 -8
  183. package/dist/handlers/machines.js.map +1 -1
  184. package/dist/handlers/messaging.d.ts +6 -0
  185. package/dist/handlers/messaging.js +893 -0
  186. package/dist/handlers/messaging.js.map +1 -0
  187. package/dist/handlers/oauth-adv.d.ts +191 -0
  188. package/dist/handlers/oauth-adv.js +1305 -0
  189. package/dist/handlers/oauth-adv.js.map +1 -0
  190. package/dist/handlers/oauth-flows.d.ts +52 -0
  191. package/dist/handlers/oauth-flows.js +480 -0
  192. package/dist/handlers/oauth-flows.js.map +1 -0
  193. package/dist/handlers/oauth.d.ts +96 -0
  194. package/dist/handlers/oauth.js +793 -0
  195. package/dist/handlers/oauth.js.map +1 -0
  196. package/dist/handlers/orgs.js +6 -0
  197. package/dist/handlers/orgs.js.map +1 -1
  198. package/dist/handlers/passkeys.d.ts +3 -0
  199. package/dist/handlers/passkeys.js +293 -0
  200. package/dist/handlers/passkeys.js.map +1 -0
  201. package/dist/handlers/platform.d.ts +20 -0
  202. package/dist/handlers/platform.js +472 -0
  203. package/dist/handlers/platform.js.map +1 -0
  204. package/dist/handlers/round2.d.ts +8 -0
  205. package/dist/handlers/round2.js +372 -0
  206. package/dist/handlers/round2.js.map +1 -0
  207. package/dist/handlers/scim.d.ts +4 -0
  208. package/dist/handlers/scim.js +595 -0
  209. package/dist/handlers/scim.js.map +1 -0
  210. package/dist/handlers/sessions.d.ts +9 -3
  211. package/dist/handlers/sessions.js +76 -38
  212. package/dist/handlers/sessions.js.map +1 -1
  213. package/dist/handlers/support.d.ts +20 -0
  214. package/dist/handlers/support.js +62 -0
  215. package/dist/handlers/support.js.map +1 -0
  216. package/dist/handlers/users.js +7 -0
  217. package/dist/handlers/users.js.map +1 -1
  218. package/dist/handlers/workflows.d.ts +2 -0
  219. package/dist/handlers/workflows.js +428 -0
  220. package/dist/handlers/workflows.js.map +1 -0
  221. package/dist/index.d.ts +8 -0
  222. package/dist/index.js +23 -1
  223. package/dist/index.js.map +1 -1
  224. package/dist/jwe.d.ts +18 -0
  225. package/dist/jwe.js +101 -0
  226. package/dist/jwe.js.map +1 -0
  227. package/dist/jws.d.ts +41 -0
  228. package/dist/jws.js +135 -0
  229. package/dist/jws.js.map +1 -0
  230. package/dist/messages.d.ts +7 -0
  231. package/dist/messages.js +8 -4
  232. package/dist/messages.js.map +1 -1
  233. package/dist/messaging-callbacks.d.ts +100 -0
  234. package/dist/messaging-callbacks.js +767 -0
  235. package/dist/messaging-callbacks.js.map +1 -0
  236. package/dist/openapi.js +41 -1
  237. package/dist/openapi.js.map +1 -1
  238. package/dist/parity-bin.js +3 -1
  239. package/dist/parity-bin.js.map +1 -1
  240. package/dist/parity-emu3-authz.d.ts +3 -0
  241. package/dist/parity-emu3-authz.js +61 -0
  242. package/dist/parity-emu3-authz.js.map +1 -0
  243. package/dist/parity-emu3-billing.d.ts +3 -0
  244. package/dist/parity-emu3-billing.js +25 -0
  245. package/dist/parity-emu3-billing.js.map +1 -0
  246. package/dist/parity-emu3-messaging.d.ts +3 -0
  247. package/dist/parity-emu3-messaging.js +27 -0
  248. package/dist/parity-emu3-messaging.js.map +1 -0
  249. package/dist/parity-emu3-misc.d.ts +2 -0
  250. package/dist/parity-emu3-misc.js +63 -0
  251. package/dist/parity-emu3-misc.js.map +1 -0
  252. package/dist/parity-emu3-oauth.d.ts +2 -0
  253. package/dist/parity-emu3-oauth.js +39 -0
  254. package/dist/parity-emu3-oauth.js.map +1 -0
  255. package/dist/parity-emu4-actions.d.ts +2 -0
  256. package/dist/parity-emu4-actions.js +38 -0
  257. package/dist/parity-emu4-actions.js.map +1 -0
  258. package/dist/parity-emu4-authz.d.ts +3 -0
  259. package/dist/parity-emu4-authz.js +68 -0
  260. package/dist/parity-emu4-authz.js.map +1 -0
  261. package/dist/parity-emu4-billing.d.ts +6 -0
  262. package/dist/parity-emu4-billing.js +44 -0
  263. package/dist/parity-emu4-billing.js.map +1 -0
  264. package/dist/parity-emu4-compliance.d.ts +2 -0
  265. package/dist/parity-emu4-compliance.js +92 -0
  266. package/dist/parity-emu4-compliance.js.map +1 -0
  267. package/dist/parity-emu4-federation.d.ts +2 -0
  268. package/dist/parity-emu4-federation.js +33 -0
  269. package/dist/parity-emu4-federation.js.map +1 -0
  270. package/dist/parity-emu4-messaging.d.ts +3 -0
  271. package/dist/parity-emu4-messaging.js +42 -0
  272. package/dist/parity-emu4-messaging.js.map +1 -0
  273. package/dist/parity-emu4-oauth.d.ts +2 -0
  274. package/dist/parity-emu4-oauth.js +55 -0
  275. package/dist/parity-emu4-oauth.js.map +1 -0
  276. package/dist/parity-emu4-trust.d.ts +2 -0
  277. package/dist/parity-emu4-trust.js +41 -0
  278. package/dist/parity-emu4-trust.js.map +1 -0
  279. package/dist/parity-routes.d.ts +14 -0
  280. package/dist/parity-routes.js +439 -0
  281. package/dist/parity-routes.js.map +1 -0
  282. package/dist/parity.d.ts +10 -3
  283. package/dist/parity.js +14 -5
  284. package/dist/parity.js.map +1 -1
  285. package/dist/sns-cert.d.ts +9 -0
  286. package/dist/sns-cert.js +101 -0
  287. package/dist/sns-cert.js.map +1 -0
  288. package/dist/tokens.d.ts +85 -3
  289. package/dist/tokens.js +196 -24
  290. package/dist/tokens.js.map +1 -1
  291. package/dist/webauthn-virtual.d.ts +70 -0
  292. package/dist/webauthn-virtual.js +186 -0
  293. package/dist/webauthn-virtual.js.map +1 -0
  294. package/dist/webhook-ext.d.ts +16 -0
  295. package/dist/webhook-ext.js +218 -0
  296. package/dist/webhook-ext.js.map +1 -0
  297. package/dist/webhooks.d.ts +3 -0
  298. package/dist/webhooks.js +37 -3
  299. package/dist/webhooks.js.map +1 -1
  300. package/package.json +2 -2
@@ -0,0 +1,439 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PARITY_EXEMPT = exports.fullParityRequests = exports.waveParityRequests = exports.extraParityRequests = void 0;
4
+ exports.uncoveredRoutes = uncoveredRoutes;
5
+ const parity_1 = require("./parity");
6
+ const parity_emu3_authz_1 = require("./parity-emu3-authz");
7
+ const parity_emu3_billing_1 = require("./parity-emu3-billing");
8
+ const parity_emu3_messaging_1 = require("./parity-emu3-messaging");
9
+ const parity_emu3_misc_1 = require("./parity-emu3-misc");
10
+ const parity_emu3_oauth_1 = require("./parity-emu3-oauth");
11
+ const parity_emu4_federation_1 = require("./parity-emu4-federation");
12
+ const parity_emu4_compliance_1 = require("./parity-emu4-compliance");
13
+ const parity_emu4_trust_1 = require("./parity-emu4-trust");
14
+ const parity_emu4_messaging_1 = require("./parity-emu4-messaging");
15
+ const parity_emu4_billing_1 = require("./parity-emu4-billing");
16
+ const parity_emu4_actions_1 = require("./parity-emu4-actions");
17
+ const parity_emu4_oauth_1 = require("./parity-emu4-oauth");
18
+ const parity_emu4_authz_1 = require("./parity-emu4-authz");
19
+ /**
20
+ * The rest of the parity contract: one or more requests for every other hand-written route (access, webhooks, the change
21
+ * feed, keys, grants and usage, sessions and tokens, the machine device-key flow, agents, the OAuth provider, SCIM, the
22
+ * Passkey API and the round-two routes). Together with `defaultParityRequests` it covers every route the emulator
23
+ * answers by hand: `uncoveredRoutes` proves it, and a test fails when a new hand-written route has no request.
24
+ *
25
+ * Each request is self-contained (its own users and organizations, named with `{{rand}}`) and cleans up after itself.
26
+ * Requests that need a feature switched on in the reference instance carry a `group`; `runParity({ groups })` selects them.
27
+ */
28
+ const PKCE_VERIFIER = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk';
29
+ const PKCE_CHALLENGE = 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM';
30
+ const CB = 'https://app.example/callback';
31
+ const q = (id, method, path, rest = {}) => ({ id, method, path, ...rest });
32
+ exports.extraParityRequests = [
33
+ // ── fixtures ──
34
+ q('x user', 'POST', '/v1/users', { body: { email_address: 'x-{{rand}}@example.com', first_name: 'Ex', password: 'Correct-horse-9-battery' }, capture: { xu: 'id' } }),
35
+ q('x user 2', 'POST', '/v1/users', { body: { email_address: 'x2-{{rand}}@example.com' }, capture: { xu2: 'id' } }),
36
+ q('x org', 'POST', '/v1/organizations', { body: { name: 'Extra {{rand}}' }, capture: { xo: 'id' } }),
37
+ // ── users and organizations ──
38
+ q('user actions ban', 'POST', '/v1/users/{{xu2}}/ban'),
39
+ q('user actions unban', 'POST', '/v1/users/{{xu2}}/unban'),
40
+ q('user lock', 'POST', '/v1/users/{{xu2}}/lock'),
41
+ q('user unlock', 'POST', '/v1/users/{{xu2}}/unlock'),
42
+ q('user email add', 'POST', '/v1/users/{{xu}}/email_addresses', { body: { email_address: 'x-alt-{{rand}}@example.com' } }),
43
+ q('user memberships', 'GET', '/v1/users/{{xu}}/organization_memberships'),
44
+ q('org update', 'PATCH', '/v1/organizations/{{xo}}', { body: { name: 'Extra renamed {{rand}}' } }),
45
+ q('org metadata', 'PUT', '/v1/organizations/{{xo}}/metadata', { body: { public_metadata: { tier: 'a' } } }),
46
+ q('org member add', 'POST', '/v1/organizations/{{xo}}/memberships', { body: { user_id: '{{xu}}', role: 'org:member' } }),
47
+ q('org member role', 'PATCH', '/v1/organizations/{{xo}}/memberships/{{xu}}', { body: { role: 'org:admin' } }),
48
+ q('org invitation', 'POST', '/v1/organizations/{{xo}}/invitations', { body: { email: 'x-inv-{{rand}}@example.com' } }),
49
+ q('invitations list', 'GET', '/v1/invitations'),
50
+ q('org list', 'GET', '/v1/organizations?limit=2'),
51
+ q('org member remove', 'DELETE', '/v1/organizations/{{xo}}/memberships/{{xu}}'),
52
+ // ── roles and permissions ──
53
+ q('permission create', 'POST', '/v1/permissions', { body: { key: 'org:x_{{rand}}:read', name: 'Extra read' }, capture: { xp: 'id' } }),
54
+ q('role create', 'POST', '/v1/roles', { body: { key: 'org:x_{{rand}}', name: 'Extra role', permissions: [] }, capture: { xr: 'id' } }),
55
+ q('role update', 'PATCH', '/v1/roles/{{xr}}', { body: { name: 'Extra role 2' } }),
56
+ q('role permissions', 'PUT', '/v1/roles/{{xr}}/permissions', { body: { permissions: ['org:read'] } }),
57
+ q('role delete', 'DELETE', '/v1/roles/{{xr}}'),
58
+ q('permission delete', 'DELETE', '/v1/permissions/{{xp}}'),
59
+ // ── webhooks, events and the change feed ──
60
+ q('hook create', 'POST', '/v1/webhook_endpoints', { body: { url: 'https://example.com/hooks/x-{{rand}}', enabled_events: ['user.created'] }, capture: { xh: 'id' }, tolerate: ['secret'] }),
61
+ q('hook list', 'GET', '/v1/webhook_endpoints'),
62
+ q('hook update', 'PATCH', '/v1/webhook_endpoints/{{xh}}', { body: { description: 'x' } }),
63
+ q('hook deliveries', 'GET', '/v1/webhook_endpoints/{{xh}}/deliveries'),
64
+ q('hook rotate', 'POST', '/v1/webhook_endpoints/{{xh}}/rotate_secret', { tolerate: ['secret'] }),
65
+ q('hook reveal', 'POST', '/v1/webhook_endpoints/{{xh}}/reveal_secret', { tolerate: ['secret'] }),
66
+ q('hook redeliver', 'POST', '/v1/webhook_endpoints/{{xh}}/redeliver', { body: { status: 'failed' } }),
67
+ q('hook replay', 'POST', '/v1/webhook_endpoints/{{xh}}/replay', { body: { status: 'failed' } }),
68
+ q('hook test', 'POST', '/v1/webhook_endpoints/{{xh}}/test'),
69
+ q('hook delete', 'DELETE', '/v1/webhook_endpoints/{{xh}}'),
70
+ q('changes snapshot', 'GET', '/v1/changes/snapshot/user?limit=1'),
71
+ q('consumer ack', 'PUT', '/v1/changes/consumers/parity-{{rand}}/ack', { body: { seq: 0 } }),
72
+ q('consumer get', 'GET', '/v1/changes/consumers/parity-{{rand}}'),
73
+ q('consumers list', 'GET', '/v1/changes/consumers', { tolerate: ['data'] }),
74
+ // ── API keys ──
75
+ q('key create', 'POST', '/v1/api_keys', { body: { subject_type: 'user', subject_id: '{{xu}}', name: 'x', scopes: ['read'], expires_in_seconds: 86400 }, capture: { xk: 'id', xks: 'secret' }, tolerate: ['secret'] }),
76
+ q('key list', 'GET', '/v1/api_keys?subject_id={{xu}}'),
77
+ q('key get', 'GET', '/v1/api_keys/{{xk}}'),
78
+ q('key update', 'PATCH', '/v1/api_keys/{{xk}}', { body: { name: 'x2' } }),
79
+ q('key verify', 'POST', '/v1/api_keys/verify', { body: { secret: '{{xks}}' } }),
80
+ q('key revoked list', 'GET', '/v1/api_keys/revoked'),
81
+ q('key posture', 'GET', '/v1/api_keys/posture', { group: 'round2' }),
82
+ q('key callers', 'GET', '/v1/api_keys/{{xk}}/callers', { group: 'round2' }),
83
+ q('key rotate', 'POST', '/v1/api_keys/{{xk}}/rotate', { body: { overlap_seconds: 60 }, capture: { xk2: 'id' }, tolerate: ['secret'], group: 'round2' }),
84
+ q('key rotated revoke', 'DELETE', '/v1/api_keys/{{xk2}}', { group: 'round2' }),
85
+ q('key revoke', 'DELETE', '/v1/api_keys/{{xk}}'),
86
+ // ── machines, grants and usage counters ──
87
+ q('m create', 'POST', '/v1/machines', { body: { name: 'x machine {{rand}}' }, capture: { xm: 'id', xms: 'secret' }, tolerate: ['secret'] }),
88
+ q('m list', 'GET', '/v1/machines?limit=2'),
89
+ q('m update', 'PATCH', '/v1/machines/{{xm}}', { body: { name: 'x machine 2' } }),
90
+ q('m rotate secret', 'POST', '/v1/machines/{{xm}}/rotate_secret', { tolerate: ['secret'] }),
91
+ q('m verify', 'POST', '/v1/m2m_tokens/verify', { body: { token: '{{xms}}' } }),
92
+ q('m verify unknown', 'POST', '/v1/m2m_tokens/verify', { body: { token: 'mk_unknown' } }),
93
+ q('m grant create', 'POST', '/v1/machines/{{xm}}/grants', { body: { resource_server_id: 'rs_parity_{{rand}}', scopes: ['read'], constraints: { counters: { calls: { limit: 5, window: 'day' } } } }, capture: { xg: 'id' } }),
94
+ q('m grants', 'GET', '/v1/machines/{{xm}}/grants'),
95
+ q('m grant update', 'PATCH', '/v1/machines/{{xm}}/grants/{{xg}}', { body: { scopes: ['read', 'write'] } }),
96
+ q('grant consume', 'POST', '/v1/grants/machine/{{xg}}/consume', { body: { counter: 'calls', idempotency_key: 'c-{{rand}}', lease: true, ttl_seconds: 60 }, capture: { xl: 'lease.id' } }),
97
+ q('grant usage', 'GET', '/v1/grants/machine/{{xg}}/usage'),
98
+ q('grant lease release', 'POST', '/v1/grants/machine/{{xg}}/leases/{{xl}}/release', { body: { consumed: 0 } }),
99
+ q('grant consume 2', 'POST', '/v1/grants/machine/{{xg}}/consume', { body: { counter: 'calls', idempotency_key: 'c2-{{rand}}' } }),
100
+ q('grant refund', 'POST', '/v1/grants/machine/{{xg}}/refund', { body: { counter: 'calls', idempotency_key: 'r-{{rand}}', consume_idempotency_key: 'c2-{{rand}}' } }),
101
+ q('m revoke', 'POST', '/v1/machines/{{xm}}/revoke'),
102
+ q('m approve', 'POST', '/v1/machines/{{xm}}/approve'),
103
+ q('m grant delete', 'DELETE', '/v1/machines/{{xm}}/grants/{{xg}}'),
104
+ q('m delete', 'DELETE', '/v1/machines/{{xm}}'),
105
+ // ── sessions, tokens and keys ──
106
+ q('session create', 'POST', '/v1/sessions', { body: { user_id: '{{xu}}' }, capture: { xs: 'id', xjwt: 'jwt', xrt: 'refresh_token' }, tolerate: ['jwt', 'refresh_token'] }),
107
+ q('session list', 'GET', '/v1/sessions?user_id={{xu}}'),
108
+ q('session get', 'GET', '/v1/sessions/{{xs}}'),
109
+ q('session events', 'GET', '/v1/sessions/{{xs}}/events', { group: 'round2' }),
110
+ q('token verify', 'POST', '/v1/tokens/verify', { body: { token: '{{xjwt}}' } }),
111
+ q('session refresh', 'POST', '/v1/client/sessions/{{xs}}/tokens', { anonymous: true, body: { refresh_token: '{{xrt}}' }, capture: { xrt2: 'refresh_token' }, tolerate: ['jwt', 'refresh_token'] }),
112
+ q('session refresh replay', 'POST', '/v1/client/sessions/{{xs}}/tokens', { anonymous: true, body: { refresh_token: '{{xrt}}' }, tolerate: ['jwt', 'refresh_token'] }),
113
+ q('session refresh unknown', 'POST', '/v1/client/sessions/{{xs}}/tokens', { anonymous: true, body: { refresh_token: 'rt_unknown' } }),
114
+ q('session template token', 'POST', '/v1/sessions/{{xs}}/tokens/missing_template'),
115
+ q('session revoke', 'POST', '/v1/sessions/{{xs}}/revoke'),
116
+ q('revocations', 'GET', '/v1/sessions/revocations', { group: 'round2' }),
117
+ q('status list', 'GET', '/v1/sessions/revocations/status-list', { group: 'round2', tolerate: ['token'] }),
118
+ q('revoke by filter dry run', 'POST', '/v1/sessions/revoke_by_filter', { body: { user_id: '{{xu}}', dry_run: true }, group: 'round2' }),
119
+ q('revoke by filter', 'POST', '/v1/sessions/revoke_by_filter', { body: { user_id: '{{xu}}' }, capture: { xj: 'id' }, group: 'round2', tolerate: ['status', 'matched', 'revoked', 'completed_at'] }),
120
+ q('revoke jobs', 'GET', '/v1/sessions/revoke_jobs', { group: 'round2' }),
121
+ q('revoke job', 'GET', '/v1/sessions/revoke_jobs/{{xj}}', { group: 'round2', tolerate: ['status', 'matched', 'revoked', 'completed_at'] }),
122
+ q('signing keys', 'GET', '/v1/instance/signing_keys'),
123
+ q('jwks', 'GET', '/.well-known/jwks.json', { anonymous: true, tolerate: ['keys'] }),
124
+ q('discovery', 'GET', '/.well-known/openid-configuration', { anonymous: true, tolerate: ['atlas_emulator', 'atlas_previous_issuers'] }),
125
+ // ── users bulk and deleted addresses ──
126
+ q('bulk dry run', 'POST', '/v1/users/bulk_actions', { body: { action: 'ban', user_ids: ['{{xu2}}'], dry_run: true }, group: 'round2' }),
127
+ q('bulk ban', 'POST', '/v1/users/bulk_actions', { body: { action: 'unban', user_ids: ['{{xu2}}'] }, capture: { xb: 'id' }, group: 'round2', tolerate: ['status', 'processed', 'succeeded', 'completed_at', 'progress', 'created_by'] }),
128
+ q('bulk list', 'GET', '/v1/users/bulk_actions', { group: 'round2' }),
129
+ q('bulk get', 'GET', '/v1/users/bulk_actions/{{xb}}', { group: 'round2', tolerate: ['status', 'processed', 'succeeded', 'completed_at', 'progress', 'created_by'] }),
130
+ q('bulk cancel', 'POST', '/v1/users/bulk_actions/{{xb}}/cancel', { group: 'round2', tolerate: ['status', 'processed', 'succeeded', 'completed_at', 'progress', 'created_by'] }),
131
+ q('deleted addresses', 'POST', '/v1/users/deleted_addresses', { body: { addresses: ['gone-{{rand}}@example.com'] }, group: 'round2' }),
132
+ q('deleted address check', 'POST', '/v1/users/deleted_addresses/check', { body: { address: 'gone-{{rand}}@example.com' }, group: 'round2' }),
133
+ // ── machine device-key flow ──
134
+ q('enrol token', 'POST', '/v1/machine_enrolment_tokens', { body: { max_uses: 2 }, capture: { xet: 'id', xets: 'secret' }, tolerate: ['secret'], group: 'device' }),
135
+ q('enrol token code', 'POST', '/v1/machine_enrolment_tokens', { body: { format: 'user_code' }, capture: { xetc: 'id' }, tolerate: ['user_code'], group: 'device' }),
136
+ q('enrol tokens list', 'GET', '/v1/machine_enrolment_tokens', { group: 'device' }),
137
+ q('enrol redeem', 'POST', '/v1/machines/redeem', { anonymous: true, body: { enrolment_token: '{{xets}}' }, group: 'device' }),
138
+ q('enrol bad key', 'POST', '/v1/machines/enroll', { anonymous: true, body: { enrolment_token: '{{xets}}', public_key_pem: 'not a key' }, group: 'device' }),
139
+ q('enrol bad token', 'POST', '/v1/machines/enroll', { anonymous: true, body: { enrolment_token: 'met_unknown', public_key_pem: 'x' }, group: 'device' }),
140
+ q('challenge unknown', 'POST', '/v1/machines/challenge', { anonymous: true, body: { machine_id: 'mch_unknown' }, group: 'device' }),
141
+ q('device token unknown', 'POST', '/v1/machines/token', { anonymous: true, body: { machine_id: 'mch_unknown', nonce: 'n', signature: 's' }, group: 'device' }),
142
+ q('enrol token delete', 'DELETE', '/v1/machine_enrolment_tokens/{{xet}}', { group: 'device' }),
143
+ q('enrol token code delete', 'DELETE', '/v1/machine_enrolment_tokens/{{xetc}}', { group: 'device' }),
144
+ // ── agents ──
145
+ q('agent config', 'PUT', '/v1/agent_registration', { body: { enabled: true, allow_anonymous: true, allow_service_auth: true, allow_refresh: true, untrusted_scopes: ['read'], trusted_scopes: ['read', 'write'] }, group: 'agents' }),
146
+ q('agent config get', 'GET', '/v1/agent_registration', { group: 'agents' }),
147
+ q('agent register', 'POST', '/v1/agents/register', { anonymous: true, body: { identity: 'anonymous', name: 'agent {{rand}}' }, capture: { xa: 'id', xas: 'secret' }, tolerate: ['secret'], group: 'agents' }),
148
+ q('agent register refresh', 'POST', '/v1/agents/register', { anonymous: true, body: { identity: 'refresh', agent_id: '{{xa}}', agent_secret: '{{xas}}' }, capture: { xas: 'secret' }, tolerate: ['secret'], group: 'agents' }),
149
+ q('agent token', 'POST', '/v1/agents/token', { anonymous: true, body: { agent_id: '{{xa}}', agent_secret: '{{xas}}' }, tolerate: ['token'], group: 'agents' }),
150
+ q('agent claim code', 'POST', '/v1/agents/claim', { anonymous: true, body: { agent_id: '{{xa}}', agent_secret: '{{xas}}' }, tolerate: ['user_code'], group: 'agents' }),
151
+ q('agent access request', 'POST', '/v1/agents/access_requests', { anonymous: true, body: { agent_id: '{{xa}}', agent_secret: '{{xas}}', kind: 'tool', resource: 'tool:search', scopes: ['read'], constraints: { counters: { calls: { limit: 3, window: 'day' } } } }, capture: { xar: 'id' }, group: 'agents' }),
152
+ q('agent requests', 'GET', '/v1/agent_access_requests?agent_id={{xa}}', { group: 'agents' }),
153
+ q('agent approve', 'POST', '/v1/agent_access_requests/{{xar}}/approve', { body: { expires_in_seconds: 3600 }, group: 'agents' }),
154
+ q('agent grant consume', 'POST', '/v1/grants/agent/{{xar}}/consume', { body: { counter: 'calls', idempotency_key: 'ac-{{rand}}' }, group: 'agents' }),
155
+ q('agent grant usage', 'GET', '/v1/grants/agent/{{xar}}/usage', { group: 'agents' }),
156
+ q('agent deny settled', 'POST', '/v1/agent_access_requests/{{xar}}/deny', { group: 'agents' }),
157
+ q('agents list', 'GET', '/v1/agents', { group: 'agents' }),
158
+ q('agent get', 'GET', '/v1/agents/{{xa}}', { group: 'agents' }),
159
+ q('agent owner', 'PUT', '/v1/agents/{{xa}}/owner', { body: { user_id: '{{xu}}' }, group: 'agents' }),
160
+ q('agent claim backend', 'POST', '/v1/agents/{{xa}}/claim', { body: { user_id: '{{xu}}' }, group: 'agents' }),
161
+ q('agent revoke access', 'POST', '/v1/agents/{{xa}}/revoke_access', { body: { disable: true }, group: 'agents', tolerate: ['revoked_grants'] }),
162
+ // ── OAuth provider ──
163
+ q('oauth client', 'POST', '/v1/oauth_clients', { body: { name: 'Parity app {{rand}}', redirect_uris: [CB], grant_types: ['authorization_code', 'refresh_token', 'client_credentials'], allowed_scopes: ['openid', 'email', 'profile', 'offline_access'] }, capture: { xc: 'client_id', xcs: 'client_secret' }, tolerate: ['client_secret'], group: 'oauth' }),
164
+ q('oauth clients', 'GET', '/v1/oauth_clients', { group: 'oauth' }),
165
+ q('oauth client get', 'GET', '/v1/oauth_clients/{{xc}}', { group: 'oauth' }),
166
+ q('oauth client update', 'PATCH', '/v1/oauth_clients/{{xc}}', { body: { name: 'Parity app renamed {{rand}}' }, group: 'oauth' }),
167
+ q('oauth metadata', 'GET', '/.well-known/oauth-authorization-server', { anonymous: true, group: 'oauth', tolerate: ['atlas_emulator', 'atlas_previous_issuers'] }),
168
+ q('oauth authorize', 'GET', `/oauth2/authorize?client_id={{xc}}&redirect_uri=${encodeURIComponent(CB)}&response_type=code&scope=openid%20email%20offline_access&state=st1&nonce=n1&code_challenge=${PKCE_CHALLENGE}&code_challenge_method=S256&login_hint={{xu}}`, { anonymous: true, capture: { xcode: 'location#code' }, group: 'oauth' }),
169
+ q('oauth authorize bad client', 'GET', `/oauth2/authorize?client_id=oac_unknown&redirect_uri=${encodeURIComponent(CB)}&response_type=code`, { anonymous: true, group: 'oauth' }),
170
+ q('oauth token', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'authorization_code', code: '{{xcode}}', redirect_uri: CB, client_id: '{{xc}}', client_secret: '{{xcs}}', code_verifier: PKCE_VERIFIER }, capture: { xat: 'access_token', xort: 'refresh_token' }, tolerate: ['access_token', 'refresh_token', 'id_token'], group: 'oauth' }),
171
+ q('oauth token code replay', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'authorization_code', code: '{{xcode}}', redirect_uri: CB, client_id: '{{xc}}', client_secret: '{{xcs}}', code_verifier: PKCE_VERIFIER }, group: 'oauth' }),
172
+ q('oauth token bad grant', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'nonsense', client_id: '{{xc}}', client_secret: '{{xcs}}' }, group: 'oauth' }),
173
+ q('oauth introspect', 'POST', '/oauth2/introspect', { anonymous: true, form: { token: '{{xat}}', client_id: '{{xc}}', client_secret: '{{xcs}}' }, tolerate: ['sub', 'aud', 'iss', 'exp', 'iat', 'jti', 'token_type', 'token_use'], group: 'oauth' }),
174
+ q('oauth userinfo', 'GET', '/oauth2/userinfo', { anonymous: true, headers: { authorization: 'Bearer {{xat}}' }, group: 'oauth' }),
175
+ q('oauth userinfo post', 'POST', '/oauth2/userinfo', { anonymous: true, headers: { authorization: 'Bearer {{xat}}' }, group: 'oauth' }),
176
+ q('oauth refresh', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'refresh_token', refresh_token: '{{xort}}', client_id: '{{xc}}', client_secret: '{{xcs}}' }, capture: { xort2: 'refresh_token' }, tolerate: ['access_token', 'refresh_token', 'id_token'], group: 'oauth' }),
177
+ q('oauth client credentials', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'client_credentials', client_id: '{{xc}}', client_secret: '{{xcs}}' }, capture: { xcc: 'access_token' }, tolerate: ['access_token'], group: 'oauth' }),
178
+ q('oauth token exchange', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', subject_token: '{{xcc}}', subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', client_id: '{{xc}}', client_secret: '{{xcs}}' }, tolerate: ['access_token', 'scope'], group: 'oauth' }),
179
+ q('oauth revoke', 'POST', '/oauth2/revoke', { anonymous: true, form: { token: '{{xort2}}', client_id: '{{xc}}', client_secret: '{{xcs}}' }, group: 'oauth' }),
180
+ q('oauth introspect revoked', 'POST', '/oauth2/introspect', { anonymous: true, form: { token: '{{xort2}}', client_id: '{{xc}}', client_secret: '{{xcs}}' }, group: 'oauth' }),
181
+ q('oauth client rotate', 'POST', '/v1/oauth_clients/{{xc}}/rotate_secret', { tolerate: ['client_secret'], group: 'oauth' }),
182
+ q('oauth client delete', 'DELETE', '/v1/oauth_clients/{{xc}}', { group: 'oauth' }),
183
+ // ── SCIM ──
184
+ q('scim token', 'POST', '/v1/scim_tokens', { body: { organization_id: '{{xo}}', name: 'parity' }, capture: { xst: 'id', xsts: 'token' }, tolerate: ['token', 'prefix'], group: 'scim' }),
185
+ q('scim tokens', 'GET', '/v1/scim_tokens?organization_id={{xo}}', { group: 'scim' }),
186
+ q('scim token update', 'PATCH', '/v1/scim_tokens/{{xst}}', { body: { name: 'parity 2' }, group: 'scim' }),
187
+ q('scim config', 'GET', '/scim/v2/ServiceProviderConfig', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, group: 'scim' }),
188
+ q('scim resource types', 'GET', '/scim/v2/ResourceTypes', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, group: 'scim', tolerate: ['Resources'] }),
189
+ q('scim schemas', 'GET', '/scim/v2/Schemas', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, group: 'scim', tolerate: ['Resources'] }),
190
+ q('scim unauthenticated', 'GET', '/scim/v2/Users', { anonymous: true, group: 'scim' }),
191
+ q('scim user create', 'POST', '/scim/v2/Users', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, body: { schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'], userName: 'scim-{{rand}}@example.com', name: { givenName: 'Scim', familyName: 'User' }, active: true }, capture: { xsu: 'id' }, tolerate: ['meta', 'externalId'], group: 'scim' }),
192
+ q('scim user list', 'GET', '/scim/v2/Users?filter=userName%20eq%20%22scim-{{rand}}%40example.com%22', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, tolerate: ['Resources'], group: 'scim' }),
193
+ q('scim user get', 'GET', '/scim/v2/Users/{{xsu}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, tolerate: ['meta', 'externalId'], group: 'scim' }),
194
+ q('scim user deactivate', 'PATCH', '/scim/v2/Users/{{xsu}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, body: { schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'], Operations: [{ op: 'replace', path: 'active', value: false }] }, tolerate: ['meta', 'externalId'], group: 'scim' }),
195
+ q('scim user replace', 'PUT', '/scim/v2/Users/{{xsu}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, body: { schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'], userName: 'scim-{{rand}}@example.com', active: true }, tolerate: ['meta', 'externalId', 'name', 'displayName'], group: 'scim' }),
196
+ q('scim group create', 'POST', '/scim/v2/Groups', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, body: { schemas: ['urn:ietf:params:scim:schemas:core:2.0:Group'], displayName: 'Parity {{rand}}', members: [] }, capture: { xsg: 'id' }, tolerate: ['meta', 'externalId'], group: 'scim' }),
197
+ q('scim groups', 'GET', '/scim/v2/Groups', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, tolerate: ['Resources'], group: 'scim' }),
198
+ q('scim group get', 'GET', '/scim/v2/Groups/{{xsg}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, tolerate: ['meta', 'externalId'], group: 'scim' }),
199
+ q('scim group patch', 'PATCH', '/scim/v2/Groups/{{xsg}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, body: { schemas: ['urn:ietf:params:scim:api:messages:2.0:PatchOp'], Operations: [{ op: 'add', path: 'members', value: [{ value: '{{xsu}}' }] }] }, tolerate: ['meta', 'externalId', 'members'], group: 'scim' }),
200
+ q('scim group delete', 'DELETE', '/scim/v2/Groups/{{xsg}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, group: 'scim' }),
201
+ q('scim user delete', 'DELETE', '/scim/v2/Users/{{xsu}}', { anonymous: true, headers: { authorization: 'Bearer {{xsts}}' }, group: 'scim' }),
202
+ q('scim token revoke', 'POST', '/v1/scim_tokens/{{xst}}/revoke', { group: 'scim' }),
203
+ // ── Passkey API (the ceremonies need a real authenticator: parity covers the server-side shapes) ──
204
+ q('passkey register init', 'POST', '/v1/passkey_api/registration/init', { body: { user_id: 'ext-{{rand}}', username: 'ex' }, tolerate: ['options'], group: 'passkeys' }),
205
+ q('passkey register finalize bad', 'POST', '/v1/passkey_api/registration/finalize', { body: { user_id: 'ext-{{rand}}', challenge: 'unknown', attestation_object: 'AA', client_data_json: 'AA' }, group: 'passkeys' }),
206
+ q('passkey login init', 'POST', '/v1/passkey_api/login/init', { body: { user_id: 'ext-{{rand}}' }, tolerate: ['handle', 'options'], group: 'passkeys' }),
207
+ q('passkey login finalize bad', 'POST', '/v1/passkey_api/login/finalize', { body: { handle: 'pkh_unknown', challenge: 'unknown', credential_id: 'AA', authenticator_data: 'AA', client_data_json: 'AA', signature: 'AA' }, group: 'passkeys' }),
208
+ q('passkeys list', 'GET', '/v1/passkey_api/users/ext-{{rand}}/passkeys', { group: 'passkeys' }),
209
+ q('passkey delete unknown', 'DELETE', '/v1/passkey_api/users/ext-{{rand}}/passkeys/pk_unknown', { group: 'passkeys' }),
210
+ q('passkey usage', 'GET', '/v1/passkey_api/usage', { group: 'passkeys' }),
211
+ // ── cleanup ──
212
+ q('x org delete', 'DELETE', '/v1/organizations/{{xo}}'),
213
+ q('x user delete', 'DELETE', '/v1/users/{{xu}}'),
214
+ q('x user 2 delete', 'DELETE', '/v1/users/{{xu2}}'),
215
+ ];
216
+ const P256 = { kty: 'EC', crv: 'P-256', x: 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU', y: 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0' };
217
+ const FLOW_CLIENT = { client_id: '{{woc}}', client_secret: '{{wocs}}' };
218
+ const asUser = { authorization: 'Bearer {{wjwt}}' };
219
+ /** One consent round: push the request (forcing the consent screen), then stop at the consent page the authorize call redirects to. */
220
+ const consentRound = (tag, state) => [
221
+ q(`${tag} par`, 'POST', '/oauth2/par', { anonymous: true, group: 'oauth_flows', form: { ...FLOW_CLIENT, redirect_uri: CB, response_type: 'code', scope: 'openid profile', code_challenge: PKCE_CHALLENGE, code_challenge_method: 'S256', login_hint: '{{wu}}', prompt: 'consent', state }, capture: { [`${tag}ru`]: 'request_uri' } }),
222
+ q(`${tag} authorize`, 'GET', `/oauth2/authorize?client_id={{woc}}&request_uri={{${tag}ru}}`, { anonymous: true, group: 'oauth_flows', capture: { [`${tag}ct`]: 'location#consent_token' } }),
223
+ ];
224
+ /**
225
+ * The routes of the machine federation, OAuth flows, approvals, governance, messaging, billing, hooks, platform and
226
+ * connection work. Requests that need a feature or a catalogue the reference instance may not have carry a `group`.
227
+ */
228
+ exports.waveParityRequests = [
229
+ // ── fixtures ──
230
+ q('w user', 'POST', '/v1/users', { body: { email_address: 'w-{{rand}}@example.com', first_name: 'Wave', password: 'Correct-horse-9-battery' }, capture: { wu: 'id' } }),
231
+ q('w org', 'POST', '/v1/organizations', { body: { name: 'Wave {{rand}}' }, capture: { wo: 'id' } }),
232
+ q('w member', 'POST', '/v1/organizations/{{wo}}/memberships', { body: { user_id: '{{wu}}', role: 'org:admin' } }),
233
+ q('w machine', 'POST', '/v1/machines', { body: { name: 'wave machine' }, capture: { wm: 'id' }, tolerate: ['secret'] }),
234
+ q('w session', 'POST', '/v1/sessions', { body: { user_id: '{{wu}}' }, capture: { wjwt: 'jwt', wsid: 'id' }, tolerate: ['jwt', 'refresh_token'] }),
235
+ // ── machine federation and token binding ──
236
+ q('trust policy create', 'POST', '/v1/machine_trust_policies', { body: { machine_id: '{{wm}}', name: 'gha-{{rand}}', kind: 'github_actions', audience: 'https://atlas.example', subject_pattern: 'repo:parity-org/repo:*', jwks: { keys: [P256] } }, capture: { wtp: 'id' }, group: 'federation' }),
237
+ q('trust policies', 'GET', '/v1/machine_trust_policies', { group: 'federation' }),
238
+ q('trust policy get', 'GET', '/v1/machine_trust_policies/{{wtp}}', { group: 'federation' }),
239
+ q('trust policy patch', 'PATCH', '/v1/machine_trust_policies/{{wtp}}', { body: { max_ttl_seconds: 300 }, group: 'federation' }),
240
+ q('trust policy test', 'POST', '/v1/machine_trust_policies/{{wtp}}/test', { body: { token: 'a.b.c' }, group: 'federation' }),
241
+ q('federate refused', 'POST', '/v1/machines/federate', { anonymous: true, body: { machine_id: '{{wm}}', token: 'a.b.c' }, group: 'federation' }),
242
+ q('trust policy delete', 'DELETE', '/v1/machine_trust_policies/{{wtp}}', { group: 'federation' }),
243
+ q('machine binding', 'PATCH', '/v1/machines/{{wm}}', { body: { token_binding: 'any' }, group: 'federation' }),
244
+ q('machine unbind', 'PATCH', '/v1/machines/{{wm}}', { body: { token_binding: 'none' }, group: 'federation' }),
245
+ // ── OAuth: consent, PAR, device authorization, CIBA ──
246
+ q('w client', 'POST', '/v1/oauth_clients', { body: { name: 'Wave client {{rand}}', redirect_uris: [CB], grant_types: ['authorization_code', 'refresh_token', 'urn:ietf:params:oauth:grant-type:device_code', 'urn:openid:params:grant-type:ciba'], token_endpoint_auth_method: 'client_secret_post', require_consent: true }, capture: { woc: 'client_id', wocs: 'client_secret' }, tolerate: ['client_secret'], group: 'oauth_flows' }),
247
+ ...consentRound('ca', 'a'),
248
+ q('consent screen', 'GET', '/oauth2/authorize/consent?consent_token={{cact}}', { anonymous: true, group: 'oauth_flows' }),
249
+ q('consent approve', 'POST', '/oauth2/authorize/consent', { anonymous: true, body: { consent_token: '{{cact}}', decision: 'approve' }, group: 'oauth_flows' }),
250
+ ...consentRound('cb', 'b'),
251
+ q('consent request', 'GET', '/v1/oauth_consent_requests?consent_challenge={{cbct}}', { group: 'oauth_flows' }),
252
+ q('consent accept', 'PUT', '/v1/oauth_consent_requests/accept?consent_challenge={{cbct}}', { body: { remember: false }, group: 'oauth_flows' }),
253
+ ...consentRound('cc', 'c'),
254
+ q('consent reject', 'PUT', '/v1/oauth_consent_requests/reject?consent_challenge={{ccct}}', { body: { error: 'access_denied' }, group: 'oauth_flows' }),
255
+ q('device authorization', 'POST', '/oauth2/device_authorization', { anonymous: true, form: { ...FLOW_CLIENT, scope: 'openid' }, capture: { wdc: 'device_code', wuc: 'user_code' }, tolerate: ['device_code', 'user_code', 'verification_uri', 'verification_uri_complete'], group: 'oauth_flows' }),
256
+ q('device page', 'GET', '/oauth2/device?user_code={{wuc}}', { anonymous: true, group: 'oauth_flows' }),
257
+ q('device verify', 'POST', '/oauth2/device/verify', { anonymous: true, body: { user_code: '{{wuc}}', decision: 'approve', user_id: '{{wu}}' }, group: 'oauth_flows' }),
258
+ q('device token', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code', device_code: '{{wdc}}', ...FLOW_CLIENT }, tolerate: ['access_token', 'refresh_token', 'id_token', 'scope'], group: 'oauth_flows' }),
259
+ q('ciba start', 'POST', '/oauth2/backchannel_authentication', { anonymous: true, form: { ...FLOW_CLIENT, login_hint: '{{wu}}', scope: 'openid', binding_message: 'Approve 42' }, capture: { wcb: 'auth_req_id' }, tolerate: ['auth_req_id'], group: 'oauth_flows' }),
260
+ q('ciba pending', 'GET', '/v1/client/me/backchannel_requests', { anonymous: true, headers: asUser, capture: { wcid: 'data.0.id' }, group: 'oauth_flows' }),
261
+ q('ciba answer', 'POST', '/v1/client/me/backchannel_requests/{{wcid}}', { anonymous: true, headers: asUser, body: { decision: 'approve' }, group: 'oauth_flows' }),
262
+ q('ciba token', 'POST', '/oauth2/token', { anonymous: true, form: { grant_type: 'urn:openid:params:grant-type:ciba', auth_req_id: '{{wcb}}', ...FLOW_CLIENT }, tolerate: ['access_token', 'refresh_token', 'id_token', 'scope'], group: 'oauth_flows' }),
263
+ q('w client delete', 'DELETE', '/v1/oauth_clients/{{woc}}', { group: 'oauth_flows' }),
264
+ // ── approvals v2 ──
265
+ q('approval create', 'POST', '/v1/approval_requests', { body: { user_id: '{{wu}}', description: 'Wire 100 to Acme', reference: 'ref-{{rand}}', binding: 'bind-{{rand}}' }, capture: { wap: 'id', wapn: 'match_number' }, tolerate: ['match_number'], group: 'approvals' }),
266
+ q('approval user create', 'POST', '/v1/users/{{wu}}/approval_requests', { body: { description: 'Second request' }, capture: { wap2: 'id' }, tolerate: ['match_number'], group: 'approvals' }),
267
+ q('approval list', 'GET', '/v1/approval_requests?status=pending&limit=5', { group: 'approvals' }),
268
+ q('approval devices', 'GET', '/v1/users/{{wu}}/approval_devices', { group: 'approvals' }),
269
+ q('approval device trust unknown', 'POST', '/v1/users/{{wu}}/approval_devices/trust', { body: { kind: 'device_key', ref: 'apk_unknown_{{rand}}' }, group: 'approvals' }),
270
+ q('approval device trust invalid', 'POST', '/v1/users/{{wu}}/approval_devices/trust', { body: { kind: 'nope', ref: 'x' }, group: 'approvals' }),
271
+ q('approval device untrust unknown', 'DELETE', '/v1/users/{{wu}}/approval_devices/atd_unknown_{{rand}}', { group: 'approvals' }),
272
+ q('approval get', 'GET', '/v1/approval_requests/{{wap}}', { group: 'approvals' }),
273
+ q('approval user get', 'GET', '/v1/users/{{wu}}/approval_requests/{{wap2}}', { group: 'approvals' }),
274
+ q('approval user cancel', 'POST', '/v1/users/{{wu}}/approval_requests/{{wap2}}/cancel', { group: 'approvals' }),
275
+ q('approver list', 'GET', '/v1/client/me/approvals', { anonymous: true, headers: asUser, group: 'approvals' }),
276
+ q('approver get', 'GET', '/v1/client/me/approvals/{{wap}}', { anonymous: true, headers: asUser, group: 'approvals' }),
277
+ q('approver decide', 'POST', '/v1/client/me/approvals/{{wap}}/decision', { anonymous: true, headers: asUser, body: { decision: 'approve', number: '{{wapn}}' }, group: 'approvals' }),
278
+ q('approval consume', 'POST', '/v1/approval_requests/{{wap}}/consume', { body: { binding: 'bind-{{rand}}' }, tolerate: ['receipt'], group: 'approvals' }),
279
+ q('approval create 3', 'POST', '/v1/approval_requests', { body: { user_id: '{{wu}}', description: 'Third request' }, capture: { wap3: 'id' }, tolerate: ['match_number'], group: 'approvals' }),
280
+ q('approval cancel', 'POST', '/v1/approval_requests/{{wap3}}/cancel', { group: 'approvals' }),
281
+ q('approval keys', 'GET', '/v1/client/me/approval_keys', { anonymous: true, headers: asUser, group: 'approvals' }),
282
+ q('approval key add', 'POST', '/v1/client/me/approval_keys', { anonymous: true, headers: asUser, body: { public_jwk: P256, label: 'parity' }, capture: { wak: 'id' }, group: 'approvals' }),
283
+ q('approval key delete', 'DELETE', '/v1/client/me/approval_keys/{{wak}}', { anonymous: true, headers: asUser, group: 'approvals' }),
284
+ // ── step-up policies and receipts ──
285
+ q('step-up policy create', 'POST', '/v1/step_up/policies', { body: { name: 'wp-{{rand}}', max_age_seconds: 600 }, capture: { wsp: 'id' }, group: 'governance' }),
286
+ q('step-up policies', 'GET', '/v1/step_up/policies', { group: 'governance' }),
287
+ q('step-up policy get', 'GET', '/v1/step_up/policies/{{wsp}}', { group: 'governance' }),
288
+ q('step-up policy patch', 'PATCH', '/v1/step_up/policies/{{wsp}}', { body: { description: 'parity' }, group: 'governance' }),
289
+ q('step-up assert', 'POST', '/v1/step_up/assert', { body: { token: '{{wjwt}}', policy: 'wp-{{rand}}' }, tolerate: ['receipt', 'receipt_id', 'receipt_expires_at', 'step_up', 'reason'], group: 'governance' }),
290
+ q('step-up policy delete', 'DELETE', '/v1/step_up/policies/{{wsp}}', { group: 'governance' }),
291
+ // ── authz explain, what-if, FGA sync, event feed ──
292
+ q('authz explain', 'POST', '/v1/authz/explain', { body: { subject: { id: '{{wu}}' }, permission: 'org:read' }, tolerate: ['layers', 'deciding'], group: 'governance' }),
293
+ q('authz rbac check', 'POST', '/v1/authz/rbac/check', { body: { subject: { id: '{{wu}}' }, permission: 'org:read' }, tolerate: ['grants', 'rules'], group: 'governance' }),
294
+ q('what-if run', 'POST', '/v1/authz/what_if', { body: { candidate: { rbac: { roles: [{ type: 'org_role', key: 'org:member', add_permissions: ['org:read'] }] } } }, capture: { wwi: 'id' }, tolerate: ['samples', 'window', 'not_evaluated'], group: 'governance' }),
295
+ q('what-if list', 'GET', '/v1/authz/what_if', { group: 'governance' }),
296
+ q('what-if get', 'GET', '/v1/authz/what_if/{{wwi}}', { tolerate: ['samples', 'window', 'not_evaluated'], group: 'governance' }),
297
+ q('what-if delete', 'DELETE', '/v1/authz/what_if/{{wwi}}', { group: 'governance' }),
298
+ q('fga sync status', 'GET', '/v1/fga/sync', { tolerate: ['last_run'], group: 'governance' }),
299
+ q('fga sync off', 'PUT', '/v1/fga/sync', { body: { enabled: false }, tolerate: ['last_run'], group: 'governance' }),
300
+ q('fga sync backfill', 'POST', '/v1/fga/sync/backfill', { body: {}, group: 'governance' }),
301
+ q('fga sync runs', 'GET', '/v1/fga/sync/runs', { group: 'governance' }),
302
+ q('fga sync run', 'GET', '/v1/fga/sync/runs/fsr_unknown', { group: 'governance' }),
303
+ q('fga sync purge', 'POST', '/v1/fga/sync/purge', { body: { confirm: true }, group: 'governance' }),
304
+ q('authz events', 'GET', '/v1/authz/events?after=latest', { tolerate: ['next_after'], group: 'governance' }),
305
+ // ── legal hold ──
306
+ q('legal hold status', 'GET', '/v1/users/{{wu}}/legal_hold', { group: 'governance' }),
307
+ q('legal hold place', 'POST', '/v1/users/{{wu}}/legal_hold', { body: { reason: 'parity case', case_ref: 'case-{{rand}}' }, group: 'governance' }),
308
+ q('legal holds', 'GET', '/v1/legal_holds', { group: 'governance' }),
309
+ q('legal hold release', 'POST', '/v1/users/{{wu}}/legal_hold/release', { body: { reason: 'done' }, group: 'governance' }),
310
+ // ── instance export and import, session export ──
311
+ q('export create', 'POST', '/v1/instance/exports', { body: {}, capture: { wex: 'id' }, tolerate: ['bundle_sha256', 'signature', 'counts'], group: 'governance' }),
312
+ q('exports', 'GET', '/v1/instance/exports', { tolerate: ['bundle_sha256', 'signature', 'counts'], group: 'governance' }),
313
+ q('export get', 'GET', '/v1/instance/exports/{{wex}}', { tolerate: ['bundle_sha256', 'signature', 'counts'], group: 'governance' }),
314
+ q('export bundle', 'GET', '/v1/instance/exports/{{wex}}/bundle', { group: 'governance' }),
315
+ q('export link', 'POST', '/v1/instance/exports/{{wex}}/download_url', { tolerate: ['url', 'expires_at'], group: 'governance' }),
316
+ q('export download refused', 'GET', '/v1/instance/exports/{{wex}}/download?i=x&exp=1&sig=x', { anonymous: true, group: 'governance' }),
317
+ q('export revoke links', 'POST', '/v1/instance/exports/{{wex}}/revoke_links', { tolerate: ['bundle_sha256', 'signature', 'counts'], group: 'governance' }),
318
+ q('import dry run', 'POST', '/v1/instance/imports', { body: { bundle: { version: 1, users: [], organizations: [], memberships: [] }, dry_run: true }, capture: { wim: 'id' }, tolerate: ['counts', 'errors'], group: 'governance' }),
319
+ q('imports', 'GET', '/v1/instance/imports', { tolerate: ['counts', 'errors'], group: 'governance' }),
320
+ q('import get', 'GET', '/v1/instance/imports/{{wim}}', { tolerate: ['counts', 'errors'], group: 'governance' }),
321
+ q('sessions export', 'GET', '/v1/sessions/export', { group: 'governance' }),
322
+ // ── messaging ──
323
+ q('messages', 'GET', '/v1/messages?limit=5', { group: 'messaging' }),
324
+ q('messages summary', 'GET', '/v1/messages/summary', { group: 'messaging' }),
325
+ q('message unknown', 'GET', '/v1/messages/msg_unknown', { group: 'messaging' }),
326
+ q('suppress', 'POST', '/v1/suppressions', { body: { address: 'sup-{{rand}}@example.com', reason: 'manual' }, capture: { wsup: 'data.0.id' }, group: 'messaging' }),
327
+ q('suppressions', 'GET', '/v1/suppressions', { group: 'messaging' }),
328
+ q('suppression get', 'GET', '/v1/suppressions/{{wsup}}', { group: 'messaging' }),
329
+ q('unsuppress', 'DELETE', '/v1/suppressions/{{wsup}}', { group: 'messaging' }),
330
+ q('fallbacks', 'GET', '/v1/messaging_providers/email/fallbacks', { group: 'messaging' }),
331
+ q('fallback add', 'POST', '/v1/messaging_providers/email/fallbacks', { body: { transport: 'smtp', from_address: 'fallback@example.com' }, capture: { wfb: 'id' }, group: 'messaging' }),
332
+ q('fallback order', 'PUT', '/v1/messaging_providers/email/fallbacks/order', { body: { ids: ['{{wfb}}'] }, group: 'messaging' }),
333
+ q('fallback update', 'PUT', '/v1/messaging_providers/email/fallbacks/{{wfb}}', { body: { status: 'disabled' }, group: 'messaging' }),
334
+ q('fallback delete', 'DELETE', '/v1/messaging_providers/email/fallbacks/{{wfb}}', { group: 'messaging' }),
335
+ q('country routes', 'GET', '/v1/messaging/country_routes', { group: 'messaging' }),
336
+ q('country route set', 'PUT', '/v1/messaging/country_routes', { body: { country: 'DE', provider_id: 'primary' }, group: 'messaging' }),
337
+ q('country route delete', 'DELETE', '/v1/messaging/country_routes/DE', { group: 'messaging' }),
338
+ q('messaging settings', 'GET', '/v1/messaging/settings', { tolerate: ['updated_at'], group: 'messaging' }),
339
+ q('messaging settings set', 'PUT', '/v1/messaging/settings', { body: { auto_suppress: true }, tolerate: ['updated_at'], group: 'messaging' }),
340
+ q('message receivers', 'GET', '/v1/message_receivers', { tolerate: ['data'], group: 'messaging' }),
341
+ q('receiver secret', 'PUT', '/v1/message_receivers/resend/secret', { body: { secret: 'whsec_parity' }, tolerate: ['url', 'events', 'last_event_at', 'last_error', 'channel', 'verification', 'token_required', 'secret_help'], group: 'messaging' }),
342
+ q('receiver secret delete', 'DELETE', '/v1/message_receivers/resend/secret', { group: 'messaging' }),
343
+ // ── billing ──
344
+ q('subscription import', 'POST', '/v1/billing/subscriptions/import', { body: { subject_type: 'user', subject_id: '{{wu}}', plan_id: 'plan_parity', trial_days: 14 }, capture: { wsub: 'id' }, tolerate: ['trial_end'], group: 'billing' }),
345
+ q('subscriptions', 'GET', '/v1/billing/subscriptions', { tolerate: ['trial_end'], group: 'billing' }),
346
+ q('subscription get', 'GET', '/v1/billing/subscriptions/{{wsub}}', { tolerate: ['trial_end'], group: 'billing' }),
347
+ q('trial extend', 'POST', '/v1/billing/subscriptions/{{wsub}}/extend_trial', { body: { days: 7, idempotency_key: 'ext-{{rand}}' }, tolerate: ['trial_end'], group: 'billing' }),
348
+ q('invoices', 'GET', '/v1/billing/invoices?subject_id={{wu}}', { group: 'billing' }),
349
+ q('invoice unknown', 'GET', '/v1/billing/invoices/in_unknown', { group: 'billing' }),
350
+ q('refund unknown', 'POST', '/v1/billing/invoices/in_unknown/refund', { body: { idempotency_key: 'r-{{rand}}' }, group: 'billing' }),
351
+ q('usage report', 'POST', '/v1/billing/usage', { body: { subject_type: 'user', subject_id: '{{wu}}', feature: 'api_calls', quantity: 2, idempotency_key: 'u-{{rand}}' }, tolerate: ['subscription_id'], group: 'billing' }),
352
+ q('usage batch', 'POST', '/v1/billing/usage/batch', { body: { events: [{ subject_type: 'user', subject_id: '{{wu}}', feature: 'api_calls', quantity: 1, idempotency_key: 'b-{{rand}}' }] }, tolerate: ['results'], group: 'billing' }),
353
+ q('usage read-back', 'GET', '/v1/billing/usage?subject_type=user&subject_id={{wu}}&events=true', { tolerate: ['events', 'by_feature'], group: 'billing' }),
354
+ q('entitlement consume', 'POST', '/v1/entitlements/consume', { body: { subject_type: 'user', subject_id: '{{wu}}', feature: 'api_calls', key: 'c-{{rand}}' }, tolerate: ['limit', 'used', 'remaining', 'reason'], group: 'billing' }),
355
+ q('entitlement refund', 'POST', '/v1/entitlements/consume/refund', { body: { subject_type: 'user', subject_id: '{{wu}}', feature: 'api_calls', key: 'c-{{rand}}' }, group: 'billing' }),
356
+ q('coupon create', 'POST', '/v1/billing/coupons', { body: { code: 'C{{rand}}', percent_off: 10, duration: 'once' }, capture: { wcp: 'id' }, group: 'billing' }),
357
+ q('coupon update', 'PATCH', '/v1/billing/coupons/{{wcp}}', { body: { first_time_only: true }, group: 'billing' }),
358
+ // ── hooks: dead letters, action versions, workflow runs, app events ──
359
+ q('w hook', 'POST', '/v1/webhook_endpoints', { body: { url: 'https://example.com/hooks/w-{{rand}}', enabled_events: ['user.created'] }, capture: { whk: 'id' }, tolerate: ['secret'], group: 'workflows' }),
360
+ q('hook dlq', 'GET', '/v1/webhook_endpoints/{{whk}}/dlq', { group: 'workflows' }),
361
+ q('hook dlq ack', 'POST', '/v1/webhook_endpoints/{{whk}}/dlq/ack', { body: { delivery_ids: ['whdl_unknown'] }, group: 'workflows' }),
362
+ q('hook dlq requeue', 'POST', '/v1/webhook_endpoints/{{whk}}/dlq/requeue', { body: { delivery_ids: ['whdl_unknown'] }, group: 'workflows' }),
363
+ q('w hook delete', 'DELETE', '/v1/webhook_endpoints/{{whk}}', { group: 'workflows' }),
364
+ q('w action', 'POST', '/v1/actions', { body: { name: 'wave-{{rand}}', trigger: 'before_user_create', code: 'return {};' }, capture: { wact: 'id' }, group: 'workflows' }),
365
+ q('action version', 'POST', '/v1/actions/{{wact}}/versions', { body: { note: 'first' }, group: 'workflows' }),
366
+ q('action versions', 'GET', '/v1/actions/{{wact}}/versions', { group: 'workflows' }),
367
+ q('action version get', 'GET', '/v1/actions/{{wact}}/versions/1', { group: 'workflows' }),
368
+ q('action rollout abort', 'PATCH', '/v1/actions/{{wact}}/rollout', { body: { percent: 0 }, group: 'workflows' }),
369
+ q('action rollback', 'POST', '/v1/actions/{{wact}}/rollback', { body: {}, group: 'workflows' }),
370
+ q('action update', 'PATCH', '/v1/actions/{{wact}}', { body: { description: 'parity', egress_allowlist: ['api.example.com'] }, tolerate: ['live_version', 'staged'], group: 'workflows' }),
371
+ q('w workflow', 'POST', '/v1/workflows', { body: { name: 'wave-{{rand}}', trigger: { type: 'manual' }, steps: [{ id: 'a', type: 'set_variable', name: 'x', value: 1 }, { id: 'ok', type: 'approval' }] }, capture: { wwf: 'id' }, group: 'workflows' }),
372
+ q('workflow run', 'POST', '/v1/workflows/{{wwf}}/run', { body: {}, capture: { wrun: 'id' }, tolerate: ['steps', 'vars', 'input'], group: 'workflows' }),
373
+ q('workflow runs', 'GET', '/v1/workflows/{{wwf}}/runs', { tolerate: ['steps', 'vars', 'input'], group: 'workflows' }),
374
+ q('workflow run get', 'GET', '/v1/workflows/{{wwf}}/runs/{{wrun}}', { tolerate: ['steps', 'vars', 'input'], group: 'workflows' }),
375
+ q('workflow run cancel', 'POST', '/v1/workflows/{{wwf}}/runs/{{wrun}}/cancel', { tolerate: ['steps', 'vars', 'input'], group: 'workflows' }),
376
+ q('workflow run retry', 'POST', '/v1/workflows/{{wwf}}/runs/{{wrun}}/retry', { body: { from_step: 'a' }, tolerate: ['steps', 'vars', 'input'], group: 'workflows' }),
377
+ q('workflow run resume', 'POST', '/v1/workflows/{{wwf}}/runs/{{wrun}}/resume', { tolerate: ['steps', 'vars', 'input'], group: 'workflows' }),
378
+ q('app event', 'POST', '/v1/app_events', { body: { type: 'wave.thing', organization_id: '{{wo}}', data: { a: 1 }, idempotency_key: 'ev-{{rand}}' }, capture: { wev: 'id' }, tolerate: ['data'], group: 'workflows' }),
379
+ q('app event broadcast', 'POST', '/v1/app_events', { body: { type: 'wave.thing', organization_id: '*', data: { a: 1 } }, tolerate: ['events'], group: 'workflows' }),
380
+ q('app events', 'GET', '/v1/app_events', { tolerate: ['data'], group: 'workflows' }),
381
+ q('app event get', 'GET', '/v1/app_events/{{wev}}', { tolerate: ['data'], group: 'workflows' }),
382
+ // ── platform: write budget, deprecations, path mount, page templates, journeys ──
383
+ q('write budget', 'GET', '/v1/instance/write_budget', { tolerate: ['budgets', 'exceeded_in_window'], group: 'platform' }),
384
+ q('write budget set', 'PUT', '/v1/instance/write_budget', { body: { mode: 'observe' }, tolerate: ['budgets', 'exceeded_in_window'], group: 'platform' }),
385
+ q('write budget reset', 'DELETE', '/v1/instance/write_budget', { tolerate: ['budgets', 'exceeded_in_window'], group: 'platform' }),
386
+ q('deprecations', 'GET', '/v1/deprecations', { anonymous: true, tolerate: ['data'], group: 'platform' }),
387
+ q('instance deprecations', 'GET', '/v1/instance/deprecations', { tolerate: ['data', 'in_use'], group: 'platform' }),
388
+ q('path mount probe', 'GET', '/v1/path_mount/probe', { anonymous: true, group: 'platform' }),
389
+ q('page templates', 'GET', '/v1/instance/page_templates', { tolerate: ['data'], group: 'platform' }),
390
+ q('page template get', 'GET', '/v1/instance/page_templates/sign-in', { tolerate: ['draft', 'live', 'scope'], group: 'platform' }),
391
+ q('page template put', 'PUT', '/v1/instance/page_templates/sign-in?locale=fr', { body: { definition: { version: 1, regions: { header: [{ type: 'heading', text: 'Bonjour' }] } } }, tolerate: ['draft', 'live', 'scope'], group: 'platform' }),
392
+ q('page template preview', 'POST', '/v1/instance/page_templates/sign-in/preview?locale=fr', { tolerate: ['url', 'expires_at'], group: 'platform' }),
393
+ q('page template promote', 'POST', '/v1/instance/page_templates/sign-in/promote?locale=fr', { tolerate: ['draft', 'live', 'scope'], group: 'platform' }),
394
+ q('page template delete', 'DELETE', '/v1/instance/page_templates/sign-in?locale=fr&status=live', { group: 'platform' }),
395
+ q('w flow', 'POST', '/v1/flows', { body: { name: 'wave-{{rand}}', definition: { steps: [{ id: 'profile', type: 'form', on: ['sign_up'] }] } }, capture: { wfl: 'id' }, tolerate: ['definition'], group: 'platform' }),
396
+ q('flow runs', 'GET', '/v1/flows/{{wfl}}/runs', { group: 'platform' }),
397
+ q('user journeys', 'GET', '/v1/users/{{wu}}/journeys', { group: 'platform' }),
398
+ q('my journeys', 'GET', '/v1/client/me/journeys', { anonymous: true, headers: asUser, group: 'platform' }),
399
+ q('user journeys reset', 'DELETE', '/v1/users/{{wu}}/journeys', { group: 'platform' }),
400
+ // ── connections: activity, provider events, reconnect ──
401
+ q('w connection', 'POST', '/v1/connections', { body: { owner: { type: 'user', id: '{{wu}}' }, kind: 'oauth', provider: 'github', name: 'wave', allowed_hosts: ['api.github.com'] }, capture: { wc: 'id' }, group: 'connections' }),
402
+ q('connection activity', 'GET', '/v1/connections/{{wc}}/activity', { group: 'connections' }),
403
+ q('connection relay', 'POST', '/v1/connections/{{wc}}/relay', { body: { url: 'https://api.github.com/user' }, group: 'connections' }),
404
+ q('provider event', 'POST', '/v1/connection_provider_events/ins_emulator/github', { anonymous: true, body: { connection_id: '{{wc}}', topic: 'push', payload: { n: 1 } }, capture: { wpe: 'id' }, group: 'connections' }),
405
+ q('provider events', 'GET', '/v1/connections/{{wc}}/provider_events', { group: 'connections' }),
406
+ q('provider event get', 'GET', '/v1/connections/{{wc}}/provider_events/{{wpe}}', { tolerate: ['payload'], group: 'connections' }),
407
+ q('provider event replay', 'POST', '/v1/connections/{{wc}}/provider_events/{{wpe}}/replay', { group: 'connections' }),
408
+ q('provider event log', 'GET', '/v1/connection_provider_event_log', { group: 'connections' }),
409
+ q('reconnect settings', 'GET', '/v1/connection_reconnect_settings', { group: 'connections' }),
410
+ q('reconnect settings set', 'PUT', '/v1/connection_reconnect_settings', { body: { enabled: true, link_ttl_hours: 24 }, group: 'connections' }),
411
+ q('reconnect link', 'POST', '/v1/connections/{{wc}}/reconnect_link', { body: {}, tolerate: ['url', 'expires_at'], group: 'connections' }),
412
+ q('reconnect page refused', 'GET', '/v1/connection_reconnect/x', { anonymous: true, group: 'connections' }),
413
+ q('reconnect start refused', 'GET', '/v1/connection_reconnect/x/start', { anonymous: true, group: 'connections' }),
414
+ q('reconnect done', 'GET', '/v1/connection_reconnect_done', { anonymous: true, group: 'connections' }),
415
+ q('token exchange', 'POST', '/v1/connections/{{wc}}/token/exchange', { body: {}, group: 'connections' }),
416
+ ];
417
+ /** Every request: the first-round list, then the rest. */
418
+ exports.fullParityRequests = [...parity_1.defaultParityRequests, ...exports.extraParityRequests, ...exports.waveParityRequests, ...parity_emu3_authz_1.emu3AuthzParityRequests, ...parity_emu3_billing_1.emu3BillingParityRequests, ...parity_emu3_messaging_1.emu3MessagingParityRequests, ...parity_emu3_misc_1.emu3MiscParityRequests, ...parity_emu3_oauth_1.emu3OauthParityRequests, ...parity_emu4_authz_1.emu4AuthzParityRequests, ...parity_emu4_oauth_1.emu4OauthParityRequests, ...parity_emu4_actions_1.emu4ActionsParityRequests, ...parity_emu4_billing_1.emu4BillingParityRequests, ...parity_emu4_messaging_1.emu4MessagingParityRequests, ...parity_emu4_trust_1.emu4TrustParityRequests, ...parity_emu4_compliance_1.emu4ComplianceParityRequests, ...parity_emu4_federation_1.emu4FederationParityRequests];
419
+ /** Hand-written routes parity deliberately does not send to a real instance, each with the reason. */
420
+ exports.PARITY_EXEMPT = {
421
+ 'POST /v1/instance/signing_keys/rotate': 'rotating the signing key of the reference instance would invalidate its live tokens; the shape is covered by the key list',
422
+ };
423
+ /** Routes the emulator answers by hand that no parity request exercises, as `METHOD /pattern` (`/__emulator` routes are the engine's own surface and do not exist on a real instance). */
424
+ function uncoveredRoutes(engine, requests = exports.fullParityRequests) {
425
+ const concrete = requests.map((r) => ({ method: r.method, path: r.path.split('?')[0].replace(/\{\{\w+\}\}/g, 'x') }));
426
+ const out = [];
427
+ for (const route of engine.handwritten()) {
428
+ if (route.pattern.startsWith('/__emulator') || exports.PARITY_EXEMPT[`${route.method} ${route.pattern}`])
429
+ continue;
430
+ // The `/scim/v2/d/:directoryId` alias is the same handler set as `/scim/v2`.
431
+ if (route.pattern.startsWith('/scim/v2/d/'))
432
+ continue;
433
+ const re = new RegExp(`^${route.pattern.split('/').map((seg) => (seg.startsWith(':') ? '[^/]+' : seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))).join('/')}/?$`);
434
+ if (!concrete.some((c) => c.method === route.method && re.test(c.path)))
435
+ out.push(`${route.method} ${route.pattern}`);
436
+ }
437
+ return out;
438
+ }
439
+ //# sourceMappingURL=parity-routes.js.map