@classytic/arc 2.30.0 → 2.31.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 (239) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -0
  3. package/TRADEMARK.md +22 -0
  4. package/dist/{BaseController-B5DwufbB.mjs → BaseController-De7GqHck.mjs} +16 -17
  5. package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
  6. package/dist/{EventTransport-C6YA8xY9.mjs → EventTransport-9mlTgqzE.mjs} +1 -0
  7. package/dist/{HookSystem-WkIougo6.mjs → HookSystem-BcmPyq5I.mjs} +2 -1
  8. package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
  9. package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-qq0mn2s9.mjs} +3 -2
  10. package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-BE5NwaQk.mjs} +1 -0
  11. package/dist/audit/index.d.mts +3 -2
  12. package/dist/audit/index.mjs +2 -1
  13. package/dist/auth/audit.d.mts +1 -0
  14. package/dist/auth/audit.mjs +1 -0
  15. package/dist/auth/index.d.mts +5 -4
  16. package/dist/auth/index.mjs +6 -5
  17. package/dist/auth/redis-session.d.mts +2 -1
  18. package/dist/auth/redis-session.mjs +1 -0
  19. package/dist/{applyPermissionResult-Cf6zUCvb.mjs → authorizationDecision-ChvUQGG1.mjs} +19 -10
  20. package/dist/{buildHandler-BYS3Vkfz.mjs → buildHandler-CDrCJzmB.mjs} +4 -3
  21. package/dist/cache/index.d.mts +4 -3
  22. package/dist/cache/index.mjs +4 -3
  23. package/dist/{caching-_NJeDd1_.mjs → caching-BFHA6Scf.mjs} +2 -1
  24. package/dist/cleanup/index.d.mts +4 -3
  25. package/dist/cleanup/index.mjs +9 -13
  26. package/dist/cli/commands/describe.d.mts +2 -1
  27. package/dist/cli/commands/describe.mjs +3 -2
  28. package/dist/cli/commands/docs.d.mts +1 -0
  29. package/dist/cli/commands/docs.mjs +3 -2
  30. package/dist/cli/commands/doctor.d.mts +1 -0
  31. package/dist/cli/commands/doctor.mjs +1 -0
  32. package/dist/cli/commands/generate.d.mts +2 -1
  33. package/dist/cli/commands/generate.mjs +2 -1
  34. package/dist/cli/commands/init.d.mts +2 -1
  35. package/dist/cli/commands/init.mjs +2 -1
  36. package/dist/cli/commands/introspect.d.mts +1 -0
  37. package/dist/cli/commands/introspect.mjs +2 -1
  38. package/dist/cli/index.d.mts +3 -2
  39. package/dist/cli/index.mjs +3 -2
  40. package/dist/{constants-DNre0wRg.mjs → constants-D3Dv4LAV.mjs} +1 -0
  41. package/dist/context/index.d.mts +1 -0
  42. package/dist/context/index.mjs +3 -2
  43. package/dist/core/index.d.mts +3 -2
  44. package/dist/core/index.mjs +7 -6
  45. package/dist/{core-poalJLvp.mjs → core-BGpIxINN.mjs} +3 -2
  46. package/dist/{createActionRouter-BxuywVRB.mjs → createActionRouter-CDXEdasl.mjs} +8 -7
  47. package/dist/{createAggregationRouter-BiY-lGdY.mjs → createAggregationRouter-KrQDzx-k.mjs} +7 -6
  48. package/dist/{createApp-CXrKFciH.mjs → createApp-Df89BEni.mjs} +484 -209
  49. package/dist/{defineAggregation-ufWjUwml.mjs → defineAggregation-DbPPS8RI.mjs} +1 -0
  50. package/dist/{defineResource-CxWqDCAy.mjs → defineResource-Bv6EtN1x.mjs} +63 -38
  51. package/dist/discovery/index.d.mts +1 -0
  52. package/dist/discovery/index.mjs +1 -0
  53. package/dist/docs/index.d.mts +3 -2
  54. package/dist/docs/index.mjs +3 -2
  55. package/dist/{elevation-C9s5hFP_.d.mts → elevation-BXCfVcNG.d.mts} +1 -0
  56. package/dist/{elevation-CIz1GjGm.mjs → elevation-D2l1mh4g.mjs} +4 -3
  57. package/dist/encryption/index.d.mts +2 -1
  58. package/dist/encryption/index.mjs +3 -2
  59. package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-C5NeD6bT.mjs} +3 -2
  60. package/dist/{errorHandler-C6yv4ZTW.d.mts → errorHandler-CpYUJmPA.d.mts} +1 -0
  61. package/dist/{errors-DHcHVAgk.d.mts → errors-BGdVwP25.d.mts} +1 -0
  62. package/dist/{errors-DkO8Nk6-.mjs → errors-Cj42AntO.mjs} +1 -0
  63. package/dist/{eventPlugin-D2NNR4py.mjs → eventPlugin-CkffEihm.mjs} +5 -4
  64. package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
  65. package/dist/events/index.d.mts +4 -3
  66. package/dist/events/index.mjs +16 -4
  67. package/dist/events/transports/redis-stream-entry.d.mts +2 -1
  68. package/dist/events/transports/redis-stream-entry.mjs +1 -0
  69. package/dist/events/transports/redis.d.mts +2 -1
  70. package/dist/events/transports/redis.mjs +1 -0
  71. package/dist/{externalPaths-BE6wFpYv.d.mts → externalPaths-CmGOxOtD.d.mts} +1 -0
  72. package/dist/factory/index.d.mts +9 -5
  73. package/dist/factory/index.mjs +7 -6
  74. package/dist/{fastify-augmentation-0WhPnUH7.d.mts → fastify-augmentation-zh0_AYHa.d.mts} +13 -4
  75. package/dist/{fields-CQ_7KQWw.mjs → fields-CB5Cuzk9.mjs} +1 -0
  76. package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
  77. package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
  78. package/dist/{generate-D0wZupBg.mjs → generate-EcA6ui4i.mjs} +2 -1
  79. package/dist/{headers-BvlQSDMB.mjs → headers-3zvE0rM3.mjs} +2 -1
  80. package/dist/{health-CB_m4S32.mjs → health-DjZYjM7H.mjs} +1 -0
  81. package/dist/hooks/index.d.mts +2 -1
  82. package/dist/hooks/index.mjs +2 -1
  83. package/dist/idempotency/index.d.mts +3 -2
  84. package/dist/idempotency/index.mjs +4 -3
  85. package/dist/idempotency/redis.d.mts +2 -1
  86. package/dist/idempotency/redis.mjs +1 -0
  87. package/dist/{index-Cx7bro4R.d.mts → index-Bc2ox8_U.d.mts} +5 -4
  88. package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
  89. package/dist/{index-Ch16aAah.d.mts → index-CEJNZFtX.d.mts} +53 -15
  90. package/dist/index-CFThn1K9.d.mts +224 -0
  91. package/dist/{index-Q5gzEIWY.d.mts → index-CpzxqePp.d.mts} +107 -32
  92. package/dist/index.d.mts +9 -8
  93. package/dist/index.mjs +11 -10
  94. package/dist/{init-tlRSt667.mjs → init-BEiNZBsJ.mjs} +19 -23
  95. package/dist/{init-BPEu8cs-.d.mts → init-Cscs4lab.d.mts} +1 -0
  96. package/dist/integrations/event-gateway.d.mts +3 -2
  97. package/dist/integrations/event-gateway.mjs +2 -1
  98. package/dist/integrations/index.d.mts +4 -3
  99. package/dist/integrations/index.mjs +1 -0
  100. package/dist/integrations/jobs/index.d.mts +2 -1
  101. package/dist/integrations/jobs/index.mjs +108 -32
  102. package/dist/integrations/mcp/index.d.mts +6 -6
  103. package/dist/integrations/mcp/index.mjs +2 -1
  104. package/dist/integrations/mcp/testing.d.mts +3 -2
  105. package/dist/integrations/mcp/testing.mjs +2 -2
  106. package/dist/integrations/streamline.d.mts +1 -0
  107. package/dist/integrations/streamline.mjs +3 -2
  108. package/dist/integrations/webhooks.d.mts +1 -0
  109. package/dist/integrations/webhooks.mjs +1 -0
  110. package/dist/integrations/websocket-pushref-redis.d.mts +2 -1
  111. package/dist/integrations/websocket-pushref-redis.mjs +1 -0
  112. package/dist/integrations/websocket-redis.d.mts +2 -1
  113. package/dist/integrations/websocket-redis.mjs +1 -0
  114. package/dist/integrations/websocket.d.mts +3 -2
  115. package/dist/integrations/websocket.mjs +1 -0
  116. package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
  117. package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
  118. package/dist/{interface-n5hpKg1d.mjs → interface-DYzM0q-u.mjs} +1 -0
  119. package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-BYbLDi5D.mjs} +2 -1
  120. package/dist/{keys-Cd316u_y.mjs → keys-C0_pa1tY.mjs} +1 -0
  121. package/dist/{loadResources-DpU8pfjV.mjs → loadResources-yawi9tAE.mjs} +2 -1
  122. package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-BAjYZonx.mjs} +2 -1
  123. package/dist/logger/index.d.mts +1 -0
  124. package/dist/logger/index.mjs +1 -0
  125. package/dist/{mcpPlugin-nRzm5g2s.mjs → mcpPlugin-B_es0hiF.mjs} +22 -23
  126. package/dist/{memory-CmnzXE5Z.mjs → memory-DR6wpa9F.mjs} +2 -1
  127. package/dist/{metrics-fdxI5mn_.mjs → metrics-CUdw_wLB.mjs} +2 -1
  128. package/dist/middleware/index.d.mts +2 -1
  129. package/dist/middleware/index.mjs +3 -2
  130. package/dist/migrations/index.d.mts +1 -0
  131. package/dist/migrations/index.mjs +2 -1
  132. package/dist/module-0pFJA6YW.mjs +78 -0
  133. package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-wOgj_fnj.mjs} +1 -0
  134. package/dist/{openapi-B8Vkbxbn.mjs → openapi-B2L4js1v.mjs} +6 -5
  135. package/dist/permissions/index.d.mts +5 -4
  136. package/dist/permissions/index.mjs +6 -5
  137. package/dist/{permissions-Bfu_8y1U.mjs → permissions-L--QDAir.mjs} +214 -41
  138. package/dist/{pipe-BOLmGIxa.mjs → pipe-DRDNsI2T.mjs} +2 -1
  139. package/dist/pipeline/index.d.mts +2 -1
  140. package/dist/pipeline/index.mjs +2 -1
  141. package/dist/{pipeline-BJnpkY9i.mjs → pipeline-UPQFatit.mjs} +6 -5
  142. package/dist/plugins/index.d.mts +8 -7
  143. package/dist/plugins/index.mjs +16 -15
  144. package/dist/plugins/response-cache.d.mts +1 -0
  145. package/dist/plugins/response-cache.mjs +2 -1
  146. package/dist/plugins/tracing-entry.d.mts +2 -1
  147. package/dist/plugins/tracing-entry.mjs +2 -1
  148. package/dist/{pluralize-BhrgwZb9.mjs → pluralize-O0FZgESl.mjs} +1 -0
  149. package/dist/{preloadResources-DCfa2egz.d.mts → preloadResources-CyoJMSiO.d.mts} +117 -12
  150. package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-Dz6BjzM6.mjs} +1 -0
  151. package/dist/presets/filesUpload.d.mts +6 -5
  152. package/dist/presets/filesUpload.mjs +9 -11
  153. package/dist/presets/index.d.mts +3 -2
  154. package/dist/presets/index.mjs +2 -1
  155. package/dist/presets/multiTenant.d.mts +2 -1
  156. package/dist/presets/multiTenant.mjs +3 -2
  157. package/dist/presets/search.d.mts +3 -2
  158. package/dist/presets/search.mjs +2 -1
  159. package/dist/{presets-CJOEXLuk.mjs → presets-C4-g4_4P.mjs} +4 -3
  160. package/dist/{projection-UBDgfMwu.mjs → projection-BHw6xlOQ.mjs} +2 -1
  161. package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
  162. package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-BZr8v46E.mjs} +5 -4
  163. package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
  164. package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-CMPY5Qms.mjs} +7 -3
  165. package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-Dn5JNmkj.mjs} +1 -0
  166. package/dist/{realtime-B9nmIY5V.mjs → realtime-C5NoxWFY.mjs} +20 -9
  167. package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
  168. package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
  169. package/dist/{registerRoute-JkxIEBJq.mjs → registerRoute-CBN84fc7.mjs} +4 -3
  170. package/dist/registry/index.d.mts +3 -2
  171. package/dist/registry/index.mjs +3 -2
  172. package/dist/{registry-BYXUUg21.mjs → registry-4213U4zb.mjs} +2 -1
  173. package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-Dll8mGgc.mjs} +1 -0
  174. package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DIiZKGUt.mjs} +2 -1
  175. package/dist/{repositoryFilter-Dz8k3S4P.mjs → repositoryFilter-f9eAfq_l.mjs} +1 -0
  176. package/dist/{requestContext-MrlJwECf.mjs → requestContext-2TxXOJ7T.mjs} +1 -0
  177. package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-CUsjNX9R.mjs} +1 -0
  178. package/dist/{schedules-CjOd2MG9.mjs → schedules-DdtlICTf.mjs} +3 -2
  179. package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-D--wdJM-.mjs} +70 -17
  180. package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-Bm2enbnh.mjs} +2 -1
  181. package/dist/schemas/index.d.mts +1 -0
  182. package/dist/schemas/index.mjs +1 -0
  183. package/dist/scim/index.d.mts +1 -0
  184. package/dist/scim/index.mjs +2 -1
  185. package/dist/scope/index.d.mts +3 -2
  186. package/dist/scope/index.mjs +5 -4
  187. package/dist/{scope-DfaPntpo.mjs → scope-DHx5Fous.mjs} +4 -3
  188. package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
  189. package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-DjQJ7FKA.mjs} +1 -0
  190. package/dist/{sse-BQ1w0dfl.mjs → sse-CUTo9y0X.mjs} +5 -4
  191. package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B_5XcVVd.mjs} +2 -1
  192. package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
  193. package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-D1MP_gRG.mjs} +1 -0
  194. package/dist/{streaming-BQcW6_Kq.mjs → streaming-PSZwi8JV.mjs} +1 -0
  195. package/dist/{subscribe-helpers-DfzsivM6.mjs → subscribe-helpers-C0Tb2aUp.mjs} +2 -1
  196. package/dist/sync/changelog.d.mts +1 -0
  197. package/dist/sync/changelog.mjs +1 -0
  198. package/dist/testing/cleanupStoreContract.d.mts +2 -1
  199. package/dist/testing/cleanupStoreContract.mjs +1 -0
  200. package/dist/testing/index.d.mts +139 -12
  201. package/dist/testing/index.mjs +230 -16
  202. package/dist/testing/outboxStoreContract.d.mts +1 -0
  203. package/dist/testing/outboxStoreContract.mjs +1 -0
  204. package/dist/testing/storageContract.d.mts +2 -1
  205. package/dist/testing/storageContract.mjs +1 -0
  206. package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
  207. package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-dAfBVUrk.mjs} +1 -0
  208. package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B0ryPo3I.mjs} +1 -0
  209. package/dist/types/index.d.mts +6 -5
  210. package/dist/types/index.mjs +1 -0
  211. package/dist/types/storage.d.mts +2 -1
  212. package/dist/types/storage.mjs +1 -0
  213. package/dist/{types-ChvvVNP8.d.mts → types-B7XeLEgK.d.mts} +34 -2
  214. package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
  215. package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
  216. package/dist/{types-D_n8L6VY.d.mts → types-CBgvkp8O.d.mts} +51 -12
  217. package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
  218. package/dist/{types-Bmmn-fO0.d.mts → types-CwcUQ610.d.mts} +2 -6
  219. package/dist/{types-CSyCxl2V.mjs → types-Cys0ko9Z.mjs} +4 -3
  220. package/dist/usage/index.d.mts +3 -2
  221. package/dist/usage/index.mjs +0 -0
  222. package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-ChYJ6CdK.mjs} +1 -0
  223. package/dist/utils/index.d.mts +16 -7
  224. package/dist/utils/index.mjs +10 -9
  225. package/dist/{utils-taUVD4p3.mjs → utils-vL8BrDE2.mjs} +3 -2
  226. package/dist/{validate-Bk44F2yT.mjs → validate-_KbifpHF.mjs} +2 -1
  227. package/dist/{versioning-gKWyryT3.mjs → versioning-CHDTor3P.mjs} +3 -2
  228. package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
  229. package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
  230. package/package.json +547 -539
  231. package/skills/arc/SKILL.md +22 -8
  232. package/skills/arc/references/api-reference.md +4 -0
  233. package/skills/arc/references/auth.md +3 -0
  234. package/skills/arc/references/integrations.md +33 -0
  235. package/skills/arc/references/mcp.md +6 -7
  236. package/skills/arc/references/production.md +24 -25
  237. package/skills/arc-code-review/references/anti-patterns.md +3 -3
  238. package/dist/index-CCzVR-N3.d.mts +0 -107
  239. package/dist/module-_-M7C5ES.mjs +0 -29
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  import { arcLog } from "./logger/index.mjs";
2
3
  //#region src/utils/schemaConverter.ts
3
4
  /**
@@ -25,12 +26,53 @@ import { arcLog } from "./logger/index.mjs";
25
26
  * Using `draft-7` fixes `.positive() / .negative() / .gt() / .lt()` out of the box.
26
27
  * - **`openapi-3.0`** — for OpenAPI doc generation (arc emits OpenAPI 3.0.3). Keeps
27
28
  * the boolean exclusive form that 3.0 tooling expects.
29
+ *
30
+ * ## Input vs output (`io`)
31
+ *
32
+ * A Zod schema has TWO shapes and `z.toJSONSchema()` emits one of them. Getting
33
+ * this wrong is not cosmetic — it rejects legal traffic:
34
+ *
35
+ * ```ts
36
+ * z.object({ title: z.string(), status: z.string().default("draft") })
37
+ * // io: "output" → required: ["title", "status"] ← a client omitting `status` is REJECTED
38
+ * // io: "input" → required: ["title"] ← correct: the default fills it in
39
+ * ```
40
+ *
41
+ * The same applies to `.transform()`: in output mode the node is unrepresentable
42
+ * and degrades to `{}` (no validation at all), while input mode emits the real
43
+ * input type. So arc converts by DIRECTION:
44
+ *
45
+ * - **request** (`body`, `querystring`, `params`, `headers`) → `io: "input"` —
46
+ * validates what the client SENDS, before defaults/transforms/coercion apply.
47
+ * - **response** (and the `entity` doc shape) → `io: "output"` — describes what
48
+ * the server RETURNS, after they apply.
49
+ *
50
+ * One deliberate consequence: a plain `z.object()` emits
51
+ * `additionalProperties: false` in output mode but NOT in input mode, because
52
+ * `z.object()` STRIPS unknown keys rather than rejecting them — so extra keys
53
+ * are legal input. Use `z.strictObject()` when the wire contract must reject
54
+ * them; it keeps `additionalProperties: false` in both modes.
28
55
  */
29
56
  const log = arcLog("schema");
30
57
  /** Default target for Fastify-consumed schemas (matches Fastify v5's default AJV draft). */
31
58
  const DEFAULT_FASTIFY_TARGET = "draft-7";
32
59
  /** Default target for OpenAPI document generation (matches arc's emitted OpenAPI version). */
33
60
  const DEFAULT_OPENAPI_TARGET = "openapi-3.0";
61
+ /**
62
+ * Map arc's target onto one Zod actually branches on.
63
+ *
64
+ * Zod's `target` is typed `"draft-04" | "draft-07" | "draft-2020-12" |
65
+ * "openapi-3.0" | ({} & string)` — that last member accepts ANY string without
66
+ * narrowing, so an unrecognized value is not rejected, it just matches none of
67
+ * the internal `target === ...` branches and silently gets draft-2020-12
68
+ * behaviour. `"draft-7"` is safe (Zod normalizes the alias to `"draft-07"`),
69
+ * but `"openapi-3.1"` is neither official nor normalized. Mapping it explicitly
70
+ * keeps the fallback intentional rather than accidental — and it is the right
71
+ * dialect, since OpenAPI 3.1 aligned its Schema Object with JSON Schema 2020-12.
72
+ */
73
+ function zodTarget(target) {
74
+ return target === "openapi-3.1" ? "draft-2020-12" : target;
75
+ }
34
76
  let _toJSONSchema = null;
35
77
  const zodReady = import("zod").then(({ z }) => {
36
78
  if (typeof z?.toJSONSchema === "function") _toJSONSchema = (schema, opts) => z.toJSONSchema(schema, opts);
@@ -92,22 +134,33 @@ function unconvertible(mode, message, cause) {
92
134
  * Pass `openapi-3.0`/`openapi-3.1` for OpenAPI document generation.
93
135
  * @param mode Failure policy for unconvertible Zod schemas — see
94
136
  * {@link UnconvertibleMode}. Defaults to `'warn'`.
137
+ * @param io Which shape to emit — see {@link SchemaIo} and the module
138
+ * docblock. Defaults to `'output'`, matching Zod's own default;
139
+ * arc's request-side call sites pass `'input'` explicitly.
95
140
  */
96
- function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET, mode = "warn") {
141
+ function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET, mode = "warn", io = "output") {
97
142
  if (input == null) return void 0;
98
143
  if (typeof input !== "object") return void 0;
99
- if (isJsonSchema(input)) return input;
144
+ if (isJsonSchema(input)) {
145
+ const plain = input;
146
+ if ("$schema" in plain) {
147
+ const { $schema: _dropped, ...rest } = plain;
148
+ return rest;
149
+ }
150
+ return plain;
151
+ }
100
152
  if (isZodSchema(input)) {
101
153
  if (!_toJSONSchema) return unconvertible(mode, "A Zod schema was passed but zod v4 is not installed (or not yet loaded). Install zod >=4 — arc converts via zod's native z.toJSONSchema().");
102
154
  try {
103
155
  const converted = _toJSONSchema(input, {
104
- target,
156
+ target: zodTarget(target),
157
+ io,
105
158
  unrepresentable: mode === "throw" ? "throw" : "any"
106
159
  });
107
160
  if ("$schema" in converted) delete converted.$schema;
108
161
  return converted;
109
162
  } catch (cause) {
110
- return unconvertible(mode, `z.toJSONSchema() failed for a Zod schema (target: ${target}): ${cause instanceof Error ? cause.message : String(cause)}. Zod-only features with no JSON Schema equivalent (.refine/.transform/custom checks) cannot express wire validation — enforce those in the handler or a pipeline step.`, cause);
163
+ return unconvertible(mode, `z.toJSONSchema() failed for a Zod schema (target: ${target}, io: ${io}): ${cause instanceof Error ? cause.message : String(cause)}. Types with no JSON Schema equivalent cannot express wire validation. For \`z.date()\` / \`z.bigint()\` on a REQUEST schema, use the wire-shaped form (\`z.iso.datetime()\` / \`z.string()\`) and convert in a hook — JSON has no date or bigint. For \`.refine()\` / \`.transform()\` / custom checks, enforce them in the handler or a pipeline step.`, cause);
111
164
  }
112
165
  }
113
166
  if (isZodV3Schema(input)) return unconvertible(mode, "A Zod v3 schema was passed (has `_def` but no v4 `_zod` marker). arc requires zod >=4 for schema conversion — upgrade zod, or pass plain JSON Schema.");
@@ -122,19 +175,19 @@ function toJsonSchema(input, target = DEFAULT_FASTIFY_TARGET, mode = "warn") {
122
175
  */
123
176
  function convertOpenApiSchemas(schemas, target = DEFAULT_OPENAPI_TARGET) {
124
177
  const result = {};
125
- const schemaFields = [
126
- "entity",
127
- "createBody",
128
- "updateBody",
129
- "params",
130
- "listQuery",
131
- "response"
132
- ];
133
- for (const field of schemaFields) {
178
+ const schemaFields = {
179
+ entity: "output",
180
+ createBody: "input",
181
+ updateBody: "input",
182
+ params: "input",
183
+ listQuery: "input",
184
+ response: "output"
185
+ };
186
+ for (const [field, io] of Object.entries(schemaFields)) {
134
187
  const value = schemas[field];
135
- if (value !== void 0) result[field] = toJsonSchema(value, target) ?? value;
188
+ if (value !== void 0) result[field] = toJsonSchema(value, target, "warn", io) ?? value;
136
189
  }
137
- for (const [key, value] of Object.entries(schemas)) if (!schemaFields.includes(key)) result[key] = value;
190
+ for (const [key, value] of Object.entries(schemas)) if (!(key in schemaFields)) result[key] = value;
138
191
  return result;
139
192
  }
140
193
  /**
@@ -158,11 +211,11 @@ function convertRouteSchema(schema, target = DEFAULT_FASTIFY_TARGET) {
158
211
  "querystring",
159
212
  "params",
160
213
  "headers"
161
- ]) if (result[field] !== void 0) result[field] = toJsonSchema(result[field], target, mode) ?? result[field];
214
+ ]) if (result[field] !== void 0) result[field] = toJsonSchema(result[field], target, mode, "input") ?? result[field];
162
215
  if (result.response !== void 0 && typeof result.response === "object" && result.response !== null) {
163
216
  const responseObj = result.response;
164
217
  const convertedResponse = {};
165
- for (const [statusCode, responseSchema] of Object.entries(responseObj)) convertedResponse[statusCode] = toJsonSchema(responseSchema, target, mode) ?? responseSchema;
218
+ for (const [statusCode, responseSchema] of Object.entries(responseObj)) convertedResponse[statusCode] = toJsonSchema(responseSchema, target, mode, "output") ?? responseSchema;
166
219
  result.response = convertedResponse;
167
220
  }
168
221
  return result;
@@ -1,4 +1,5 @@
1
- import { o as toJsonSchema } from "./schemaConverter-DJvodiHj.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { o as toJsonSchema } from "./schemaConverter-D--wdJM-.mjs";
2
3
  //#region src/core/schemaIR.ts
3
4
  /**
4
5
  * Schema IR — one canonical representation, two adapters.
@@ -1,3 +1,4 @@
1
+
1
2
  import { ErrorContract, ErrorDetail } from "@classytic/repo-core/errors";
2
3
  import { Static, TObject, TSchema, TSchema as TSchema$1, Type, Type as Type$1 } from "typebox";
3
4
  import { FastifyPluginAsyncTypebox, FastifyPluginCallbackTypebox, TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
2
3
  import { Type, Type as Type$1 } from "typebox";
3
4
  //#region src/schemas/index.ts
@@ -1,3 +1,4 @@
1
+
1
2
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
2
3
  import { RepositoryLike } from "@classytic/repo-core/adapter";
3
4
  //#region src/scim/mapping.d.ts
@@ -1,4 +1,5 @@
1
- import { t as parseJsonBody } from "../jsonBody-BlzAoGCe.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { t as parseJsonBody } from "../jsonBody-BYbLDi5D.mjs";
2
3
  import fp from "fastify-plugin";
3
4
  //#region src/scim/errors.ts
4
5
  var ScimError = class extends Error {
@@ -1,5 +1,6 @@
1
- import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-DjC-KUCz.mjs";
2
- import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-C9s5hFP_.mjs";
1
+
2
+ import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-ChuShpCP.mjs";
3
+ import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-BXCfVcNG.mjs";
3
4
  import { FastifyReply, FastifyRequest } from "fastify";
4
5
  //#region src/scope/rateLimitKey.d.ts
5
6
  interface RateLimitKeyContext {
@@ -1,5 +1,6 @@
1
- import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-CSyCxl2V.mjs";
2
- import { n as elevation_default, t as elevationPlugin } from "../elevation-CIz1GjGm.mjs";
3
- import { t as createTenantKeyGenerator } from "../rateLimitKey-BoWRH3e7.mjs";
4
- import { t as resolveOrgFromHeader } from "../scope-DfaPntpo.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-Cys0ko9Z.mjs";
3
+ import { n as elevation_default, t as elevationPlugin } from "../elevation-D2l1mh4g.mjs";
4
+ import { t as createTenantKeyGenerator } from "../rateLimitKey-Dn5JNmkj.mjs";
5
+ import { t as resolveOrgFromHeader } from "../scope-DHx5Fous.mjs";
5
6
  export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin, elevationPlugin as elevationPluginFn, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
@@ -1,6 +1,7 @@
1
- import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-DkO8Nk6-.mjs";
2
- import { n as requireSingleHeaderValue } from "./headers-BvlQSDMB.mjs";
3
- import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-Cj42AntO.mjs";
3
+ import { n as requireSingleHeaderValue } from "./headers-3zvE0rM3.mjs";
4
+ import { n as getUserRoles } from "./userHelpers-ChYJ6CdK.mjs";
4
5
  //#region src/scope/resolveOrgFromHeader.ts
5
6
  /**
6
7
  * Create a preHandler hook that resolves org scope from a header.
@@ -1,3 +1,4 @@
1
+
1
2
  import { FastifyPluginAsync, FastifyReply, FastifyRequest } from "fastify";
2
3
  //#region src/auth/sessionManager.d.ts
3
4
  /**
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  //#region src/utils/simpleEqualityMatcher.ts
2
3
  /**
3
4
  * `simpleEqualityMatcher` — a minimal, dialect-agnostic flat-key equality
@@ -1,8 +1,9 @@
1
- import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.mjs";
2
3
  import { arcLog } from "./logger/index.mjs";
3
- import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-CSyCxl2V.mjs";
4
- import { a as promoteStreamTokenToHeader } from "./streaming-BQcW6_Kq.mjs";
5
- import { t as openSseStream } from "./sseStream-BQ7Qaynp.mjs";
4
+ import { c as getOrgId, n as PUBLIC_SCOPE } from "./types-Cys0ko9Z.mjs";
5
+ import { a as promoteStreamTokenToHeader } from "./streaming-PSZwi8JV.mjs";
6
+ import { t as openSseStream } from "./sseStream-B_5XcVVd.mjs";
6
7
  import fp from "fastify-plugin";
7
8
  //#region src/plugins/sse.ts
8
9
  var sse_exports = /* @__PURE__ */ __exportAll({
@@ -1,4 +1,5 @@
1
- import { n as forwardedStreamHeaders } from "./streaming-BQcW6_Kq.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { n as forwardedStreamHeaders } from "./streaming-PSZwi8JV.mjs";
2
3
  //#region src/utils/sseStream.ts
3
4
  /**
4
5
  * Take over the reply socket and open an SSE stream. Call ONLY after every
@@ -1,3 +1,4 @@
1
+
1
2
  //#region src/types/storage.d.ts
2
3
  /**
3
4
  * Arc's minimal backend-agnostic storage contract.
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  //#region src/utils/store-helpers.ts
2
3
  /**
3
4
  * Classify an error thrown by `getOne` / `getById` / `update` as a
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  import { Readable } from "node:stream";
2
3
  //#region src/utils/streaming.ts
3
4
  /**
@@ -1,4 +1,5 @@
1
- import { n as createEvent } from "./EventTransport-C6YA8xY9.mjs";
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
+ import { n as createEvent } from "./EventTransport-9mlTgqzE.mjs";
2
3
  //#region src/events/defineEvent.ts
3
4
  /**
4
5
  * defineEvent — Typed Event Definitions with Optional Schema Validation
@@ -1,2 +1,3 @@
1
+
1
2
  import { ChangeEntry, ChangeLogAppendOptions, ChangeLogStore, ChangeOp, ChangesPage, ChangesSinceOptions, CursorExpiredError, MemoryChangeLogStore, PushMutation, PushVerdict, PushVerdictStatus } from "@classytic/repo-core/sync";
2
3
  export { type ChangeEntry, type ChangeLogAppendOptions, type ChangeLogStore, type ChangeOp, type ChangesPage, type ChangesSinceOptions, CursorExpiredError, MemoryChangeLogStore, type PushMutation, type PushVerdict, type PushVerdictStatus };
@@ -1,2 +1,3 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  import { CursorExpiredError, MemoryChangeLogStore } from "@classytic/repo-core/sync";
2
3
  export { CursorExpiredError, MemoryChangeLogStore };
@@ -1,4 +1,5 @@
1
- import { D as CleanupRunStore } from "../types-C_ZZRcD6.mjs";
1
+
2
+ import { D as CleanupRunStore } from "../types-C4QE8qnm.mjs";
2
3
  //#region src/testing/cleanupStoreContract.d.ts
3
4
  interface CleanupRunStoreContractSetupResult {
4
5
  store: CleanupRunStore;
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
2
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
3
  //#region src/testing/cleanupStoreContract.ts
3
4
  /**
@@ -1,8 +1,10 @@
1
- import { V as ResourceDefinition } from "../index-Q5gzEIWY.mjs";
2
- import { l as AnyRecord, r as PermissionCheck } from "../types-ChvvVNP8.mjs";
3
- import { n as ArcModuleInput } from "../types-D_n8L6VY.mjs";
4
- import "../index-CCzVR-N3.mjs";
5
- import { n as preloadResourcesAsync, r as CreateAppOptions, t as preloadResources } from "../preloadResources-DCfa2egz.mjs";
1
+
2
+ import { V as ResourceDefinition } from "../index-CpzxqePp.mjs";
3
+ import { i as RequestScope } from "../types-ChuShpCP.mjs";
4
+ import { l as AnyRecord, r as PermissionCheck } from "../types-B7XeLEgK.mjs";
5
+ import { n as ArcModuleInput } from "../types-CBgvkp8O.mjs";
6
+ import "../index-CFThn1K9.mjs";
7
+ import { n as preloadResourcesAsync, r as CreateAppOptions, t as preloadResources } from "../preloadResources-CyoJMSiO.mjs";
6
8
  import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
7
9
  import { FastifyInstance, FastifyServerOptions } from "fastify";
8
10
  import { Mock } from "vitest";
@@ -84,6 +86,13 @@ interface SurfaceObservation {
84
86
  interface AuthorizationConformance {
85
87
  /** `GET /confs` — the CRUD list surface. */
86
88
  list: SurfaceObservation;
89
+ /**
90
+ * `GET /confs/:id` — the SINGLE-RECORD surface. Its `filter` is the compound
91
+ * `buildIdFilter` output (route id AND policy AND tenant), so a policy that
92
+ * tried to redirect/widen the target would show here — the surface the
93
+ * filter-overwrite class of bug lives on.
94
+ */
95
+ get: SurfaceObservation;
87
96
  /** `GET /confs/aggregations/tally` — the aggregation surface. */
88
97
  aggregation: SurfaceObservation;
89
98
  /** Close the throwaway app. Always call (e.g. in `finally`). */
@@ -327,6 +336,81 @@ declare function createBetterAuthTestHelpers(options?: BetterAuthTestHelpersOpti
327
336
  */
328
337
  declare function setupBetterAuthTestApp(input: SetupBetterAuthTestAppInput): Promise<SetupBetterAuthTestAppResult>;
329
338
  //#endregion
339
+ //#region src/testing/testActor.d.ts
340
+ /** The header the testing harness uses to carry a request's actor. */
341
+ declare const TEST_ACTOR_HEADER = "x-test-actor";
342
+ /**
343
+ * What a test wants to be. An `organizationId` produces a `member` scope (the
344
+ * org-scoped case); without one the actor is `authenticated`.
345
+ */
346
+ interface TestActor {
347
+ /**
348
+ * Defaults to a synthetic id derived from the roles. Pass one explicitly when
349
+ * a test distinguishes two actors — ownership and audit assertions do, and
350
+ * two actors sharing roles would otherwise share an id.
351
+ */
352
+ userId?: string;
353
+ /** Global roles — what `requireRoles` reads. */
354
+ roles?: string[];
355
+ /** Tenant. Present ⇒ a `member` scope. */
356
+ organizationId?: string;
357
+ /** Org-level roles from membership records. Defaults to `roles`. */
358
+ orgRoles?: string[];
359
+ teamId?: string;
360
+ /** App-defined dimensions (branch, project, region) read via `getScopeContext`. */
361
+ context?: Record<string, string>;
362
+ /**
363
+ * Elevated (platform-admin) scope. The value is the `elevatedBy` audit string
364
+ * arc requires — an elevated scope must record who granted it. `true` is
365
+ * shorthand for `'test'`.
366
+ *
367
+ * An elevated scope carries no roles: elevation is a different kind of actor,
368
+ * not a role. A test needing a role gate AND elevation wants `member`.
369
+ */
370
+ elevated?: string | boolean;
371
+ }
372
+ /**
373
+ * Build the headers that make an injected request act as `actor`.
374
+ *
375
+ * A string is shorthand for a single role. `null` means "stay public", so a test
376
+ * can prove an endpoint is reachable anonymously.
377
+ *
378
+ * @example
379
+ * ```ts
380
+ * t.app.inject({ method: 'GET', url: '/pos/shifts/current',
381
+ * headers: testActorHeaders('manager', ORG) });
382
+ * ```
383
+ */
384
+ declare function testActorHeaders(actor: TestActor | string | null, organizationId?: string): Record<string, string>;
385
+ /**
386
+ * Turn a spec into a `RequestScope`.
387
+ *
388
+ * `orgRoles` defaults to `roles`: a test that says "manager" means it at both
389
+ * levels, and making the caller repeat itself only produces tests that pass a
390
+ * global gate and mysteriously fail an org one.
391
+ */
392
+ declare function scopeFromTestActor(spec: TestActor): RequestScope;
393
+ /**
394
+ * Build the `auth` option that makes the test actor arc's authenticator.
395
+ *
396
+ * It must be an authenticator, not a hook. Fastify encapsulates plugins and
397
+ * modules register DURING `createApp`, so a hook added to the root instance
398
+ * afterwards never runs for their routes — every request still resolves as
399
+ * public and every role gate still answers 401, which reads exactly like a
400
+ * broken authorization rule in the code under test. Registering as
401
+ * `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
402
+ * at the phase arc expects, so tenant filtering and permission checks behave
403
+ * identically to production.
404
+ *
405
+ * `defaultActor` applies to requests carrying no header. A malformed header
406
+ * REJECTS rather than falling back to public — a test that meant to be an admin
407
+ * and got quietly demoted is the most expensive way to fail.
408
+ */
409
+ declare function testActorAuth(defaultActor?: TestActor): {
410
+ type: "authenticator";
411
+ authenticate: (request: unknown) => Promise<void>;
412
+ };
413
+ //#endregion
330
414
  //#region src/testing/bootModuleApp.d.ts
331
415
  /** Handed to a modules factory — the in-memory DB is live at call time. */
332
416
  /**
@@ -350,8 +434,6 @@ type TestDatabaseFactory<TConn = unknown> = (opts: {
350
434
  interface TestkitContext<TConn = unknown> {
351
435
  /** The provisioned database's connection string. */
352
436
  uri: string;
353
- /** @deprecated Alias of `uri` from the Mongo-default era — prefer `uri`. */
354
- mongoUri: string;
355
437
  /**
356
438
  * Driver connection handle (the default factory yields mongoose's
357
439
  * DEFAULT connection). Generic so DB-typed wrappers assert the driver
@@ -377,13 +459,20 @@ interface BootModuleAppOptions<TConn = unknown> {
377
459
  * boot when a module genuinely exercises one).
378
460
  */
379
461
  app?: Partial<CreateAppOptions>;
462
+ /**
463
+ * Default actor for every injected request that carries no `x-test-actor` header.
464
+ *
465
+ * The harness boots with `auth: false`, so without this every request is
466
+ * `PUBLIC_SCOPE` and role-gated routes answer 401 — correct, but it left tests with
467
+ * no supported way to be somebody. See `testActor.ts` for why the seam lives in the
468
+ * testing entrypoint and can never be reached from production.
469
+ */
470
+ actor?: TestActor;
380
471
  }
381
472
  interface ModuleTestApp<TConn = unknown> {
382
473
  app: FastifyInstance;
383
474
  /** The provisioned database's connection string. */
384
475
  uri: string;
385
- /** @deprecated Alias of `uri` from the Mongo-default era — prefer `uri`. */
386
- mongoUri: string;
387
476
  /** The mongoose `Connection` handed to the modules factory. */
388
477
  connection: TConn;
389
478
  /**
@@ -555,13 +644,26 @@ declare function createSpy<T extends (...args: unknown[]) => unknown>(_name?: st
555
644
  getLastCall(): unknown[];
556
645
  };
557
646
  /**
558
- * Wait for a condition to be true
647
+ * Wait until `condition` holds — the standard way to assert on asynchronous
648
+ * effects in arc's suites.
559
649
  *
560
- * Useful for async testing
650
+ * **Prefer this over `await sleep(n)` + assert.** A fixed delay encodes a guess
651
+ * about scheduling: too short and it flakes the moment the pool is busy, too
652
+ * long and every run pays for the worst case. Polling returns as soon as the
653
+ * effect lands, so the fast path stays fast and a loaded machine simply waits —
654
+ * and the failure mode becomes "this never happened", which is the thing worth
655
+ * asserting.
656
+ *
657
+ * Supply `label`: without it a timeout reports only its own duration, which is
658
+ * the least useful sentence a flaky test can print.
659
+ *
660
+ * @example
661
+ * await waitFor(() => runs >= 3, { label: "3 scheduler ticks" });
561
662
  */
562
663
  declare function waitFor(condition: () => boolean | Promise<boolean>, options?: {
563
664
  timeout?: number;
564
665
  interval?: number;
666
+ label?: string;
565
667
  }): Promise<void>;
566
668
  /**
567
669
  * Create a test timer that can be controlled
@@ -573,6 +675,31 @@ declare function createTestTimer(): {
573
675
  reset: () => void;
574
676
  };
575
677
  //#endregion
678
+ //#region src/testing/scopeRoleGate.d.ts
679
+ /**
680
+ * Grants iff the caller is an org member holding one of `allowed` as an ORG role.
681
+ *
682
+ * This is `requireOrgRole` under a name package tests reach for — deliberately a
683
+ * delegation, not a reimplementation. It previously read `scope.userRoles` while
684
+ * requiring an organization, so it disagreed with production for any actor whose
685
+ * global and org roles differ: given
686
+ * `{ roles: ["employee"], orgRoles: ["manager"], organizationId: "org-1" }`,
687
+ * `requireOrgRole("manager")` allowed and this denied. The tests never caught it
688
+ * because `scopeFromTestActor` defaults `orgRoles` to `roles`, making the two
689
+ * dimensions identical for the common actor.
690
+ *
691
+ * Delegating also inherits the semantics a hand-rolled gate kept missing:
692
+ * elevated scopes pass, and a service (API-key) identity is refused with a
693
+ * message naming `requireServiceScope` instead of silently failing a role test.
694
+ *
695
+ * **Pair it with an org-bearing actor.** `testActorHeaders('manager', ORG)`
696
+ * yields a `member` scope and passes; `testActorHeaders('manager')` yields an
697
+ * `authenticated` scope with no tenant, which is refused — arc's generated CRUD
698
+ * filters by tenant, so a gate that waves a tenant-less request through is how a
699
+ * green test reads another org's rows.
700
+ */
701
+ declare function scopeRoleGate(...allowed: string[]): PermissionCheck;
702
+ //#endregion
576
703
  //#region src/testing/testApp.d.ts
577
704
  type DbMode = "in-memory" | {
578
705
  uri: string;
@@ -650,4 +777,4 @@ declare function createTestApp(options?: CreateTestAppOptions): Promise<TestAppC
650
777
  */
651
778
  declare function createMinimalTestApp(options?: FastifyServerOptions): FastifyInstance;
652
779
  //#endregion
653
- export { type ArcAssertion, type ArcResponseLike, type AuthMode, type AuthResponse, type AuthorizationConformance, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type BetterAuthTestUser, type BootModuleAppOptions, type BootModulesInput, type CreateOrgInput, type CreateTestAppOptions, type DbMode, type FixtureDestroyer, type FixtureFactory, type FixtureRegistration, HttpTestHarness, type HttpTestHarnessOptions, type MockRepository, type ModuleTestApp, type OrgResponse, type RoleConfig, type SetupBetterAuthTestAppInput, type SetupBetterAuthTestAppResult, type SignInInput, type SignUpInput, type StorageContractSetup, type StorageContractSetupResult, type SurfaceObservation, type TestAppContext, type TestAuthProvider, type TestAuthSession, type TestDatabase, type TestDatabaseFactory, type TestFixtures, type TestkitContext, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
780
+ export { type ArcAssertion, type ArcResponseLike, type AuthMode, type AuthResponse, type AuthorizationConformance, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type BetterAuthTestUser, type BootModuleAppOptions, type BootModulesInput, type CreateOrgInput, type CreateTestAppOptions, type DbMode, type FixtureDestroyer, type FixtureFactory, type FixtureRegistration, HttpTestHarness, type HttpTestHarnessOptions, type MockRepository, type ModuleTestApp, type OrgResponse, type RoleConfig, type SetupBetterAuthTestAppInput, type SetupBetterAuthTestAppResult, type SignInInput, type SignUpInput, type StorageContractSetup, type StorageContractSetupResult, type SurfaceObservation, TEST_ACTOR_HEADER, type TestActor, type TestAppContext, type TestAuthProvider, type TestAuthSession, type TestDatabase, type TestDatabaseFactory, type TestFixtures, type TestkitContext, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, scopeFromTestActor, scopeRoleGate, setupBetterAuthTestApp, testActorAuth, testActorHeaders, waitFor };