@classytic/arc 2.41.1 → 2.44.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 (248) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-DSqD_Xsg.mjs → EventTransport-DZWDsaPa.mjs} +22 -17
  3. package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
  6. package/dist/acquire-BozJ-9yt.mjs +35 -0
  7. package/dist/actionPermissions-5N04zBL4.mjs +772 -0
  8. package/dist/audit/index.d.mts +46 -8
  9. package/dist/audit/index.mjs +48 -26
  10. package/dist/auth/audit.d.mts +8 -8
  11. package/dist/auth/audit.mjs +1 -1
  12. package/dist/auth/index.d.mts +46 -16
  13. package/dist/auth/index.mjs +134 -156
  14. package/dist/auth/redis-session.d.mts +4 -5
  15. package/dist/auth/redis-session.mjs +1 -1
  16. package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
  17. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  18. package/dist/boundedMap-DWiKW7lP.mjs +57 -0
  19. package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
  20. package/dist/cache/index.d.mts +31 -43
  21. package/dist/cache/index.mjs +4 -4
  22. package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
  23. package/dist/claimed-DYvQoZLv.mjs +8 -0
  24. package/dist/cleanup/index.d.mts +18 -22
  25. package/dist/cleanup/index.mjs +27 -73
  26. package/dist/cli/commands/describe.d.mts +7 -5
  27. package/dist/cli/commands/describe.mjs +7 -4
  28. package/dist/cli/commands/docs.d.mts +2 -3
  29. package/dist/cli/commands/docs.mjs +3 -3
  30. package/dist/cli/commands/doctor.d.mts +2 -3
  31. package/dist/cli/commands/doctor.mjs +1 -1
  32. package/dist/cli/commands/generate.mjs +2 -3
  33. package/dist/cli/commands/init.mjs +2 -3
  34. package/dist/cli/commands/introspect.d.mts +2 -3
  35. package/dist/cli/commands/introspect.mjs +2 -2
  36. package/dist/cli/index.mjs +3 -3
  37. package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
  38. package/dist/context/index.d.mts +11 -14
  39. package/dist/context/index.mjs +18 -38
  40. package/dist/core/index.d.mts +3 -3
  41. package/dist/core/index.mjs +8 -8
  42. package/dist/{core-5lazhva9.mjs → core-COcqo0Cp.mjs} +15 -5
  43. package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
  44. package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
  45. package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
  46. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
  47. package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
  48. package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
  49. package/dist/discovery/index.d.mts +6 -7
  50. package/dist/discovery/index.mjs +1 -1
  51. package/dist/docs/index.d.mts +11 -5
  52. package/dist/docs/index.mjs +4 -4
  53. package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
  54. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  55. package/dist/encryption/index.d.mts +5 -5
  56. package/dist/encryption/index.mjs +3 -3
  57. package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
  58. package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.mjs} +58 -23
  59. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  60. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  61. package/dist/{eventPlugin-zZY-unhr.mjs → eventPlugin-BweHAVws.mjs} +82 -21
  62. package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
  63. package/dist/events/index.d.mts +43 -18
  64. package/dist/events/index.mjs +49 -53
  65. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  66. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  67. package/dist/events/transports/redis.d.mts +5 -6
  68. package/dist/events/transports/redis.mjs +2 -2
  69. package/dist/factory/index.d.mts +42 -27
  70. package/dist/factory/index.mjs +213 -32
  71. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
  72. package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
  73. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  74. package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
  75. package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
  76. package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
  77. package/dist/health-BYAwA6YS.mjs +22 -0
  78. package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
  79. package/dist/hooks/index.d.mts +2 -2
  80. package/dist/hooks/index.mjs +2 -3
  81. package/dist/idempotency/index.d.mts +36 -10
  82. package/dist/idempotency/index.mjs +230 -111
  83. package/dist/idempotency/redis.d.mts +1 -1
  84. package/dist/idempotency/redis.mjs +77 -12
  85. package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
  86. package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
  87. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  88. package/dist/index-CUJvoyw3.d.mts +510 -0
  89. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  90. package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
  91. package/dist/index.d.mts +9 -9
  92. package/dist/index.mjs +11 -11
  93. package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
  94. package/dist/integrations/event-gateway.d.mts +11 -7
  95. package/dist/integrations/event-gateway.mjs +4 -4
  96. package/dist/integrations/index.d.mts +3 -3
  97. package/dist/integrations/index.mjs +1 -1
  98. package/dist/integrations/jobs/index.d.mts +1 -1
  99. package/dist/integrations/jobs/index.mjs +18 -5
  100. package/dist/integrations/mcp/index.d.mts +102 -89
  101. package/dist/integrations/mcp/index.mjs +19 -4
  102. package/dist/integrations/mcp/testing.d.mts +8 -9
  103. package/dist/integrations/mcp/testing.mjs +2 -2
  104. package/dist/integrations/streamline.d.mts +85 -144
  105. package/dist/integrations/streamline.mjs +131 -55
  106. package/dist/integrations/webhooks.d.mts +83 -143
  107. package/dist/integrations/webhooks.mjs +36 -80
  108. package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
  109. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  110. package/dist/integrations/websocket-redis.d.mts +5 -6
  111. package/dist/integrations/websocket-redis.mjs +1 -1
  112. package/dist/integrations/websocket.d.mts +3 -3
  113. package/dist/integrations/websocket.mjs +2 -2
  114. package/dist/{interface-DEWNOMza.d.mts → interface-JJzwH4Ro.d.mts} +34 -2
  115. package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
  116. package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
  117. package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
  118. package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
  119. package/dist/logger/index.d.mts +9 -17
  120. package/dist/logger/index.mjs +2 -153
  121. package/dist/logger-D4V1zGLI.mjs +160 -0
  122. package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
  123. package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
  124. package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
  125. package/dist/middleware/index.d.mts +5 -5
  126. package/dist/middleware/index.mjs +3 -3
  127. package/dist/migrations/index.d.mts +13 -13
  128. package/dist/migrations/index.mjs +2 -2
  129. package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
  130. package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
  131. package/dist/mongoose-CYJCLyi-.mjs +67451 -0
  132. package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
  133. package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
  134. package/dist/node-D8UZv_8c.mjs +41236 -0
  135. package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
  136. package/dist/outbox-admin/index.d.mts +5 -5
  137. package/dist/outbox-admin/index.mjs +6 -18
  138. package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.mjs} +203 -33
  139. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  140. package/dist/permissions/index.d.mts +5 -4
  141. package/dist/permissions/index.mjs +8 -6
  142. package/dist/{permissions-D8TBH44E.mjs → permissions-C5ijIz5Q.mjs} +149 -33
  143. package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
  144. package/dist/pipeline/index.d.mts +7 -7
  145. package/dist/pipeline/index.mjs +2 -2
  146. package/dist/plan-CJGMKoui.mjs +565 -0
  147. package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
  148. package/dist/plugins/index.d.mts +17 -14
  149. package/dist/plugins/index.mjs +44 -22
  150. package/dist/plugins/response-cache.d.mts +5 -6
  151. package/dist/plugins/response-cache.mjs +8 -14
  152. package/dist/plugins/tracing-entry.mjs +2 -2
  153. package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
  154. package/dist/presets/filesUpload.d.mts +8 -8
  155. package/dist/presets/filesUpload.mjs +5 -5
  156. package/dist/presets/index.d.mts +35 -40
  157. package/dist/presets/index.mjs +4 -4
  158. package/dist/presets/multiTenant.d.mts +2 -107
  159. package/dist/presets/multiTenant.mjs +22 -162
  160. package/dist/presets/search.d.mts +9 -10
  161. package/dist/presets/search.mjs +2 -2
  162. package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
  163. package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
  164. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  165. package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
  166. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  167. package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
  168. package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
  169. package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
  170. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  171. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  172. package/dist/registry/index.d.mts +15 -10
  173. package/dist/registry/index.mjs +4 -4
  174. package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
  175. package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
  176. package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
  177. package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
  178. package/dist/requirement-Bu50QALH.mjs +35 -0
  179. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  180. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  181. package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
  182. package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
  183. package/dist/routeValidator-DHtpzrzk.mjs +70 -0
  184. package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
  185. package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
  186. package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
  187. package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
  188. package/dist/schemas/index.d.mts +20 -10
  189. package/dist/schemas/index.mjs +8 -3
  190. package/dist/scim/index.d.mts +13 -13
  191. package/dist/scim/index.mjs +2 -2
  192. package/dist/scope/index.d.mts +4 -4
  193. package/dist/scope/index.mjs +6 -6
  194. package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
  195. package/dist/shutdown-LpLMH8T1.mjs +59 -0
  196. package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
  197. package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
  198. package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
  199. package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
  200. package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
  201. package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
  202. package/dist/tenancy-DxV8OuX2.mjs +43 -0
  203. package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
  204. package/dist/testing/cleanupStoreContract.d.mts +4 -5
  205. package/dist/testing/cleanupStoreContract.mjs +1 -1
  206. package/dist/testing/index.d.mts +215 -197
  207. package/dist/testing/index.mjs +289 -214
  208. package/dist/testing/outboxStoreContract.d.mts +4 -5
  209. package/dist/testing/outboxStoreContract.mjs +137 -2
  210. package/dist/testing/requiredPorts.d.mts +16 -0
  211. package/dist/testing/requiredPorts.mjs +65 -0
  212. package/dist/testing/storageContract.d.mts +4 -5
  213. package/dist/testing/storageContract.mjs +1 -1
  214. package/dist/timing-CntavTlR.mjs +46 -0
  215. package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
  216. package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.mjs} +1 -1
  217. package/dist/types/index.d.mts +4 -4
  218. package/dist/types/index.mjs +1 -1
  219. package/dist/types/storage.mjs +1 -1
  220. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  221. package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
  222. package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
  223. package/dist/usage/index.d.mts +5 -6
  224. package/dist/usage/index.mjs +5 -5
  225. package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
  226. package/dist/utils/index.d.mts +125 -264
  227. package/dist/utils/index.mjs +13 -12
  228. package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
  229. package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
  230. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  231. package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
  232. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  233. package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
  234. package/package.json +57 -59
  235. package/skills/arc/SKILL.md +13 -4
  236. package/skills/arc/references/auth.md +8 -5
  237. package/skills/arc/references/integrations.md +1 -1
  238. package/skills/arc/references/mcp.md +8 -6
  239. package/skills/arc/references/multi-tenancy.md +19 -14
  240. package/skills/arc/references/production.md +27 -10
  241. package/dist/actionPermissions-BoPGQ5-4.mjs +0 -350
  242. package/dist/index-CFfLKMH4.d.mts +0 -290
  243. package/dist/interface-YzIbF7Ii.mjs +0 -17
  244. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  245. package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
  246. package/dist/rolldown-runtime-eysyzFAF.mjs +0 -15
  247. package/dist/sync/changelog.d.mts +0 -3
  248. package/dist/sync/changelog.mjs +0 -3
@@ -1,5 +1,5 @@
1
1
 
2
- import { n as DomainEvent, r as EventHandler } from "./EventTransport-CbUKDzZZ.mjs";
2
+ import { n as DomainEvent, r as EventHandler } from "./EventTransport-CiicKz2Z.mjs";
3
3
  import { n as ErrorMapper } from "./errorHandler--4rSMMvq.mjs";
4
4
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
5
5
  import { LockAdapter } from "@classytic/repo-core/lock";
@@ -36,7 +36,6 @@ interface HealthOptions {
36
36
  /** Collect HTTP request metrics (default: true if metrics enabled) */
37
37
  collectHttpMetrics?: boolean;
38
38
  }
39
- declare const healthPlugin: FastifyPluginAsync<HealthOptions>;
40
39
  declare const _default$1: FastifyPluginAsync<HealthOptions>;
41
40
  //#endregion
42
41
  //#region src/plugins/schedules.d.ts
@@ -132,7 +131,6 @@ declare module "fastify" {
132
131
  getScheduleStats?: () => ScheduleStats[];
133
132
  }
134
133
  }
135
- declare const schedulesPlugin: FastifyPluginAsync<SchedulesPluginOptions>;
136
134
  declare const _default: FastifyPluginAsync<SchedulesPluginOptions>;
137
135
  //#endregion
138
136
  //#region src/factory/loadResources.d.ts
@@ -360,25 +358,26 @@ type ModuleDisposer = () => void | Promise<void>;
360
358
  * Second argument to a module's setup phases (`plugins`, `bootstrap`,
361
359
  * `afterResources`).
362
360
  */
363
- interface ModuleSetupContext {
361
+ interface ModuleSetupContext<TPorts extends PortName = PortName, TOptionalPorts extends PortName = PortName> {
364
362
  /**
365
- * Register a teardown callback for the resource just acquired. Call it
366
- * IMMEDIATELY after acquisition — that is what makes partial-init teardown
367
- * exact rather than defensive.
368
- *
369
- * Disposers run LIFO, after this module's `onClose`, on both the rollback
370
- * and shutdown paths, and exactly once. A throwing disposer is logged and
371
- * never blocks the ones behind it.
363
+ * Register a teardown callback for the resource just acquired — call it
364
+ * IMMEDIATELY after acquisition. Runs LIFO, after this module's `onClose`,
365
+ * exactly once on rollback or shutdown. A throwing disposer is logged and
366
+ * never blocks the rest.
372
367
  */
373
368
  defer(disposer: ModuleDisposer): void;
369
+ /**
370
+ * The ports THIS module declared in `requires`, frozen: each `ports` entry is present (boot
371
+ * fails otherwise), each `optionalPorts` entry is the value or `undefined`. Nothing else.
372
+ */
373
+ readonly ports: ModulePorts<TPorts, TOptionalPorts>;
374
374
  }
375
375
  //#endregion
376
376
  //#region src/factory/module/types.d.ts
377
377
  /**
378
378
  * A module's contribution of some arm (event handlers, scheduled jobs, …).
379
379
  * Either a static array or a factory resolved AFTER all module bootstraps, so a
380
- * contribution can close over booted engines / the Fastify instance instead of
381
- * reaching for a global getter (the coupling the module contract removes).
380
+ * contribution can close over booted engines instead of a global getter.
382
381
  */
383
382
  type ModuleContribution<T> = readonly T[] | ((fastify: FastifyInstance) => readonly T[] | Promise<readonly T[]>);
384
383
  /**
@@ -398,22 +397,13 @@ interface EventHandlerDefinition {
398
397
  */
399
398
  name?: string;
400
399
  /**
401
- * Contain handler failures inside an error boundary — arc wraps `handler`
402
- * with `wrapWithBoundary`, so a throw is logged (through `fastify.log`, with
403
- * `{ err, event, eventId, handler }`) and swallowed instead of reaching the
404
- * transport.
405
- *
406
- * **Off by default, deliberately.** Reaching the transport is what makes the
407
- * durable path work: `RedisStreamTransport` leaves the message unacked, so it
408
- * is redelivered and eventually DLQ'd. Silently swallowing by default would
409
- * turn every module handler into best-effort delivery.
400
+ * Contain handler failures: a throw is logged via `fastify.log` and swallowed
401
+ * instead of reaching the transport.
410
402
  *
411
- * Turn it ON for fire-and-forget work — projections, cache invalidation,
412
- * notification fan-out — where a failure must not block the ack and a retry
413
- * would only delay the next event's resync. That is precisely the case where
414
- * a module author would otherwise abandon this arm for an imperative
415
- * `subscribeWithBoundary` call in `afterResources` and lose arc's
416
- * teardown-before-`onClose` guarantee.
403
+ * **Off by default, deliberately** — reaching the transport is what makes the
404
+ * durable path work (unacked → redelivered → DLQ). Turn it ON for
405
+ * fire-and-forget work (projections, cache invalidation, fan-out) where a
406
+ * failure must not block the ack.
417
407
  *
418
408
  * ```ts
419
409
  * defineModule({ name: "search", eventHandlers: [
@@ -421,57 +411,77 @@ interface EventHandlerDefinition {
421
411
  * ] })
422
412
  * ```
423
413
  *
424
- * The object form adds an `onError` sink (metrics / alerting) in place of the
425
- * default log; it runs INSIDE the boundary, so a throwing `onError` is itself
426
- * caught and logged rather than escaping.
414
+ * The object form's `onError` replaces the default log; it runs INSIDE the
415
+ * boundary, so a throwing `onError` is caught and logged too.
427
416
  */
428
417
  boundary?: boolean | {
429
418
  onError?: (error: Error, event: DomainEvent) => void | Promise<void>;
430
419
  };
420
+ /**
421
+ * What a failure means for a DURABLE event: `'retry'` rethrows to the relay (pair with
422
+ * `withInbox` so a redelivery re-runs only this handler); `'boundary'` may swallow and is
423
+ * the same as `boundary: true`. The module graph reports a durable event's handler without it.
424
+ */
425
+ durability?: "retry" | "boundary";
431
426
  }
432
- interface ArcModule<TExports = unknown> {
427
+ interface ArcModule<TExports = unknown, TPorts extends PortName = PortName, TOptionalPorts extends PortName = PortName> {
433
428
  /** Stable identifier — appears in boot logs and duplicate-module detection. */
434
429
  readonly name: string;
435
430
  /**
436
- * Names of modules this one must be composed AFTER. arc topologically orders
437
- * the `modules` array from these edges before ANY phase runs, so a
438
- * dependency's `bootstrap` (and thus its `fastify.arc.modules[dep]` export)
439
- * is always live before this module's bootstrap, resources, or
440
- * afterResources. Teardown (`onClose`) runs in the reverse of that order.
441
- *
442
- * Use this instead of relying on list position when a module reads another's
443
- * public export — e.g. `@classytic/arc-reservation` wiring off
444
- * `arc.modules.order`:
431
+ * Names of modules this one must be composed AFTER. A dependency's
432
+ * `bootstrap` (and its `fastify.arc.modules[dep]` export) is always live
433
+ * before this module's phases run; teardown runs in reverse.
445
434
  *
446
435
  * ```ts
447
436
  * defineModule({ name: "reservation", dependsOn: ["order"], bootstrap: (f) => {
448
437
  * const order = getModuleExports<OrderEngine>(f, "order"); // guaranteed live
449
- * …
450
438
  * }});
451
439
  * ```
452
440
  *
453
- * The order is a STABLE topological sort: modules with no `dependsOn` (and
454
- * any two modules with no edge between them) keep their original list order,
455
- * so adding `dependsOn` never silently reorders an unrelated module. All
456
- * failure modes are fail-fast at boot: a name not in the composed set, a
457
- * self-reference, and dependency CYCLES each throw with the offending path.
458
- *
459
- * **`dependsOn` is COMPOSITION order, not lazy loading.** Every module input
460
- * (including dynamic-import thunks) is resolved — imported — up front via
461
- * `Promise.all` BEFORE the sort; `dependsOn` then orders the *composition*
462
- * (which module's `plugins`/`bootstrap` runs first), it does NOT delay
463
- * *importing* a dependent until its dependency is ready. Use a thunk to gate
464
- * whether a module is imported at all (region/tier packs); use `dependsOn`
465
- * to order modules that are already selected.
466
- *
467
- * **Convention: every HARD sibling dependency belongs here** — including
468
- * ones read lazily. `lazyRequiredModuleExports` defers the export READ to
469
- * first use (and eagerly validates the module is composed), but only
470
- * `dependsOn` orders composition so the sibling's engine exists before
471
- * yours initializes. Omit the edge only for genuinely optional
441
+ * STABLE topological sort: modules with no edge between them keep list
442
+ * order. Unknown names, self-references and cycles fail at boot.
443
+ *
444
+ * Orders COMPOSITION, not importing — every input (thunks included) is
445
+ * imported up front. Every HARD sibling dependency belongs here, even one
446
+ * read via `lazyRequiredModuleExports`; omit it only for optional
472
447
  * integrations (`lazyModuleExports` / `getOptionalModuleExports`).
473
448
  */
474
- readonly dependsOn?: readonly string[];
449
+ readonly dependsOn?: readonly ModuleName[];
450
+ /**
451
+ * - `'domain'` (default).
452
+ * - `'bridge'` joins two or more domains (order ↔ accounting): ACTIVE only when every
453
+ * `dependsOn` module is composed, dropped otherwise — no gate code.
454
+ * - `'platform'` — infrastructure (auth, outbox, notifications): orders ahead of every other
455
+ * ready module, and `resolveModuleClosure` includes it in every closure.
456
+ * - `'aggregator'` reads across the whole composed fleet (data cleanup, period close): it
457
+ * orders AFTER every non-aggregator module, as if it `dependsOn` all of them. A module that
458
+ * `dependsOn` an aggregator is therefore a cycle.
459
+ */
460
+ readonly kind?: "domain" | "bridge" | "platform" | "aggregator";
461
+ /**
462
+ * The events this module OWNS — the facts it publishes, one owner per event, each named inside
463
+ * `eventNamespace`. What it consumes is derived from `eventHandlers`. Checked at boot by
464
+ * `createApp({ moduleGraph: { events } })`; rendered by `renderModuleGraph`.
465
+ */
466
+ readonly publishes?: readonly (string | {
467
+ readonly name: string;
468
+ })[];
469
+ /**
470
+ * Owner prefix(es) of every event in `publishes` — `order` owns `order:placed` and
471
+ * `order.placed`. A list when one module owns several aggregates (`['order', 'rma']`).
472
+ * Default: the module name.
473
+ */
474
+ readonly eventNamespace?: string | readonly string[];
475
+ /**
476
+ * What must be SUPPLIED or COMPOSED for this module to boot — checked before any module boots,
477
+ * every gap in one error. Ordering stays `dependsOn`'s job; nothing here adds an edge.
478
+ *
479
+ * ```ts
480
+ * defineModule({ name: "order", requires: { ports: ["outbox"], optionalPorts: ["calendar"], modules: ["invoice"] },
481
+ * bootstrap: (f, { ports }) => createOrderEngine(ports.outbox, ports.calendar) })
482
+ * ```
483
+ */
484
+ readonly requires?: ModuleRequirements<TPorts, TOptionalPorts>;
475
485
  /**
476
486
  * Infra SETUP — the module analog of the app's `plugins()`. Runs after the
477
487
  * app's own `plugins()`, in `dependsOn` order, before any module `bootstrap`.
@@ -483,7 +493,6 @@ interface ArcModule<TExports = unknown> {
483
493
  * plugin throws; a bare multi-arg function warns.
484
494
  *
485
495
  * Receives `{ defer }` for teardown, or return one disposer as shorthand.
486
- * v3 renames this slot to `setup` with no alias (v3.md).
487
496
  *
488
497
  * ```ts
489
498
  * plugins: async (fastify) => {
@@ -493,48 +502,22 @@ interface ArcModule<TExports = unknown> {
493
502
  * },
494
503
  * ```
495
504
  */
496
- plugins?: (fastify: FastifyInstance, context: ModuleSetupContext) => ModuleDisposer | undefined | Promise<ModuleDisposer | undefined>;
505
+ plugins?: (fastify: FastifyInstance, context: ModuleSetupContext<TPorts, TOptionalPorts>) => ModuleDisposer | undefined | Promise<ModuleDisposer | undefined>;
497
506
  /**
498
507
  * Domain error mappers this module ships (see `defineErrorMapper`). Merged
499
508
  * into the app's error handler at boot, AFTER any host-declared mappers —
500
509
  * host config keeps priority; module mappers follow in composition order.
501
- *
502
- * Closes the "composition root must know every domain's error classes"
503
- * coupling: before this slot, adding `@classytic/arc-shipping` meant
504
- * remembering to also register its `ShippingError` mapper in the host's
505
- * `errorHandler.errorMappers` — forget it and the domain error crossed the
506
- * wire as an opaque 500. Now the module carries its own mapper:
507
- *
508
- * ```ts
509
- * defineModule({
510
- * name: 'shipping',
511
- * errorMappers: [shippingErrorMapper],
512
- * ...
513
- * })
514
- * ```
510
+ * The host no longer has to know every domain's error classes.
515
511
  */
516
512
  readonly errorMappers?: readonly ErrorMapper[];
517
513
  /**
518
- * Domain init — runs in the `bootstrap` phase (after all `plugins`, before
519
- * resources). Initialize engines / singletons / event subscriptions here so
520
- * they are live when this module's `resources` factory runs.
514
+ * Domain init — runs after all `plugins`, before resources, so engines are
515
+ * live when this module's `resources` factory runs.
521
516
  *
522
- * A returned value becomes the module's PUBLIC EXPORT, recorded at
523
- * `fastify.arc.modules[name]` (wiki/modules.md — "no container", layer 2).
524
- * Later modules' bootstraps can read it (list order = init order), which is
525
- * how cross-module ports are wired without a DI container. Declare the
526
- * export shape via `defineModule<TExports>` and read it back with the typed
527
- * accessor — no cast at the consuming end:
528
- *
529
- * ```ts
530
- * // arc-invoice bootstrap, composed AFTER arc-accounting:
531
- * const acct = getModuleExports<AccountingEngine>(fastify, "accounting");
532
- * ```
533
- *
534
- * The return value is the module's export, so teardown here goes through
535
- * `defer` (the second argument) rather than a returned disposer — register
536
- * each resource the moment it exists and partial-init cleanup becomes exact
537
- * instead of a chain of `?.` guards:
517
+ * The returned value is the module's PUBLIC EXPORT at
518
+ * `fastify.arc.modules[name]`; later modules read it with
519
+ * `getModuleExports<T>(fastify, name)`. Because the return slot is taken,
520
+ * teardown goes through `defer`, registered the moment each resource exists:
538
521
  *
539
522
  * ```ts
540
523
  * bootstrap: async (fastify, { defer }) => {
@@ -546,27 +529,18 @@ interface ArcModule<TExports = unknown> {
546
529
  * },
547
530
  * ```
548
531
  */
549
- bootstrap?: (fastify: FastifyInstance, context: ModuleSetupContext) => TExports | Promise<TExports>;
532
+ bootstrap?: (fastify: FastifyInstance, context: ModuleSetupContext<TPorts, TOptionalPorts>) => TExports | Promise<TExports>;
550
533
  /**
551
- * App-level resource NAMES this module SUPERSEDES. When the host lists (or
552
- * discovers) a resource of the same name, arc drops the app copy and
553
- * registers this module's — so the host maintains no "which resources did
554
- * modules take over" filter set.
534
+ * App-level resource NAMES this module SUPERSEDES: an app resource of the
535
+ * same name is dropped and this module's registered instead.
555
536
  *
556
- * PREFER `owns: "provided"`: arc derives the list from the resources the
557
- * module actually returned, so the claim cannot disagree with reality.
558
- * Resources resolve once, names are validated, `owns` is derived, and
559
- * supersession runs after — one phase arc owns.
537
+ * PREFER `owns: "provided"` — derived from the resources the module actually
538
+ * returned, so the claim cannot disagree with reality. An explicit array is
539
+ * ENFORCED at boot: each name must appear in THIS module's own `resources`
540
+ * (a sibling's does not count), else a typo would silently remove the app's
541
+ * route with nothing serving it.
560
542
  *
561
- * The explicit array is ENFORCED at boot: a claimed name must appear in THIS
562
- * module's own `resources`, or boot fails naming the unmet names. Without
563
- * that, a typo or a deleted resource booted "successfully" with the app's
564
- * route removed and nothing serving it. A sibling module providing the name
565
- * does NOT satisfy the claim — ownership is local to its declarer.
566
- *
567
- * It only filters the APP list, never the module's own resources; an entry
568
- * matching no app resource is a silent no-op (a module may pre-declare a
569
- * name before any fork exists). Collected across modules as a union.
543
+ * Filters only the APP list; a name matching no app resource is a no-op.
570
544
  */
571
545
  readonly owns?: readonly string[] | "provided";
572
546
  /**
@@ -577,27 +551,17 @@ interface ArcModule<TExports = unknown> {
577
551
  */
578
552
  resources?: ReadonlyArray<ResourceLike> | ((fastify: FastifyInstance) => ReadonlyArray<ResourceLike> | Promise<ReadonlyArray<ResourceLike>>);
579
553
  /**
580
- * Post-registration wiring — runs in the `afterResources` phase, once this
581
- * module's routes are mounted. Use for cross-resource event subscriptions the
582
- * module owns.
583
- *
584
- * Receives `{ defer }`, and may return a disposer, on the same terms as
585
- * `plugins` — handy for wiring that must be unwound (a subscription, a
586
- * listener) without hoisting a handle into module scope.
554
+ * Post-registration wiring — runs once this module's routes are mounted.
555
+ * Receives `{ defer }` and may return a disposer, same as `plugins`.
587
556
  */
588
- afterResources?: (fastify: FastifyInstance, context: ModuleSetupContext) => ModuleDisposer | undefined | Promise<ModuleDisposer | undefined>;
557
+ afterResources?: (fastify: FastifyInstance, context: ModuleSetupContext<TPorts, TOptionalPorts>) => ModuleDisposer | undefined | Promise<ModuleDisposer | undefined>;
589
558
  /**
590
- * Readiness checks this module contributes to the app's
591
- * `healthPlugin` (`/_health/ready`). Collected across ALL modules in
592
- * dependency order and merged BEFORE the host's app-level `arcPlugins.health`
593
- * checks (modules-first, host-last) — the same additive convention arc uses
594
- * for resources and error mappers. Check `name`s must be unique across the
595
- * module graph (arc fails at boot on a collision, attributing both owners).
559
+ * Readiness checks for `/_health/ready`, merged BEFORE the host's
560
+ * `arcPlugins.health` checks. Names must be unique across the module graph
561
+ * (boot fails on a collision).
596
562
  *
597
- * STATIC by design (not a factory): declare the check up front and let its
598
- * `check()` closure resolve dependencies LAZILY at probe time, so collection
599
- * can happen before the health plugin registers and the worker probe
600
- * (`createWorker`) receives the identical union without a second registration.
563
+ * STATIC by design (not a factory): collection happens before the health
564
+ * plugin registers, so `check()` should resolve dependencies LAZILY.
601
565
  *
602
566
  * ```ts
603
567
  * defineModule({ name: "inventory", healthChecks: [
@@ -607,15 +571,10 @@ interface ArcModule<TExports = unknown> {
607
571
  */
608
572
  readonly healthChecks?: readonly HealthCheck[];
609
573
  /**
610
- * Domain-event subscriptions this module owns — the declarative replacement
611
- * for imperative `fastify.events.subscribe(...)` calls inside
612
- * `afterResources`. Subscribed in dependency order during the
613
- * `afterResources` phase (routes mounted, engines booted), and arc RETAINS
614
- * every unsubscribe and invokes it at shutdown BEFORE module `onClose` (while
615
- * module deps are still alive). Named handlers must be unique across the
616
- * module graph; a module that declares handlers while the event subsystem is
617
- * disabled (`arcPlugins.events: false`) fails at boot. Array or factory (the
618
- * factory runs after bootstraps, so it can close over booted engines).
574
+ * Domain-event subscriptions this module owns. Subscribed in dependency order
575
+ * during `afterResources`; unsubscribed at shutdown BEFORE module `onClose`
576
+ * (deps still alive). Named handlers must be unique across the graph;
577
+ * declaring handlers with `arcPlugins.events: false` fails at boot.
619
578
  *
620
579
  * ```ts
621
580
  * defineModule({ name: "party", eventHandlers: [
@@ -625,13 +584,9 @@ interface ArcModule<TExports = unknown> {
625
584
  */
626
585
  readonly eventHandlers?: ModuleContribution<EventHandlerDefinition>;
627
586
  /**
628
- * Durable workflows this module owns. OPAQUE to arc core — arc never imports
629
- * `@classytic/streamline`; the value flows through untouched and the
630
- * streamline integration (`@classytic/arc/integrations/streamline`, a peer)
631
- * gives it meaning (name/shape validation, registration). Collected via
632
- * `collectModuleWorkflows` at integration-init time (after module bootstraps),
633
- * so a factory can close over the shared workflow container the integration
634
- * decorates on the instance.
587
+ * Durable workflows this module owns. OPAQUE to arc core — the streamline
588
+ * integration (`@classytic/arc/integrations/streamline`) validates and
589
+ * registers them after module bootstraps.
635
590
  *
636
591
  * ```ts
637
592
  * defineModule({ name: "invoice",
@@ -640,12 +595,10 @@ interface ArcModule<TExports = unknown> {
640
595
  */
641
596
  readonly workflows?: ModuleContribution<unknown>;
642
597
  /**
643
- * Recurring interval schedules this module owns. Arc composes them into its
644
- * existing `schedulesPlugin` after bootstraps, in dependency order. Names
645
- * must be unique across the module graph. Configure multi-replica locking via
646
- * `arcPlugins.schedules`; explicitly disabling that runner while a module
647
- * declares schedules fails boot instead of silently dropping work. Array or
648
- * factory (the factory runs once after bootstraps and may close over engines).
598
+ * Recurring interval schedules, composed into `schedulesPlugin` after
599
+ * bootstraps. Names must be unique across the graph; locking is configured
600
+ * via `arcPlugins.schedules`, and disabling that runner fails boot rather
601
+ * than silently dropping work.
649
602
  *
650
603
  * ```ts
651
604
  * defineModule({ name: "loyalty", scheduledJobs: [
@@ -655,42 +608,18 @@ interface ArcModule<TExports = unknown> {
655
608
  */
656
609
  readonly scheduledJobs?: ModuleContribution<ScheduleDefinition>;
657
610
  /**
658
- * Teardown — destroy engines, stop timers, flush the module's outbox.
659
- * Modules close in REVERSE composition order (last composed, first closed),
660
- * mirroring init, BEFORE the app-level `onClose` and before infra plugins'
661
- * own close hooks (so teardown still sees a live DB/Redis).
662
- *
663
- * Boot is transactional: if any phase fails mid-boot — including THIS
664
- * module's own `plugins`/`bootstrap` — arc immediately closes every module
665
- * that entered an init phase, this closer included, in reverse order, then
666
- * rethrows the original boot error. A closer runs AT MOST ONCE across the
667
- * rollback and shutdown paths, and one throwing closer never blocks the
668
- * rest (best-effort; failures are logged).
669
- *
670
- * Because rollback fires after a PARTIAL init — your `bootstrap` opened a
671
- * client and threw on the next line, or your `plugins` ran but `bootstrap`
672
- * never did — write closers defensively. Guard everything init may not have
673
- * reached: `client?.close()`, not `client.close()`.
674
- *
675
- * ```ts
676
- * let client: Client | undefined;
677
- * defineModule({
678
- * name: "billing",
679
- * bootstrap: async () => {
680
- * client = await openClient(); // allocated…
681
- * await migrate(client); // …and this may throw
682
- * },
683
- * onClose: async () => { await client?.close(); }, // still runs
684
- * });
685
- * ```
686
- *
687
- * Prefer `defer` (the setup phases' second argument) for anything acquired
688
- * step-by-step: it registers each teardown at the point of acquisition, so
689
- * the runtime knows exactly what was reached and the `?.` guards above
690
- * disappear. `onClose` remains the right slot for tearing down what
691
- * `bootstrap` RETURNED — the module's engine — and runs FIRST, ahead of the
692
- * module's deferred disposers (see `disposers.ts` for the ordering rule).
693
- * The two compose; either alone is complete.
611
+ * Teardown — destroy engines, stop timers, flush the outbox. Modules close in
612
+ * REVERSE composition order, BEFORE the app-level `onClose` and infra
613
+ * plugins' close hooks (so teardown still sees a live DB/Redis).
614
+ *
615
+ * Boot is transactional: if any phase fails, every module that entered an
616
+ * init phase (this one included) is closed in reverse, then the original
617
+ * error is rethrown. A closer runs AT MOST ONCE; a throwing one never blocks
618
+ * the rest.
619
+ *
620
+ * Rollback can follow a PARTIAL init, so guard what init may not have
621
+ * reached (`client?.close()`), or prefer `defer` for step-by-step
622
+ * acquisition. `onClose` runs FIRST, ahead of deferred disposers.
694
623
  */
695
624
  onClose?: (fastify: FastifyInstance) => void | Promise<void>;
696
625
  }
@@ -701,11 +630,8 @@ interface ArcModule<TExports = unknown> {
701
630
  * - `Promise<ArcModule>` — an in-flight import
702
631
  * - `() => ArcModule | Promise<…>` — a thunk, resolved at boot
703
632
  *
704
- * The **thunk of a dynamic import** is the point of this union — it lets a host
705
- * compose modules *logically* and load them *lazily / conditionally*, the
706
- * `next/dynamic` idea applied to backend domains. The module's code (and its
707
- * heavy deps) is only pulled in when the thunk runs, so region- or tier-gated
708
- * packs never enter the bundle path they aren't selected for:
633
+ * A thunk of a dynamic import loads a module only when selected (region- or
634
+ * tier-gated packs):
709
635
  *
710
636
  * ```ts
711
637
  * const taxPack =
@@ -717,11 +643,59 @@ interface ArcModule<TExports = unknown> {
717
643
  * // only the selected tax package is imported + composed
718
644
  * ```
719
645
  */
720
- type ArcModuleInput<TExports = unknown> = ArcModule<TExports> | Promise<ArcModule<TExports>> | (() => ArcModule<TExports> | Promise<ArcModule<TExports>>);
646
+ type ArcModuleInput<TExports = unknown> = ArcModule<TExports> | Promise<ArcModule<TExports>> | (() => ArcModule<TExports> | Promise<ArcModule<TExports>>) | LazyBridge<TExports>;
647
+ /**
648
+ * A bridge whose graph identity is declared up front, so arc decides activation
649
+ * BEFORE importing it — a bridge to a domain that is not composed is never
650
+ * loaded, not merely never activated. (A plain thunk must run before its
651
+ * `dependsOn` is known.) The loaded module must match the declaration.
652
+ *
653
+ * ```ts
654
+ * modules: [order, {
655
+ * kind: "bridge", name: "order-accounting", dependsOn: ["order", "accounting"],
656
+ * load: () => import("./bridges/order-accounting.js").then((m) => m.create(deps)),
657
+ * }]
658
+ * ```
659
+ */
660
+ interface LazyBridge<TExports = unknown> {
661
+ readonly kind: "bridge";
662
+ readonly name: string;
663
+ readonly dependsOn: readonly ModuleName[];
664
+ /** Must equal the loaded module's `requires` — `planModules` checks ports without loading it. */
665
+ readonly requires?: ModuleRequirements;
666
+ readonly load: () => ArcModule<TExports> | Promise<ArcModule<TExports>>;
667
+ }
668
+ /**
669
+ * Host collaborators by name — augment it so ports are typed and a misspelt name is a compile error:
670
+ *
671
+ * ```ts
672
+ * declare module "@classytic/arc/factory" {
673
+ * interface ArcPortRegistry { outbox: OutboxStore; calendar: BusinessCalendar }
674
+ * }
675
+ * ```
676
+ */
677
+ interface ArcPortRegistry {}
678
+ /** A registered port name. */
679
+ type PortName = keyof ArcPortRegistry & string;
680
+ /** What `createApp({ ports })` accepts — any registered port, typed by the registry. */
681
+ type ArcPorts = { readonly [K in PortName]?: ArcPortRegistry[K]; };
682
+ /** The ports one module's setup phases see: required ones typed present, optional ones `| undefined`. */
683
+ type ModulePorts<TPorts extends PortName, TOptionalPorts extends PortName> = { readonly [K in TPorts]: ArcPortRegistry[K]; } & { readonly [K in TOptionalPorts]?: ArcPortRegistry[K]; };
684
+ /** What a module needs from its host. See `ArcModule.requires`. */
685
+ interface ModuleRequirements<TPorts extends PortName = PortName, TOptionalPorts extends PortName = PortName> {
686
+ /** Ports that must be supplied to `createApp({ ports })`. */
687
+ readonly ports?: readonly TPorts[];
688
+ /** Ports handed over when supplied, `undefined` otherwise. */
689
+ readonly optionalPorts?: readonly TOptionalPorts[];
690
+ /**
691
+ * Modules that must be COMPOSED, with no ordering edge — a mutual request-time need
692
+ * (accounting ⇄ invoice) a two-way `dependsOn` would make a cycle. Read the peer lazily.
693
+ */
694
+ readonly modules?: readonly ModuleName[];
695
+ }
721
696
  /**
722
- * Typed module registry — hosts augment this via declaration merging so
723
- * `getModuleExports(f, "order")` infers the export type without a manual type
724
- * argument (the fastify / awilix pattern):
697
+ * Typed module registry — augment via declaration merging so
698
+ * `getModuleExports(f, "order")` infers the export type:
725
699
  *
726
700
  * ```ts
727
701
  * declare module "@classytic/arc/factory" {
@@ -731,10 +705,21 @@ type ArcModuleInput<TExports = unknown> = ArcModule<TExports> | Promise<ArcModul
731
705
  * }
732
706
  * }
733
707
  * ```
734
- *
735
- * Left empty here so the fallback `getModuleExports<T>(f, name: string)` stays
736
- * available for apps that don't augment it.
737
708
  */
738
709
  interface ArcModuleRegistry {}
710
+ /**
711
+ * Host-level composition switches. A HOST (never a package) sets `strictModuleNames: true` once
712
+ * every module it composes is in `ArcModuleRegistry`; then a misspelt `dependsOn` /
713
+ * `requires.modules` name is a compile error.
714
+ *
715
+ * ```ts
716
+ * declare module "@classytic/arc/factory" { interface ArcCompositionConfig { strictModuleNames: true } }
717
+ * ```
718
+ */
719
+ interface ArcCompositionConfig {}
720
+ /** A module name as a reference: a registry key under `strictModuleNames`, else any string. */
721
+ type ModuleName = ArcCompositionConfig extends {
722
+ readonly strictModuleNames: true;
723
+ } ? keyof ArcModuleRegistry & string : string;
739
724
  //#endregion
740
- export { HealthCheck as _, ModuleContribution as a, healthPlugin as b, LoadResourcesOptions as c, loadResources as d, ScheduleDefinition as f, schedulesPlugin as g, _default as h, EventHandlerDefinition as i, ResourceLike as l, SchedulesPluginOptions as m, ArcModuleInput as n, ModuleDisposer as o, ScheduleStats as p, ArcModuleRegistry as r, ModuleSetupContext as s, ArcModule as t, ResourceModule as u, HealthOptions as v, _default$1 as y };
725
+ export { _default as C, _default$1 as E, SchedulesPluginOptions as S, HealthOptions as T, ResourceLike as _, ArcPortRegistry as a, ScheduleDefinition as b, LazyBridge as c, ModulePorts as d, ModuleRequirements as f, LoadResourcesOptions as g, ModuleSetupContext as h, ArcModuleRegistry as i, ModuleContribution as l, ModuleDisposer as m, ArcModule as n, ArcPorts as o, PortName as p, ArcModuleInput as r, EventHandlerDefinition as s, ArcCompositionConfig as t, ModuleName as u, ResourceModule as v, HealthCheck as w, ScheduleStats as x, loadResources as y };
@@ -1,5 +1,6 @@
1
1
 
2
- import { V as ResourceDefinition } from "./index-DHbyPo5j.mjs";
2
+ import { F as ResourceDefinition } from "./index-WH7xgvxS.mjs";
3
+ import { i as ResourceRateLimiter } from "./resourceRateLimit-Wf6d6FL9.mjs";
3
4
  import { z } from "zod";
4
5
  //#region src/integrations/mcp/types.d.ts
5
6
  /** Behavioral hints for MCP clients (tool annotations per MCP spec) */
@@ -87,8 +88,12 @@ interface ToolDefinition<TSession = McpAuthResult> {
87
88
  name: string;
88
89
  description: string;
89
90
  title?: string;
90
- /** Flat Zod shape: `{ field: z.string() }`. NOT z.object(). */
91
- inputSchema?: Record<string, z.ZodTypeAny>;
91
+ /**
92
+ * Flat Zod shape `{ field: z.string() }` (the SDK wraps it in a STRIPPING
93
+ * `z.object()`), or a whole Zod schema registered as-is — use the whole form
94
+ * when unknown keys must be refused or the input is a union of objects.
95
+ */
96
+ inputSchema?: ToolInputSchema;
92
97
  /** Flat Zod shape for structured output validation */
93
98
  outputSchema?: Record<string, z.ZodTypeAny>;
94
99
  annotations?: ToolAnnotations;
@@ -105,6 +110,8 @@ interface ToolDefinition<TSession = McpAuthResult> {
105
110
  */
106
111
  source?: string;
107
112
  }
113
+ /** A tool's input contract: a flat Zod shape, or a whole Zod schema validated as-is. */
114
+ type ToolInputSchema = Record<string, z.ZodTypeAny> | z.ZodType;
108
115
  /** Output of definePrompt() — plain data, not yet registered */
109
116
  interface PromptDefinition {
110
117
  name: string;
@@ -178,6 +185,8 @@ type CrudDescriptionOverride = string | ((meta: CrudDescriptionMeta) => string);
178
185
  * omit it to keep the legacy dispatch-only behavior.
179
186
  */
180
187
  interface McpExecutionWiring {
188
+ /** Same resource budget and backend used by REST routes. */
189
+ readonly rateLimiter?: ResourceRateLimiter;
181
190
  /** Shared `HookSystem` — `fastify.arc.hooks`. */
182
191
  readonly hooks?: unknown;
183
192
  /** Event bus decorator — `fastify.events`. */
@@ -192,6 +201,12 @@ interface McpExecutionWiring {
192
201
  * on mutating CRUD tools and enables executor-level check/lock/replay.
193
202
  */
194
203
  readonly idempotencyStore?: unknown;
204
+ /** Explicit migration escape matching the HTTP idempotency plugin. */
205
+ readonly allowUnfencedCompletion?: boolean;
206
+ /** Cached result lifetime shared with the HTTP plugin; defaults to 24 hours. */
207
+ readonly idempotencyTtlMs?: number;
208
+ /** Completion lease shared with the HTTP plugin; defaults to 30 seconds. */
209
+ readonly idempotencyLockTimeoutMs?: number;
195
210
  }
196
211
  /** Per-resource MCP configuration overrides */
197
212
  interface McpResourceConfig {
@@ -328,7 +343,7 @@ interface McpPluginOptions {
328
343
  /** Instructions for the LLM — guidance on tool usage, constraints */
329
344
  instructions?: string;
330
345
  /**
331
- * Resources to exclude by name (ignored if `expose`/`include` is set).
346
+ * Resources to exclude by name. Mutually exclusive with `expose`.
332
347
  *
333
348
  * Drift-prone for LLM-facing surfaces: a new `defineResource()` is exposed
334
349
  * by default and you have to remember to add it here. Prefer `expose` when
@@ -341,10 +356,8 @@ interface McpPluginOptions {
341
356
  * inverse of `exclude`), matching the principle-of-least-authority that
342
357
  * LLM-driven tool surfaces want.
343
358
  *
344
- * Mutually exclusive with `include` (a deprecated alias) — passing both
345
- * throws at registration. Mutually exclusive with `exclude`: the deny
346
- * default would render the exclude list redundant; passing both throws
347
- * with a hint to drop `exclude`.
359
+ * Mutually exclusive with `exclude` (the deny default makes it redundant);
360
+ * passing both throws with a hint to drop `exclude`.
348
361
  *
349
362
  * @example
350
363
  * ```ts
@@ -482,4 +495,4 @@ interface CreateMcpServerConfig {
482
495
  /** CRUD operation type */
483
496
  type CrudOperation = "list" | "get" | "create" | "update" | "delete";
484
497
  //#endregion
485
- export { ToolDefinition as _, CrudDescriptionOverride as a, McpAuthResult as c, McpResourceConfig as d, PromptDefinition as f, ToolContext as g, ToolAnnotations as h, CrudDescriptionMeta as i, McpExecutionWiring as l, SessionEntry as m, CallToolResult as n, CrudOperation as o, PromptResult as p, CreateMcpServerConfig as r, McpAuthResolver as s, BetterAuthHandler as t, McpPluginOptions as u };
498
+ export { ToolDefinition as _, CrudDescriptionOverride as a, McpAuthResult as c, McpResourceConfig as d, PromptDefinition as f, ToolContext as g, ToolAnnotations as h, CrudDescriptionMeta as i, McpExecutionWiring as l, SessionEntry as m, CallToolResult as n, CrudOperation as o, PromptResult as p, CreateMcpServerConfig as r, McpAuthResolver as s, BetterAuthHandler as t, McpPluginOptions as u, ToolInputSchema as v };
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { l as UnauthorizedError, o as OrgRequiredError } from "./errors-BH7YU_9O.mjs";
3
- import { n as requireSingleHeaderValue } from "./headers-BIXWKrzQ.mjs";
4
- import { t as getUserId$1 } from "./userHelpers-ChRvkuSZ.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { l as UnauthorizedError, o as OrgRequiredError } from "./errors-CW4anJBy.mjs";
3
+ import { n as requireSingleHeaderValue } from "./headers-DKZQKL-Z.mjs";
4
+ import { t as getUserId$1 } from "./userHelpers-C76zD5pa.mjs";
5
5
  //#region src/scope/types.ts
6
6
  /**
7
7
  * Request Scope — The One Standard