@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,10 +1,24 @@
1
1
 
2
- import { a as EventTransport, n as DomainEvent, t as DeadLetteredEvent } from "./EventTransport-CbUKDzZZ.mjs";
3
- import { t as ArcModule } from "./types-DfVDvkdP.mjs";
2
+ import { a as EventTransport, n as DomainEvent, t as DeadLetteredEvent } from "./EventTransport-CiicKz2Z.mjs";
3
+ import { n as ArcModule } from "./types-BJ-2XqVF.mjs";
4
4
  import { FastifyInstance } from "fastify";
5
5
  import { MemoryOutboxStore } from "@classytic/primitives/memory-outbox";
6
6
  import { OutboxFailurePolicy, OutboxStore, OutboxWriteOptions } from "@classytic/primitives/outbox";
7
7
  import { RepositoryLike } from "@classytic/repo-core/adapter";
8
+ //#region src/events/outbox/health.d.ts
9
+ /** Pending age past which the relay counts as stalled. */
10
+ declare const DEFAULT_RELAY_LAG_UNHEALTHY_MS: number;
11
+ interface OutboxHealth {
12
+ pending: number;
13
+ deadLetter: number;
14
+ /** Age of the oldest pending event, `null` when nothing is pending. */
15
+ relayLagMs: number | null;
16
+ healthy: boolean;
17
+ }
18
+ /** A store that can report its own depth — both reads are optional on the contract. */
19
+ type OutboxHealthStore = OutboxStore & Required<Pick<OutboxStore, "countByStatus" | "oldestPendingAgeMs">>;
20
+ declare function readOutboxHealth(store: OutboxHealthStore, relayLagUnhealthyMs?: number): Promise<OutboxHealth>;
21
+ //#endregion
8
22
  //#region src/events/outbox/relay.d.ts
9
23
  /** Reason codes passed to {@link EventOutboxOptions.onError}. */
10
24
  type OutboxRelayErrorKind = "publish_failed" | "acknowledge_failed" | "fail_failed" | "ownership_mismatch" | "malformed_event";
@@ -94,6 +108,8 @@ interface EventOutboxOptions {
94
108
  * logging and metrics. Must not throw — exceptions are swallowed.
95
109
  */
96
110
  readonly onError?: OutboxRelayErrorHandler;
111
+ /** Called with each event the transport accepted — observation only, never affects the batch. */
112
+ readonly onPublished?: (event: DomainEvent) => void;
97
113
  /**
98
114
  * Enable {@link EventTransport.publishMany} when the transport implements it.
99
115
  * Default: `true`. Set to `false` to force per-event `publish()` — useful
@@ -166,6 +182,7 @@ declare class EventOutbox {
166
182
  private readonly _consumerId;
167
183
  private readonly _leaseMs;
168
184
  private readonly _onError?;
185
+ private readonly _onPublished?;
169
186
  private readonly _usePublishMany;
170
187
  private readonly _sessionProvider?;
171
188
  private readonly _traceContextProvider?;
@@ -175,9 +192,11 @@ declare class EventOutbox {
175
192
  * process; resets on restart. Populated as failures occur and cleared on
176
193
  * successful ack or dead-letter transition; additionally hard-capped at
177
194
  * {@link MAX_TRACKED_ATTEMPTS} (oldest-write evicted first) so events
178
- * resolved by another relay worker can't accumulate forever. For durable
179
- * authoritative counts, apps can query the store directly inside
180
- * {@link OutboxFailurePolicy}.
195
+ * resolved by another relay worker can't accumulate forever.
196
+ *
197
+ * A FALLBACK only: when the store reports `attempts` on the fenced claim
198
+ * (`repositoryAsOutboxStore` and primitives' `MemoryOutboxStore` do), the
199
+ * durable count wins — see the failure path in `relayBatch`.
181
200
  */
182
201
  private readonly _attempts;
183
202
  constructor(opts: EventOutboxOptions);
@@ -365,6 +384,46 @@ interface OutboxModuleOptions {
365
384
  * the events the previous process failed to publish.
366
385
  */
367
386
  runOnStart?: boolean;
387
+ /**
388
+ * The relay's readiness check (`/_health/ready`, name `<name>.relay`). NON-critical:
389
+ * a stalled relay is a cluster problem, and pulling this instance from rotation would
390
+ * not drain it. Unhealthy when anything is dead-lettered or the oldest pending event
391
+ * is older than `relayLagUnhealthyMs` (default 5 min), and when the store cannot
392
+ * report either. `false` omits the check.
393
+ *
394
+ * `probeCacheMs` (default 5000) is how long the check reuses its last reading.
395
+ * The reading costs two status counts and an oldest-pending lookup, and a
396
+ * readiness probe runs on EVERY replica every few seconds — uncached, 50 pods
397
+ * probed every 5 s is ~30 outbox queries a second, and a status count is at
398
+ * its most expensive during a backlog, the moment the database has least to
399
+ * spare. Concurrent probes also share one in-flight reading. Keep it at or
400
+ * below the probe interval; `0` reads on every probe. The `health()` export is
401
+ * never cached — an operator asking wants the current answer.
402
+ *
403
+ * `alertEveryMs` (default 5 min, `0` disables) schedules `<name>.health`, which logs
404
+ * while unhealthy: `marker: 'OUTBOX_RELAY_LAG'` at error, `marker: 'OUTBOX_DEAD_LETTER'`
405
+ * at warn. A readiness check is PULLED — nobody is paged by a degraded row in a probe
406
+ * body, so the stall is also pushed into the log where alerting reads it. It is a
407
+ * `kind: 'relay'` arm, so it runs wherever the relay runs (including a `role: 'relay'`
408
+ * process). A store that cannot report counts is warned about once, not every tick.
409
+ */
410
+ health?: {
411
+ relayLagUnhealthyMs?: number;
412
+ probeCacheMs?: number;
413
+ alertEveryMs?: number;
414
+ } | false;
415
+ }
416
+ /** Totals across the passes of one {@link OutboxModuleExports.drain}. */
417
+ interface OutboxDrainResult {
418
+ passes: number;
419
+ relayed: number;
420
+ publishFailed: number;
421
+ deadLettered: number;
422
+ /**
423
+ * `true` once a pass found nothing VISIBLE to claim. Rows backing off after a failed
424
+ * publish are invisible until their retry time, so read `publishFailed` too.
425
+ */
426
+ drained: boolean;
368
427
  }
369
428
  /** What the module exports at `fastify.arc.modules.<name>`. */
370
429
  interface OutboxModuleExports {
@@ -385,6 +444,20 @@ interface OutboxModuleExports {
385
444
  * The pass runs scope-free, so each event opens its own (see `runWorkScope`).
386
445
  */
387
446
  requestDrain: () => void;
447
+ /**
448
+ * Relay passes until nothing visible is pending, and resolve with the totals —
449
+ * including events published BY the handlers it ran. For tests that assert what
450
+ * an event eventually did, and for a shutdown that should empty the queue.
451
+ * Bounded by `maxPasses` (default 100). Runs outside the caller's request scope,
452
+ * like {@link requestDrain}. Waits for this module's own in-flight passes (a nudge,
453
+ * a scheduled tick), whose leased rows no other pass can see; rows leased by
454
+ * ANOTHER process are not awaited.
455
+ */
456
+ drain: (options?: {
457
+ maxPasses?: number;
458
+ }) => Promise<OutboxDrainResult>;
459
+ /** The same reading as the readiness check. Throws when the store cannot report. */
460
+ health: () => Promise<OutboxHealth>;
388
461
  }
389
462
  /**
390
463
  * Compose the outbox as a module.
@@ -415,4 +488,4 @@ interface OutboxModuleExports {
415
488
  */
416
489
  declare function createOutboxModule(options: OutboxModuleOptions): ArcModule<OutboxModuleExports>;
417
490
  //#endregion
418
- export { EventOutbox as a, OutboxRelayErrorKind as c, MemoryOutboxStore as i, RelayResult as l, OutboxModuleOptions as n, EventOutboxOptions as o, createOutboxModule as r, OutboxRelayErrorHandler as s, OutboxModuleExports as t };
491
+ export { MemoryOutboxStore as a, OutboxRelayErrorHandler as c, DEFAULT_RELAY_LAG_UNHEALTHY_MS as d, OutboxHealth as f, createOutboxModule as i, OutboxRelayErrorKind as l, readOutboxHealth as m, OutboxModuleExports as n, EventOutbox as o, OutboxHealthStore as p, OutboxModuleOptions as r, EventOutboxOptions as s, OutboxDrainResult as t, RelayResult as u };
@@ -1,5 +1,6 @@
1
1
 
2
- import { c as DataPolicy, d as UserBase, l as PermissionCheck, m as normalizeRoles, p as getUserRoles, s as AuthorizationDecision, u as PermissionContext } from "../base-CxrV1LmG.mjs";
3
- import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission } from "../fields-I_EeRWND.mjs";
4
- import { $ as requireAuth, A as RequireGrantOptions, B as ConnectEventsOptions, C as requireServiceScope, D as GRANT_MODES, E as createRoleHierarchy, F as PermissionRequirement, G as createOrgPermissions, H as DynamicPermissionMatrixConfig, I as collectPublicSurface, J as allowPublic, K as allOf, L as describePermission, M as requireGrant, N as conjoinPolicyFilters, O as GrantMode, P as AccessExplanation, Q as not, R as describePermissionMap, S as requireScopeContext, T as RoleHierarchy, U as PermissionEventBus, V as DynamicPermissionMatrix, W as createDynamicPermissionMatrix, X as deny, Y as anyOf, Z as denyAll, _ as publicReadAdminWrite, a as PermissionMatrixRegistry, at as scopeOf, b as requireOrgMembership, c as introspectModule, ct as evaluatePermissionDecision, d as authenticated, dt as RequireMandateOptions, et as requireOwnership, f as fullPublic, ft as requireAgentScope, g as publicRead, h as presets_d_exports, i as PermissionMatrixOptions, it as when, j as modeSatisfies, k as GrantResolution, l as introspectRegistry, lt as normalizeToDecision, m as permissionMatrix, mt as requireMandate, n as PermissionEntryType, nt as requireRoles, o as buildPermissionMatrix, ot as PermissionCheckReturn, p as ownerWithAdminBypass, pt as requireDPoP, q as allow, r as PermissionMatrix, rt as roles, s as introspectCheck, st as applyAuthorizationDecision, t as PermissionEntry, tt as requirePlatformRole, u as adminOnly, ut as RequireAgentScopeOptions, v as readOnly, w as requireTeamMembership, x as requireOrgRole, y as requireOrgInScope, z as explainAccess } from "../index-BHdZD8zK.mjs";
5
- export { type AccessExplanation, type AuthorizationDecision, type ConnectEventsOptions, type DataPolicy, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type FieldPermission, type FieldPermissionMap, type FieldPermissionType, GRANT_MODES, type GrantMode, type GrantResolution, type PermissionCheck, type PermissionCheckReturn, type PermissionContext, type PermissionEntry, type PermissionEntryType, type PermissionEventBus, type PermissionMatrix, type PermissionMatrixOptions, type PermissionMatrixRegistry, type PermissionRequirement, type RequireAgentScopeOptions, type RequireGrantOptions, type RequireMandateOptions, type RoleHierarchy, type UserBase, adminOnly, allOf, allow, allowPublic, anyOf, applyAuthorizationDecision, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, buildPermissionMatrix, collectPublicSurface, conjoinPolicyFilters, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, deny, denyAll, describePermission, describePermissionMap, evaluatePermissionDecision, explainAccess, fields, fullPublic, getUserRoles, introspectCheck, introspectModule, introspectRegistry, modeSatisfies, normalizeRoles, normalizeToDecision, not, ownerWithAdminBypass, permissionMatrix, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireGrant, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requirePlatformRole, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, scopeOf, when };
2
+ import { c as DataPolicy, d as UserBase, l as PermissionCheck, m as normalizeRoles, p as getUserRoles, s as AuthorizationDecision, u as PermissionContext } from "../base-CZyixEQR.mjs";
3
+ import { a as applyFieldReadPermissions, c as resolveEffectiveRoles, i as FieldWritePermissionResult, n as FieldPermissionMap, o as applyFieldWritePermissions, r as FieldPermissionType, s as fields, t as FieldPermission } from "../fields-BBSpf8pS.mjs";
4
+ import { $ as deny, A as GrantResolution, B as explainAccess, C as requireScopeContext, D as createRoleHierarchy, E as RoleHierarchy, F as AccessExplanation, G as createDynamicPermissionMatrix, H as DynamicPermissionMatrix, I as PermissionRequirement, J as PublicPolicy, K as createOrgPermissions, L as collectPublicSurface, M as modeSatisfies, N as requireGrant, O as GRANT_MODES, P as conjoinPolicyFilters, Q as anyOf, R as describePermission, S as requireOrgRole, T as requireTeamMembership, U as DynamicPermissionMatrixConfig, V as ConnectEventsOptions, W as PermissionEventBus, X as allow, Y as allOf, Z as allowPublic, _ as publicRead, _t as requireMandate, a as PermissionMatrixRegistry, at as requireRoles, b as requireOrgInScope, c as introspectModule, ct as scopeOf, d as adminOnly, dt as evaluatePermissionDecision, et as denyAll, f as authenticated, ft as normalizeToDecision, g as presets_d_exports, gt as requireDPoP, h as permissionMatrix, ht as requireAgentScope, i as PermissionMatrixOptions, it as requirePlatformRole, j as RequireGrantOptions, k as GrantMode, l as introspectRegistry, lt as PermissionCheckReturn, m as ownerWithAdminBypass, mt as RequireMandateOptions, n as PermissionEntryType, nt as requireAuth, o as buildPermissionMatrix, ot as roles, p as fullPublic, pt as RequireAgentScopeOptions, q as AllowPublicOptions, r as PermissionMatrix, rt as requireOwnership, s as introspectCheck, st as when, t as PermissionEntry, tt as not, u as PermissionOverrides, ut as applyAuthorizationDecision, v as publicReadAdminWrite, w as requireServiceScope, x as requireOrgMembership, y as readOnly, z as describePermissionMap } from "../index-Bxriq3so.mjs";
5
+ import { a as createMemoryResourceRateLimiter, i as ResourceRateLimiter, n as ResourceRateLimitDecision, o as resolveResourceRateLimiter, r as ResourceRateLimitPolicy, t as ResourceRateLimitContext } from "../resourceRateLimit-Wf6d6FL9.mjs";
6
+ export { type AccessExplanation, type AllowPublicOptions, type AuthorizationDecision, type ConnectEventsOptions, type DataPolicy, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type FieldPermission, type FieldPermissionMap, type FieldPermissionType, type FieldWritePermissionResult, GRANT_MODES, type GrantMode, type GrantResolution, type PermissionCheck, type PermissionCheckReturn, type PermissionContext, type PermissionEntry, type PermissionEntryType, type PermissionEventBus, type PermissionMatrix, type PermissionMatrixOptions, type PermissionMatrixRegistry, type PermissionOverrides, type PermissionRequirement, type PublicPolicy, type RequireAgentScopeOptions, type RequireGrantOptions, type RequireMandateOptions, type ResourceRateLimitContext, type ResourceRateLimitDecision, type ResourceRateLimitPolicy, type ResourceRateLimiter, type RoleHierarchy, type UserBase, adminOnly, allOf, allow, allowPublic, anyOf, applyAuthorizationDecision, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, buildPermissionMatrix, collectPublicSurface, conjoinPolicyFilters, createDynamicPermissionMatrix, createMemoryResourceRateLimiter, createOrgPermissions, createRoleHierarchy, deny, denyAll, describePermission, describePermissionMap, evaluatePermissionDecision, explainAccess, fields, fullPublic, getUserRoles, introspectCheck, introspectModule, introspectRegistry, modeSatisfies, normalizeRoles, normalizeToDecision, not, ownerWithAdminBypass, permissionMatrix, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireGrant, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requirePlatformRole, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, resolveResourceRateLimiter, roles, scopeOf, when };
@@ -1,6 +1,8 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { a as conjoinPolicyFilters, i as normalizeToDecision, r as evaluatePermissionDecision, t as applyAuthorizationDecision } from "../authorizationDecision-DawKGoXb.mjs";
3
- import { n as getUserRoles, r as normalizeRoles } from "../userHelpers-ChRvkuSZ.mjs";
4
- import { a as resolveEffectiveRoles, i as fields, n as applyFieldWritePermissions, t as applyFieldReadPermissions } from "../fields-BHrkoF5y.mjs";
5
- import { A as requireAgentScope, B as requireAuth, C as createOrgPermissions, D as requireScopeContext, E as requireOrgRole, F as allowPublic, G as when, H as requirePlatformRole, I as anyOf, K as scopeOf, L as deny, M as requireMandate, N as allOf, O as requireServiceScope, P as allow, R as denyAll, S as createDynamicPermissionMatrix, T as requireOrgMembership, U as requireRoles, V as requireOwnership, W as roles, _ as requireGrant, a as adminOnly, b as describePermissionMap, c as ownerWithAdminBypass, d as publicRead, f as publicReadAdminWrite, g as modeSatisfies, h as GRANT_MODES, i as introspectRegistry, j as requireDPoP, k as requireTeamMembership, l as permissionMatrix, m as createRoleHierarchy, n as introspectCheck, o as authenticated, p as readOnly, r as introspectModule, s as fullPublic, t as buildPermissionMatrix, u as presets_exports, v as collectPublicSurface, w as requireOrgInScope, x as explainAccess, y as describePermission, z as not } from "../permissions-D8TBH44E.mjs";
6
- export { GRANT_MODES, adminOnly, allOf, allow, allowPublic, anyOf, applyAuthorizationDecision, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, buildPermissionMatrix, collectPublicSurface, conjoinPolicyFilters, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, deny, denyAll, describePermission, describePermissionMap, evaluatePermissionDecision, explainAccess, fields, fullPublic, getUserRoles, introspectCheck, introspectModule, introspectRegistry, modeSatisfies, normalizeRoles, normalizeToDecision, not, ownerWithAdminBypass, permissionMatrix, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireGrant, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requirePlatformRole, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, scopeOf, when };
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { t as conjoinPolicyFilters } from "../filter-merge-DGM-GQ1J.mjs";
3
+ import { n as getUserRoles, r as normalizeRoles } from "../userHelpers-C76zD5pa.mjs";
4
+ import { a as resolveEffectiveRoles, i as fields, n as applyFieldWritePermissions, t as applyFieldReadPermissions } from "../fields-A5crhsJb.mjs";
5
+ import { i as normalizeToDecision, r as evaluatePermissionDecision, t as applyAuthorizationDecision } from "../authorizationDecision-wX3SA9w_.mjs";
6
+ import { i as resolveResourceRateLimiter, t as createMemoryResourceRateLimiter } from "../resourceRateLimit-urWPJxrt.mjs";
7
+ import { A as requireAgentScope, B as requireAuth, C as createOrgPermissions, D as requireScopeContext, E as requireOrgRole, F as allowPublic, G as when, H as requirePlatformRole, I as anyOf, K as scopeOf, L as deny, M as requireMandate, N as allOf, O as requireServiceScope, P as allow, R as denyAll, S as createDynamicPermissionMatrix, T as requireOrgMembership, U as requireRoles, V as requireOwnership, W as roles, _ as requireGrant, a as adminOnly, b as describePermissionMap, c as ownerWithAdminBypass, d as publicRead, f as publicReadAdminWrite, g as modeSatisfies, h as GRANT_MODES, i as introspectRegistry, j as requireDPoP, k as requireTeamMembership, l as permissionMatrix, m as createRoleHierarchy, n as introspectCheck, o as authenticated, p as readOnly, r as introspectModule, s as fullPublic, t as buildPermissionMatrix, u as presets_exports, v as collectPublicSurface, w as requireOrgInScope, x as explainAccess, y as describePermission, z as not } from "../permissions-C5ijIz5Q.mjs";
8
+ export { GRANT_MODES, adminOnly, allOf, allow, allowPublic, anyOf, applyAuthorizationDecision, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, buildPermissionMatrix, collectPublicSurface, conjoinPolicyFilters, createDynamicPermissionMatrix, createMemoryResourceRateLimiter, createOrgPermissions, createRoleHierarchy, deny, denyAll, describePermission, describePermissionMap, evaluatePermissionDecision, explainAccess, fields, fullPublic, getUserRoles, introspectCheck, introspectModule, introspectRegistry, modeSatisfies, normalizeRoles, normalizeToDecision, not, ownerWithAdminBypass, permissionMatrix, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireGrant, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requirePlatformRole, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, resolveResourceRateLimiter, roles, scopeOf, when };
@@ -1,11 +1,12 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-eysyzFAF.mjs";
3
- import { arcLog } from "./logger/index.mjs";
4
- import { a as conjoinPolicyFilters, i as normalizeToDecision } from "./authorizationDecision-DawKGoXb.mjs";
5
- import { n as getUserRoles } from "./userHelpers-ChRvkuSZ.mjs";
6
- import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-CgenB1gv.mjs";
7
- import { n as declareRuntimeCapability } from "./runtimeCapabilities-CkhS6jn6.mjs";
8
- import { t as MemoryCacheStore } from "./memory-Iotz0cCx.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { r as __exportAll } from "./rolldown-runtime-MYrLNGnq.mjs";
3
+ import { t as arcLog } from "./logger-D4V1zGLI.mjs";
4
+ import { t as conjoinPolicyFilters } from "./filter-merge-DGM-GQ1J.mjs";
5
+ import { n as getUserRoles } from "./userHelpers-C76zD5pa.mjs";
6
+ import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-fvO6Ovzx.mjs";
7
+ import { i as normalizeToDecision } from "./authorizationDecision-wX3SA9w_.mjs";
8
+ import { n as declareRuntimeCapability } from "./runtimeCapabilities-DIMKMV2G.mjs";
9
+ import { t as MemoryCacheStore } from "./memory-CTa5pOCW.mjs";
9
10
  import { randomUUID } from "node:crypto";
10
11
  //#region src/permissions/context.ts
11
12
  /**
@@ -76,20 +77,78 @@ function deny(reason) {
76
77
  function normalizeVariadicOrArray(args) {
77
78
  return Array.isArray(args[0]) ? args[0] : args;
78
79
  }
80
+ /** A non-null, non-array object — the only shape a row policy can take. */
81
+ function isPolicyObject(value) {
82
+ return value !== null && typeof value === "object" && !Array.isArray(value);
83
+ }
84
+ /** Own keys, symbol-keyed included (a branded adapter-native filter is symbol-keyed). */
85
+ function hasPolicyKeys(policy) {
86
+ return Object.keys(policy).length > 0 || Object.getOwnPropertySymbols(policy).length > 0;
87
+ }
79
88
  /**
80
- * Allow public access (no authentication required).
89
+ * Allow public access (no authentication required) — optionally row-filtered.
90
+ *
91
+ * Without options, anyone passes and sees everything the query asks for.
92
+ *
93
+ * With `policy`, anyone still passes, but only the rows the policy admits are
94
+ * readable (or writable). This is the declarative form of a check that returns
95
+ * `allow({ policy })`, and it exists because the hand-written form has a trap:
96
+ * a check function is treated as PROTECTED unless it carries `_isPublic`, so a
97
+ * custom "public, but only published rows" check that forgets to set the flag
98
+ * answers every anonymous visitor with a 401 — the public page silently
99
+ * becomes a login wall. `allowPublic({ policy })` sets it for you.
100
+ *
101
+ * Fail-closed on both ends:
102
+ * - a STATIC policy must be a non-empty object — `{}` filters nothing and is
103
+ * almost certainly a mistake, so it throws at definition time (use
104
+ * `allowPublic()` for an unrestricted gate);
105
+ * - a RESOLVER that returns anything but an object throws per request, which
106
+ * arc answers as a 403 for everyone. A forgotten branch that falls off the
107
+ * end of the function must not quietly mean "no filter". Return `{}` when a
108
+ * caller genuinely should be unrestricted.
109
+ *
110
+ * Composing: inside `anyOf`, the FIRST granting branch wins, and a public
111
+ * branch grants everyone — so `anyOf(allowPublic({ policy }), requireRoles("admin"))`
112
+ * filters admins too. Put the privileged branch first, or use the resolver
113
+ * form and return `{}` for staff.
81
114
  *
82
115
  * @example
83
116
  * ```typescript
84
- * permissions: {
85
- * list: allowPublic(),
86
- * get: allowPublic(),
87
- * }
117
+ * // Strangers see only published reviews, whatever filter they send.
118
+ * list: allowPublic({ policy: { status: { $in: ["published", "flagged"] } } }),
119
+ *
120
+ * // Scope-aware: the author also sees their own pending review; staff see all.
121
+ * list: allowPublic({
122
+ * policy: (ctx) => {
123
+ * const scope = scopeOf(ctx);
124
+ * if (isElevated(scope)) return {};
125
+ * const visible = { status: { $in: ["published", "flagged"] } };
126
+ * const userId = getUserId(scope);
127
+ * return userId ? { $or: [visible, { authorId: userId }] } : visible;
128
+ * },
129
+ * }),
88
130
  * ```
89
131
  */
90
- function allowPublic() {
91
- const check = () => true;
132
+ function allowPublic(options) {
133
+ const policy = options?.policy;
134
+ if (policy === void 0) {
135
+ const check = () => true;
136
+ check._isPublic = true;
137
+ return check;
138
+ }
139
+ let check;
140
+ if (typeof policy === "function") check = async (ctx) => {
141
+ const resolved = await policy(ctx);
142
+ if (!isPolicyObject(resolved)) throw new Error(`[arc] allowPublic({ policy }) resolver returned ${resolved === null ? "null" : Array.isArray(resolved) ? "an array" : typeof resolved} for '${ctx.resource}:${ctx.action}'. Return a filter object, or {} to admit this caller unrestricted. Returning nothing is refused rather than read as 'no filter'.`);
143
+ return hasPolicyKeys(resolved) ? allow({ policy: resolved }) : allow();
144
+ };
145
+ else {
146
+ if (!isPolicyObject(policy)) throw new Error(`[arc] allowPublic({ policy }) expects a filter object or a function returning one, got ${policy === null ? "null" : Array.isArray(policy) ? "an array" : typeof policy}.`);
147
+ if (!hasPolicyKeys(policy)) throw new Error("[arc] allowPublic({ policy: {} }) filters nothing. Use allowPublic() for an unrestricted public gate, or give the policy a constraint.");
148
+ check = () => allow({ policy });
149
+ }
92
150
  check._isPublic = true;
151
+ check._rowPolicy = true;
93
152
  return check;
94
153
  }
95
154
  /**
@@ -262,8 +321,11 @@ function allOf(...checks) {
262
321
  };
263
322
  const roleBranches = checks.filter((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0);
264
323
  if (checks.some((c) => c._isDenied)) check._isDenied = true;
265
- else if (checks.length > 0 && checks.every((c) => c._isPublic)) check._isPublic = true;
266
- else if (roleBranches.length === 1) {
324
+ else if (checks.some((c) => c._deniedWhen)) check._deniedWhen = () => checks.some((c) => c._deniedWhen?.() === true);
325
+ if (check._isDenied) {} else if (checks.length > 0 && checks.every((c) => c._isPublic)) {
326
+ check._isPublic = true;
327
+ if (checks.some((c) => c._rowPolicy)) check._rowPolicy = true;
328
+ } else if (roleBranches.length === 1) {
267
329
  const [rb] = roleBranches;
268
330
  if (rb?._roles?.length) check._roles = [...rb._roles];
269
331
  if (rb?._orgRoles?.length) check._orgRoles = [...rb._orgRoles];
@@ -274,7 +336,7 @@ function allOf(...checks) {
274
336
  else scopeContext[dim] = val;
275
337
  if (Object.keys(scopeContext).length > 0) check._scopeContext = scopeContext;
276
338
  const hasMetaSurface = (c) => Boolean(c._isPublic || c._roles?.length || c._orgRoles?.length || c._scopeContext && Object.keys(c._scopeContext).length > 0);
277
- const opaqueBranch = checks.some((c) => !hasMetaSurface(c) && !c._requiresAuth);
339
+ const opaqueBranch = checks.some((c) => !hasMetaSurface(c) && !c._requiresAuth && !c._deniedWhen);
278
340
  const childConditional = checks.some((c) => c._conditional);
279
341
  if (opaqueBranch || scopeConflict || childConditional) check._conditional = true;
280
342
  return check;
@@ -302,9 +364,13 @@ function anyOf(...checks) {
302
364
  }
303
365
  return deny(reasons.join("; "));
304
366
  };
367
+ const closed = (c) => Boolean(c._isDenied || c._deniedWhen?.());
305
368
  if (checks.length > 0 && checks.every((c) => c._isDenied)) check._isDenied = true;
306
- else if (checks.some((c) => c._isPublic)) check._isPublic = true;
307
- else if (checks.every((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0)) {
369
+ else if (checks.length > 0 && checks.every((c) => c._isDenied || c._deniedWhen)) check._deniedWhen = () => checks.every(closed);
370
+ else if (checks.some((c) => c._isPublic)) {
371
+ check._isPublic = true;
372
+ if (checks.find((c) => c._isPublic)?._rowPolicy) check._rowPolicy = true;
373
+ } else if (checks.every((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0)) {
308
374
  const roles = /* @__PURE__ */ new Set();
309
375
  const orgRoles = /* @__PURE__ */ new Set();
310
376
  for (const c of checks) {
@@ -314,9 +380,27 @@ function anyOf(...checks) {
314
380
  if (roles.size > 0) check._roles = [...roles];
315
381
  if (orgRoles.size > 0) check._orgRoles = [...orgRoles];
316
382
  if (checks.some((c) => c._conditional)) check._conditional = true;
383
+ const scopes = checks.map((c) => c._scopeContext ?? {});
384
+ const key = (s) => JSON.stringify(Object.entries(s).sort(([a], [b]) => a.localeCompare(b)));
385
+ const first = scopes[0] ?? {};
386
+ if (scopes.every((s) => key(s) === key(first))) {
387
+ if (Object.keys(first).length > 0) check._scopeContext = { ...first };
388
+ } else check._conditional = true;
389
+ } else if (checks.some(isMembershipGate) && checks.every((c) => isMembershipGate(c) || isPlainRoleGate(c))) {
390
+ check._orgPermission = "membership";
391
+ const roles = /* @__PURE__ */ new Set();
392
+ const orgRoles = /* @__PURE__ */ new Set();
393
+ for (const c of checks) {
394
+ for (const r of c._roles ?? []) roles.add(r);
395
+ for (const r of c._orgRoles ?? []) orgRoles.add(r);
396
+ }
397
+ if (roles.size > 0) check._roles = [...roles];
398
+ if (orgRoles.size > 0) check._orgRoles = [...orgRoles];
317
399
  }
318
400
  return check;
319
401
  }
402
+ const isMembershipGate = (c) => c._orgPermission === "membership" && !c._conditional && !c._scopeContext;
403
+ const isPlainRoleGate = (c) => ((c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0) && !c._conditional && !c._scopeContext;
320
404
  /**
321
405
  * Invert a permission check. Grants when the wrapped check denies, denies
322
406
  * when the wrapped check grants. Useful for "block if X" patterns —
@@ -1085,25 +1169,33 @@ function createDynamicPermissionMatrix(config) {
1085
1169
  * fully captured statically carries `conditional: true` (see `_conditional`).
1086
1170
  */
1087
1171
  function describePermission(check) {
1088
- if (check._isDenied) return { kind: "denied" };
1089
- if (check._isPublic) return { kind: "public" };
1172
+ if (check._isDenied || check._deniedWhen?.() === true) return { kind: "denied" };
1173
+ if (check._isPublic) return check._rowPolicy ? {
1174
+ kind: "public",
1175
+ rowFiltered: true
1176
+ } : { kind: "public" };
1090
1177
  const roles = [...check._roles ?? [], ...check._orgRoles ?? []];
1091
1178
  const scope = check._scopeContext;
1092
- const conditional = check._conditional ? true : void 0;
1179
+ const conditional = check._conditional === true;
1180
+ const flag = conditional ? { conditional: true } : {};
1093
1181
  if (scope && Object.keys(scope).length > 0) return roles.length > 0 ? {
1094
1182
  kind: "scoped",
1095
1183
  scope,
1096
1184
  roles,
1097
- conditional
1185
+ ...flag
1098
1186
  } : {
1099
1187
  kind: "scoped",
1100
1188
  scope,
1101
- conditional
1189
+ ...flag
1102
1190
  };
1191
+ if (check._orgPermission === "membership" && !conditional) return roles.length > 0 ? {
1192
+ kind: "member",
1193
+ roles
1194
+ } : { kind: "member" };
1103
1195
  if (roles.length > 0) return {
1104
1196
  kind: "roles",
1105
1197
  roles,
1106
- conditional
1198
+ ...flag
1107
1199
  };
1108
1200
  return { kind: "authenticated" };
1109
1201
  }
@@ -1131,7 +1223,7 @@ function explainAccess(check, principal) {
1131
1223
  };
1132
1224
  if (requirement.kind === "public") return {
1133
1225
  decision: "allow",
1134
- reason: "public — no authentication required",
1226
+ reason: requirement.rowFiltered ? "public — no authentication required; rows narrowed by a policy" : "public — no authentication required",
1135
1227
  requirement
1136
1228
  };
1137
1229
  if (requirement.kind === "authenticated") return {
@@ -1140,6 +1232,29 @@ function explainAccess(check, principal) {
1140
1232
  requirement
1141
1233
  };
1142
1234
  const held = /* @__PURE__ */ new Set([...principal?.roles ?? [], ...principal?.orgRoles ?? []]);
1235
+ if (requirement.kind === "member") {
1236
+ const reason = `requires membership of the active organization${requirement.roles?.length ? ` or one of: ${requirement.roles.join(", ")}` : ""}`;
1237
+ const byRole = requirement.roles?.filter((r) => held.has(r)) ?? [];
1238
+ if (byRole.length > 0) return {
1239
+ decision: "allow",
1240
+ reason: `granted via role(s): ${byRole.join(", ")}`,
1241
+ requirement
1242
+ };
1243
+ if (principal?.member === void 0) return {
1244
+ decision: "conditional",
1245
+ reason,
1246
+ requirement
1247
+ };
1248
+ return principal.member ? {
1249
+ decision: "allow",
1250
+ reason: "granted as a member of the active organization",
1251
+ requirement
1252
+ } : {
1253
+ decision: "deny",
1254
+ reason,
1255
+ requirement
1256
+ };
1257
+ }
1143
1258
  if (requirement.kind === "scoped") {
1144
1259
  const scopeReason = describeScope(requirement.scope);
1145
1260
  const rolesReason = requirement.roles?.length ? `role(s) ${requirement.roles.join(", ")} + ` : "";
@@ -1600,12 +1715,9 @@ function readOnly(overrides) {
1600
1715
  *
1601
1716
  * ## Known gap
1602
1717
  *
1603
- * `anyOf(a, b)` composes checks into a bare function with no aggregated meta,
1604
- * so `anyOf(platformAdminOnly(), requireOrgRole(…))` introspects as
1605
- * `authenticated` with no roles. Consumers must read `authenticated` as "the
1606
- * server decides" rather than "any logged-in user", and fall back to an
1607
- * explicit gate for those. Fixing it means teaching the combinators to
1608
- * aggregate meta — an arc change, tracked here because this is where it shows.
1718
+ * `authenticated` means "the server decides" — a custom or request-time check
1719
+ * (ownership, service scope) — never "any logged-in user". Membership and
1720
+ * role/membership disjunctions are `member`; only opaque branches remain here.
1609
1721
  */
1610
1722
  /** Re-shape arc's typed requirement into the wire entry. */
1611
1723
  function introspectCheck(check) {
@@ -1623,6 +1735,10 @@ function introspectCheck(check) {
1623
1735
  roles: req.roles ? [...req.roles] : [],
1624
1736
  scope: { ...req.scope }
1625
1737
  };
1738
+ if (req.kind === "member") return {
1739
+ type: "member",
1740
+ roles: req.roles ? [...req.roles] : []
1741
+ };
1626
1742
  if (req.kind === "denied") return {
1627
1743
  type: "denied",
1628
1744
  roles: []
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { r as ForbiddenError } from "./errors-BH7YU_9O.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { r as ForbiddenError } from "./errors-CW4anJBy.mjs";
3
3
  //#region src/pipeline/pipe.ts
4
4
  /**
5
5
  * Compose pipeline steps into an ordered array.
@@ -1,5 +1,5 @@
1
1
 
2
- import { Ct as PipelineStep, Fn as IControllerResponse, St as PipelineContext, _t as Guard, bt as OperationFilter, vt as Interceptor, wt as Transform, xt as PipelineConfig, yt as NextFunction } from "../index-DHbyPo5j.mjs";
2
+ import { Vn as IControllerResponse, _t as PipelineConfig, bt as Transform, gt as OperationFilter, ht as NextFunction, mt as Interceptor, pt as Guard, vt as PipelineContext, yt as PipelineStep } from "../index-WH7xgvxS.mjs";
3
3
  //#region src/pipeline/guard.d.ts
4
4
  interface GuardOptions {
5
5
  operations?: OperationFilter;
@@ -11,7 +11,7 @@ interface GuardOptions {
11
11
  * @param name - Guard name (for debugging/introspection)
12
12
  * @param handlerOrOptions - Handler function or options object
13
13
  */
14
- declare function guard(name: string, handlerOrOptions: ((ctx: PipelineContext) => boolean | Promise<boolean>) | GuardOptions): Guard;
14
+ export declare function guard(name: string, handlerOrOptions: ((ctx: PipelineContext) => boolean | Promise<boolean>) | GuardOptions): Guard;
15
15
  //#endregion
16
16
  //#region src/pipeline/intercept.d.ts
17
17
  interface InterceptOptions {
@@ -24,14 +24,14 @@ interface InterceptOptions {
24
24
  * @param name - Interceptor name (for debugging/introspection)
25
25
  * @param handlerOrOptions - Handler function or options object
26
26
  */
27
- declare function intercept(name: string, handlerOrOptions: ((ctx: PipelineContext, next: NextFunction) => Promise<IControllerResponse<unknown>>) | InterceptOptions): Interceptor;
27
+ export declare function intercept(name: string, handlerOrOptions: ((ctx: PipelineContext, next: NextFunction) => Promise<IControllerResponse<unknown>>) | InterceptOptions): Interceptor;
28
28
  //#endregion
29
29
  //#region src/pipeline/pipe.d.ts
30
30
  /**
31
31
  * Compose pipeline steps into an ordered array.
32
32
  * Accepts guards, transforms, and interceptors in any order.
33
33
  */
34
- declare function pipe(...steps: PipelineStep[]): PipelineStep[];
34
+ export declare function pipe(...steps: PipelineStep[]): PipelineStep[];
35
35
  /**
36
36
  * Execute a pipeline against a request context.
37
37
  *
@@ -44,7 +44,7 @@ declare function pipe(...steps: PipelineStep[]): PipelineStep[];
44
44
  * @param operation - The CRUD operation name
45
45
  * @returns The controller response (possibly modified by interceptors)
46
46
  */
47
- declare function executePipeline(steps: PipelineStep[], ctx: PipelineContext, handler: (ctx: PipelineContext) => Promise<IControllerResponse<unknown>>, operation: string): Promise<IControllerResponse<unknown>>;
47
+ export declare function executePipeline(steps: PipelineStep[], ctx: PipelineContext, handler: (ctx: PipelineContext) => Promise<IControllerResponse<unknown>>, operation: string): Promise<IControllerResponse<unknown>>;
48
48
  //#endregion
49
49
  //#region src/pipeline/transform.d.ts
50
50
  interface TransformOptions {
@@ -57,6 +57,6 @@ interface TransformOptions {
57
57
  * @param name - Transform name (for debugging/introspection)
58
58
  * @param handlerOrOptions - Handler function or options object
59
59
  */
60
- declare function transform(name: string, handlerOrOptions: ((ctx: PipelineContext) => PipelineContext | undefined | Promise<PipelineContext | undefined>) | TransformOptions): Transform;
60
+ export declare function transform(name: string, handlerOrOptions: ((ctx: PipelineContext) => PipelineContext | undefined | Promise<PipelineContext | undefined>) | TransformOptions): Transform;
61
61
  //#endregion
62
- export { type Guard, type Interceptor, type NextFunction, type OperationFilter, type PipelineConfig, type PipelineContext, type PipelineStep, type Transform, executePipeline, guard, intercept, pipe, transform };
62
+ export type { Guard, GuardOptions, InterceptOptions, Interceptor, NextFunction, OperationFilter, PipelineConfig, PipelineContext, PipelineStep, Transform, TransformOptions };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { n as pipe, t as executePipeline } from "../pipe-FMWNheSO.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { n as pipe, t as executePipeline } from "../pipe-D81zMtWs.mjs";
3
3
  //#region src/pipeline/guard.ts
4
4
  /**
5
5
  * Create a named guard.