@classytic/arc 2.30.0 → 2.32.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 (242) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -0
  3. package/TRADEMARK.md +22 -0
  4. package/dist/{BaseController-B5DwufbB.mjs → BaseController-Dk34dxhI.mjs} +21 -19
  5. package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
  6. package/dist/{EventTransport-C6YA8xY9.mjs → EventTransport-BV10D0oG.mjs} +1 -0
  7. package/dist/{HookSystem-WkIougo6.mjs → HookSystem-vimw5D7Z.mjs} +2 -1
  8. package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
  9. package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-BVr7PgA0.mjs} +18 -15
  10. package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-Dti7ebyv.mjs} +1 -0
  11. package/dist/audit/index.d.mts +3 -2
  12. package/dist/audit/index.mjs +2 -1
  13. package/dist/auth/audit.d.mts +1 -0
  14. package/dist/auth/audit.mjs +1 -0
  15. package/dist/auth/index.d.mts +5 -4
  16. package/dist/auth/index.mjs +11 -9
  17. package/dist/auth/redis-session.d.mts +2 -1
  18. package/dist/auth/redis-session.mjs +1 -0
  19. package/dist/{applyPermissionResult-Cf6zUCvb.mjs → authorizationDecision-__T3EJDA.mjs} +19 -10
  20. package/dist/{buildHandler-BYS3Vkfz.mjs → buildHandler-Dn7rzUJo.mjs} +6 -4
  21. package/dist/cache/index.d.mts +4 -3
  22. package/dist/cache/index.mjs +4 -3
  23. package/dist/{caching-_NJeDd1_.mjs → caching-DFJqHyFs.mjs} +2 -1
  24. package/dist/cleanup/index.d.mts +39 -16
  25. package/dist/cleanup/index.mjs +50 -38
  26. package/dist/cli/commands/describe.d.mts +2 -1
  27. package/dist/cli/commands/describe.mjs +4 -5
  28. package/dist/cli/commands/docs.d.mts +1 -0
  29. package/dist/cli/commands/docs.mjs +3 -2
  30. package/dist/cli/commands/doctor.d.mts +1 -0
  31. package/dist/cli/commands/doctor.mjs +1 -0
  32. package/dist/cli/commands/generate.d.mts +2 -1
  33. package/dist/cli/commands/generate.mjs +2 -1
  34. package/dist/cli/commands/init.d.mts +2 -1
  35. package/dist/cli/commands/init.mjs +2 -1
  36. package/dist/cli/commands/introspect.d.mts +1 -0
  37. package/dist/cli/commands/introspect.mjs +2 -1
  38. package/dist/cli/index.d.mts +3 -2
  39. package/dist/cli/index.mjs +3 -2
  40. package/dist/{constants-DNre0wRg.mjs → constants-J99LGTJI.mjs} +1 -0
  41. package/dist/context/index.d.mts +1 -0
  42. package/dist/context/index.mjs +3 -2
  43. package/dist/core/index.d.mts +3 -2
  44. package/dist/core/index.mjs +7 -6
  45. package/dist/{core-poalJLvp.mjs → core-BwwmNSp7.mjs} +3 -2
  46. package/dist/{createActionRouter-BxuywVRB.mjs → createActionRouter-DkJZWo0m.mjs} +14 -10
  47. package/dist/{createAggregationRouter-BiY-lGdY.mjs → createAggregationRouter-B1exOVD8.mjs} +10 -7
  48. package/dist/{createApp-CXrKFciH.mjs → createApp-Bv0m-Q6_.mjs} +625 -229
  49. package/dist/{defineAggregation-ufWjUwml.mjs → defineAggregation-gX4No1gt.mjs} +1 -0
  50. package/dist/{defineResource-CxWqDCAy.mjs → defineResource-XFPPUu2c.mjs} +84 -50
  51. package/dist/discovery/index.d.mts +1 -0
  52. package/dist/discovery/index.mjs +1 -0
  53. package/dist/docs/index.d.mts +3 -2
  54. package/dist/docs/index.mjs +3 -2
  55. package/dist/{elevation-C9s5hFP_.d.mts → elevation-BXCfVcNG.d.mts} +1 -0
  56. package/dist/{elevation-CIz1GjGm.mjs → elevation-DNXXxgjl.mjs} +4 -3
  57. package/dist/encryption/index.d.mts +2 -1
  58. package/dist/encryption/index.mjs +5 -3
  59. package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-B2AZUJUj.mjs} +3 -2
  60. package/dist/{errorHandler-C6yv4ZTW.d.mts → errorHandler-CpYUJmPA.d.mts} +1 -0
  61. package/dist/{errors-DHcHVAgk.d.mts → errors-BGdVwP25.d.mts} +1 -0
  62. package/dist/{errors-DkO8Nk6-.mjs → errors-Bh1KK5ps.mjs} +1 -0
  63. package/dist/{eventPlugin-D2NNR4py.mjs → eventPlugin-Dk_TyXeE.mjs} +8 -6
  64. package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
  65. package/dist/events/index.d.mts +112 -44
  66. package/dist/events/index.mjs +177 -141
  67. package/dist/events/transports/redis-stream-entry.d.mts +2 -1
  68. package/dist/events/transports/redis-stream-entry.mjs +14 -9
  69. package/dist/events/transports/redis.d.mts +2 -1
  70. package/dist/events/transports/redis.mjs +1 -0
  71. package/dist/{externalPaths-BE6wFpYv.d.mts → externalPaths-CmGOxOtD.d.mts} +1 -0
  72. package/dist/factory/index.d.mts +9 -5
  73. package/dist/factory/index.mjs +131 -7
  74. package/dist/fastify-augmentation-BppG5bMT.d.mts +179 -0
  75. package/dist/{fields-CQ_7KQWw.mjs → fields-DEi65pGj.mjs} +6 -10
  76. package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
  77. package/dist/{generate-D0wZupBg.mjs → generate-CRSG0jTv.mjs} +2 -1
  78. package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
  79. package/dist/{headers-BvlQSDMB.mjs → headers-BXjQYbeC.mjs} +2 -1
  80. package/dist/{health-CB_m4S32.mjs → health-5XnTa2uo.mjs} +1 -0
  81. package/dist/hooks/index.d.mts +2 -1
  82. package/dist/hooks/index.mjs +2 -1
  83. package/dist/idempotency/index.d.mts +3 -2
  84. package/dist/idempotency/index.mjs +8 -5
  85. package/dist/idempotency/redis.d.mts +2 -1
  86. package/dist/idempotency/redis.mjs +1 -0
  87. package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
  88. package/dist/{index-Q5gzEIWY.d.mts → index-CpzxqePp.d.mts} +107 -32
  89. package/dist/{index-Cx7bro4R.d.mts → index-DXlHwvuE.d.mts} +5 -4
  90. package/dist/index-ZWhLTp_s.d.mts +290 -0
  91. package/dist/{index-Ch16aAah.d.mts → index-zq6VrjaB.d.mts} +122 -15
  92. package/dist/index.d.mts +9 -8
  93. package/dist/index.mjs +11 -10
  94. package/dist/{init-tlRSt667.mjs → init-BbfP8aky.mjs} +25 -27
  95. package/dist/{init-BPEu8cs-.d.mts → init-Cscs4lab.d.mts} +1 -0
  96. package/dist/integrations/event-gateway.d.mts +3 -2
  97. package/dist/integrations/event-gateway.mjs +2 -1
  98. package/dist/integrations/index.d.mts +4 -3
  99. package/dist/integrations/index.mjs +1 -0
  100. package/dist/integrations/jobs/index.d.mts +2 -1
  101. package/dist/integrations/jobs/index.mjs +109 -33
  102. package/dist/integrations/mcp/index.d.mts +6 -6
  103. package/dist/integrations/mcp/index.mjs +2 -1
  104. package/dist/integrations/mcp/testing.d.mts +3 -2
  105. package/dist/integrations/mcp/testing.mjs +2 -2
  106. package/dist/integrations/streamline.d.mts +1 -0
  107. package/dist/integrations/streamline.mjs +3 -2
  108. package/dist/integrations/webhooks.d.mts +1 -0
  109. package/dist/integrations/webhooks.mjs +1 -0
  110. package/dist/integrations/websocket-pushref-redis.d.mts +2 -1
  111. package/dist/integrations/websocket-pushref-redis.mjs +1 -0
  112. package/dist/integrations/websocket-redis.d.mts +2 -1
  113. package/dist/integrations/websocket-redis.mjs +1 -0
  114. package/dist/integrations/websocket.d.mts +3 -2
  115. package/dist/integrations/websocket.mjs +38 -34
  116. package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
  117. package/dist/{interface-n5hpKg1d.mjs → interface-BP4IJCBO.mjs} +1 -0
  118. package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
  119. package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-DDSR2HFl.mjs} +2 -1
  120. package/dist/{keys-Cd316u_y.mjs → keys-B-HBTGXl.mjs} +1 -0
  121. package/dist/{loadResources-DpU8pfjV.mjs → loadResources-DisEAmkm.mjs} +4 -2
  122. package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-D1MJp13f.mjs} +2 -1
  123. package/dist/logger/index.d.mts +1 -0
  124. package/dist/logger/index.mjs +1 -0
  125. package/dist/{mcpPlugin-nRzm5g2s.mjs → mcpPlugin-CpfVpKHE.mjs} +38 -38
  126. package/dist/{memory-CmnzXE5Z.mjs → memory-DsuWnj4T.mjs} +5 -4
  127. package/dist/{metrics-fdxI5mn_.mjs → metrics-DdQL8syd.mjs} +2 -1
  128. package/dist/middleware/index.d.mts +2 -1
  129. package/dist/middleware/index.mjs +3 -2
  130. package/dist/migrations/index.d.mts +1 -0
  131. package/dist/migrations/index.mjs +3 -2
  132. package/dist/module-Br81am6m.mjs +78 -0
  133. package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-BnkBLN7v.mjs} +3 -2
  134. package/dist/{openapi-B8Vkbxbn.mjs → openapi-DIRzvxC0.mjs} +6 -5
  135. package/dist/outbox-admin/index.d.mts +85 -0
  136. package/dist/outbox-admin/index.mjs +115 -0
  137. package/dist/permissions/index.d.mts +5 -4
  138. package/dist/permissions/index.mjs +6 -5
  139. package/dist/{permissions-Bfu_8y1U.mjs → permissions-Dl_m0NCe.mjs} +308 -42
  140. package/dist/{pipe-BOLmGIxa.mjs → pipe-ELHCP3zj.mjs} +3 -4
  141. package/dist/pipeline/index.d.mts +2 -1
  142. package/dist/pipeline/index.mjs +2 -1
  143. package/dist/{pipeline-BJnpkY9i.mjs → pipeline-CPA-jx0M.mjs} +17 -14
  144. package/dist/plugins/index.d.mts +8 -7
  145. package/dist/plugins/index.mjs +16 -15
  146. package/dist/plugins/response-cache.d.mts +1 -0
  147. package/dist/plugins/response-cache.mjs +2 -1
  148. package/dist/plugins/tracing-entry.d.mts +2 -1
  149. package/dist/plugins/tracing-entry.mjs +2 -1
  150. package/dist/{pluralize-BhrgwZb9.mjs → pluralize-C2ZkKbsn.mjs} +1 -0
  151. package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-CDs87fTw.mjs} +1 -0
  152. package/dist/{preloadResources-DCfa2egz.d.mts → preloadResources-T1hmlTEZ.d.mts} +117 -12
  153. package/dist/presets/filesUpload.d.mts +6 -5
  154. package/dist/presets/filesUpload.mjs +10 -12
  155. package/dist/presets/index.d.mts +3 -2
  156. package/dist/presets/index.mjs +2 -1
  157. package/dist/presets/multiTenant.d.mts +2 -1
  158. package/dist/presets/multiTenant.mjs +3 -2
  159. package/dist/presets/search.d.mts +3 -2
  160. package/dist/presets/search.mjs +2 -1
  161. package/dist/{presets-CJOEXLuk.mjs → presets-Dntd_pUO.mjs} +6 -4
  162. package/dist/{projection-UBDgfMwu.mjs → projection-CjHhM5W1.mjs} +2 -1
  163. package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
  164. package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
  165. package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-Dhps-Ldt.mjs} +6 -5
  166. package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-C7mmAGs3.mjs} +7 -3
  167. package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-CccEFBfk.mjs} +1 -0
  168. package/dist/{realtime-B9nmIY5V.mjs → realtime-DVvVZZFz.mjs} +20 -9
  169. package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
  170. package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
  171. package/dist/{registerRoute-JkxIEBJq.mjs → registerRoute-B_QrjYZS.mjs} +10 -7
  172. package/dist/registry/index.d.mts +3 -2
  173. package/dist/registry/index.mjs +3 -2
  174. package/dist/{registry-BYXUUg21.mjs → registry-DqUmzA9W.mjs} +2 -1
  175. package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-BpP9wwj7.mjs} +1 -0
  176. package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DS-P0b32.mjs} +2 -1
  177. package/dist/{repositoryFilter-Dz8k3S4P.mjs → repositoryFilter-DotlxG3L.mjs} +1 -0
  178. package/dist/{requestContext-MrlJwECf.mjs → requestContext-B6vQF-sy.mjs} +1 -0
  179. package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-BhflRrBs.mjs} +1 -0
  180. package/dist/{schedules-CjOd2MG9.mjs → schedules-C2Q3uv2d.mjs} +3 -2
  181. package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-Crjvczi_.mjs} +70 -17
  182. package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-CnyKK_1j.mjs} +2 -1
  183. package/dist/schemas/index.d.mts +1 -0
  184. package/dist/schemas/index.mjs +1 -0
  185. package/dist/scim/index.d.mts +1 -0
  186. package/dist/scim/index.mjs +2 -1
  187. package/dist/scope/index.d.mts +3 -2
  188. package/dist/scope/index.mjs +5 -4
  189. package/dist/{scope-DfaPntpo.mjs → scope-afz_Ieek.mjs} +4 -3
  190. package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
  191. package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-C4KiMWUa.mjs} +1 -0
  192. package/dist/{sse-BQ1w0dfl.mjs → sse-B9P4Y-UI.mjs} +7 -5
  193. package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B3zb_d1h.mjs} +2 -1
  194. package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
  195. package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-DmEMHlPQ.mjs} +1 -0
  196. package/dist/{streaming-BQcW6_Kq.mjs → streaming-DPZdQmDz.mjs} +1 -0
  197. package/dist/{subscribe-helpers-DfzsivM6.mjs → subscribe-helpers-sp5xTDVA.mjs} +2 -1
  198. package/dist/sync/changelog.d.mts +1 -0
  199. package/dist/sync/changelog.mjs +1 -0
  200. package/dist/testing/cleanupStoreContract.d.mts +2 -1
  201. package/dist/testing/cleanupStoreContract.mjs +10 -5
  202. package/dist/testing/index.d.mts +166 -12
  203. package/dist/testing/index.mjs +315 -43
  204. package/dist/testing/outboxStoreContract.d.mts +12 -2
  205. package/dist/testing/outboxStoreContract.mjs +110 -20
  206. package/dist/testing/storageContract.d.mts +2 -1
  207. package/dist/testing/storageContract.mjs +11 -5
  208. package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
  209. package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-BvLKvOIL.mjs} +1 -0
  210. package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B_5-hKjJ.mjs} +1 -0
  211. package/dist/types/index.d.mts +6 -5
  212. package/dist/types/index.mjs +1 -0
  213. package/dist/types/storage.d.mts +2 -1
  214. package/dist/types/storage.mjs +1 -0
  215. package/dist/{types-ChvvVNP8.d.mts → types-B7XeLEgK.d.mts} +34 -2
  216. package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
  217. package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
  218. package/dist/{types-D_n8L6VY.d.mts → types-CT5vu6M0.d.mts} +132 -17
  219. package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
  220. package/dist/{types-Bmmn-fO0.d.mts → types-CwcUQ610.d.mts} +2 -6
  221. package/dist/{types-CSyCxl2V.mjs → types-DXYX7dIR.mjs} +4 -3
  222. package/dist/usage/index.d.mts +3 -2
  223. package/dist/usage/index.mjs +0 -0
  224. package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-BNsOXzOd.mjs} +1 -0
  225. package/dist/utils/index.d.mts +16 -7
  226. package/dist/utils/index.mjs +10 -9
  227. package/dist/{utils-taUVD4p3.mjs → utils-BtOha_Sp.mjs} +3 -2
  228. package/dist/{validate-Bk44F2yT.mjs → validate-PMCPR9El.mjs} +2 -1
  229. package/dist/{versioning-gKWyryT3.mjs → versioning-CKv2jz_U.mjs} +4 -3
  230. package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
  231. package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
  232. package/package.json +557 -539
  233. package/skills/arc/SKILL.md +22 -8
  234. package/skills/arc/references/api-reference.md +4 -0
  235. package/skills/arc/references/auth.md +3 -0
  236. package/skills/arc/references/integrations.md +33 -0
  237. package/skills/arc/references/mcp.md +6 -7
  238. package/skills/arc/references/production.md +24 -25
  239. package/skills/arc-code-review/references/anti-patterns.md +3 -3
  240. package/dist/fastify-augmentation-0WhPnUH7.d.mts +0 -76
  241. package/dist/index-CCzVR-N3.d.mts +0 -107
  242. package/dist/module-_-M7C5ES.mjs +0 -29
@@ -1,5 +1,6 @@
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
2
  import { OutboxOwnershipError } from "@classytic/primitives/outbox";
2
- import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
4
  //#region src/testing/outboxStoreContract.ts
4
5
  let seq = 0;
5
6
  /** A minimal well-formed event. Ids are unique per call. */
@@ -29,9 +30,16 @@ function runOutboxStoreContract(name, setup) {
29
30
  afterAll(async () => {
30
31
  if (teardown) await teardown();
31
32
  });
33
+ /**
34
+ * Isolation for EVERY test, not just the ones that call `seed()`. Resetting inside
35
+ * `seed()` left any test that asserts on an empty store — or that only reads —
36
+ * running against its predecessor's rows.
37
+ */
38
+ beforeEach(async () => {
39
+ await reset();
40
+ });
32
41
  /** Save n events, oldest first. */
33
42
  async function seed(n, type) {
34
- if (reset) await reset();
35
43
  const events = [];
36
44
  for (let i = 0; i < n; i++) {
37
45
  const e = event(type);
@@ -43,10 +51,10 @@ function runOutboxStoreContract(name, setup) {
43
51
  describe("save — rejects malformed events (invariant 1)", () => {
44
52
  it("persists a well-formed event", async () => {
45
53
  const [e] = await seed(1);
46
- expect((await store.getPending(10)).map(idOf)).toContain(idOf(e));
54
+ const pending = await store.getPending(10);
55
+ expect(pending.map(idOf)).toContain(idOf(e));
47
56
  });
48
57
  it("THROWS rather than persisting an event with no `type`", async () => {
49
- if (reset) await reset();
50
58
  await expect(store.save({
51
59
  payload: {},
52
60
  meta: { id: "no-type" }
@@ -54,7 +62,6 @@ function runOutboxStoreContract(name, setup) {
54
62
  expect((await store.getPending(10)).length).toBe(0);
55
63
  });
56
64
  it("THROWS rather than persisting an event with no `meta.id`", async () => {
57
- if (reset) await reset();
58
65
  await expect(store.save({
59
66
  type: "x",
60
67
  payload: {},
@@ -66,7 +73,8 @@ function runOutboxStoreContract(name, setup) {
66
73
  describe("getPending", () => {
67
74
  it("returns events FIFO (oldest first)", async () => {
68
75
  const saved = await seed(3);
69
- expect((await store.getPending(10)).slice(0, 3).map(idOf)).toEqual(saved.map(idOf));
76
+ const pending = await store.getPending(10);
77
+ expect(pending.slice(0, 3).map(idOf)).toEqual(saved.map(idOf));
70
78
  });
71
79
  it("honours the limit", async () => {
72
80
  await seed(3);
@@ -85,10 +93,11 @@ function runOutboxStoreContract(name, setup) {
85
93
  it("claims pending events", async ({ skip }) => {
86
94
  if (!store.claimPending) return skip();
87
95
  await seed(2);
88
- expect((await store.claimPending({
96
+ const claimed = await store.claimPending({
89
97
  consumerId: "c1",
90
98
  limit: 10
91
- })).length).toBe(2);
99
+ });
100
+ expect(claimed.length).toBe(2);
92
101
  });
93
102
  it("two concurrent claimers NEVER receive the same event", async ({ skip }) => {
94
103
  if (!store.claimPending) return skip();
@@ -102,7 +111,8 @@ function runOutboxStoreContract(name, setup) {
102
111
  })]);
103
112
  const idsA = a.map(idOf);
104
113
  const idsB = b.map(idOf);
105
- expect(idsA.filter((id) => idsB.includes(id))).toEqual([]);
114
+ const overlap = idsA.filter((id) => idsB.includes(id));
115
+ expect(overlap).toEqual([]);
106
116
  expect(idsA.length + idsB.length).toBeLessThanOrEqual(6);
107
117
  });
108
118
  it("does not re-claim an event whose lease is still live", async ({ skip }) => {
@@ -113,10 +123,11 @@ function runOutboxStoreContract(name, setup) {
113
123
  limit: 10,
114
124
  leaseMs: 6e4
115
125
  });
116
- expect(await store.claimPending({
126
+ const second = await store.claimPending({
117
127
  consumerId: "c2",
118
128
  limit: 10
119
- })).toEqual([]);
129
+ });
130
+ expect(second).toEqual([]);
120
131
  });
121
132
  it("RECLAIMS an event whose lease expired (crashed worker)", async ({ skip }) => {
122
133
  if (!store.claimPending) return skip();
@@ -126,18 +137,20 @@ function runOutboxStoreContract(name, setup) {
126
137
  limit: 10,
127
138
  leaseMs: 0
128
139
  });
129
- expect((await store.claimPending({
140
+ const reclaimed = await store.claimPending({
130
141
  consumerId: "c2",
131
142
  limit: 10
132
- })).length).toBe(1);
143
+ });
144
+ expect(reclaimed.length).toBe(1);
133
145
  });
134
146
  it("claims FIFO", async ({ skip }) => {
135
147
  if (!store.claimPending) return skip();
136
148
  const saved = await seed(3);
137
- expect((await store.claimPending({
149
+ const claimed = await store.claimPending({
138
150
  consumerId: "c1",
139
151
  limit: 2
140
- })).map(idOf)).toEqual(saved.slice(0, 2).map(idOf));
152
+ });
153
+ expect(claimed.map(idOf)).toEqual(saved.slice(0, 2).map(idOf));
141
154
  });
142
155
  });
143
156
  describe("acknowledge", () => {
@@ -147,7 +160,6 @@ function runOutboxStoreContract(name, setup) {
147
160
  expect((await store.getPending(10)).map(idOf)).not.toContain(idOf(e));
148
161
  });
149
162
  it("is a NO-OP for an unknown id — never an ownership error (invariant 4)", async () => {
150
- if (reset) await reset();
151
163
  await expect(store.acknowledge("does-not-exist")).resolves.not.toThrow();
152
164
  });
153
165
  it("THROWS OutboxOwnershipError when consumerId is not the lease owner (invariant 3)", async ({ skip }) => {
@@ -184,10 +196,11 @@ function runOutboxStoreContract(name, setup) {
184
196
  });
185
197
  if (!claimed) return skip();
186
198
  await store.fail(idOf(claimed), { message: "boom" });
187
- expect((await store.claimPending({
199
+ const again = await store.claimPending({
188
200
  consumerId: "c2",
189
201
  limit: 10
190
- })).map(idOf)).toContain(idOf(claimed));
202
+ });
203
+ expect(again.map(idOf)).toContain(idOf(claimed));
191
204
  });
192
205
  it("THROWS OutboxOwnershipError on a consumerId mismatch (invariant 3)", async ({ skip }) => {
193
206
  if (!store.fail || !store.claimPending) return skip();
@@ -210,10 +223,11 @@ function runOutboxStoreContract(name, setup) {
210
223
  });
211
224
  if (!claimed) return skip();
212
225
  await store.fail(idOf(claimed), { message: "boom" }, { retryAt: new Date(Date.now() + 6e4) });
213
- expect((await store.claimPending({
226
+ const again = await store.claimPending({
214
227
  consumerId: "c2",
215
228
  limit: 10
216
- })).map(idOf)).not.toContain(idOf(claimed));
229
+ });
230
+ expect(again.map(idOf)).not.toContain(idOf(claimed));
217
231
  });
218
232
  it("`deadLetter: true` removes it from the pending stream", async ({ skip }) => {
219
233
  if (!store.fail) return skip();
@@ -241,6 +255,82 @@ function runOutboxStoreContract(name, setup) {
241
255
  expect((await store.getPending(10)).length).toBe(2);
242
256
  });
243
257
  });
258
+ /**
259
+ * `requeue`, `countByStatus` and `oldestPendingAgeMs` were a HOST's three extra
260
+ * methods before they were contract members — which is why they are pinned here.
261
+ * A dead-letter row an operator cannot re-drive, or a health number that lies, fails
262
+ * silently: the operator concludes the queue is fine, and the whole point of the
263
+ * table is that somebody eventually looks.
264
+ */
265
+ describe("requeue", () => {
266
+ it("returns a dead-lettered event to pending and clears the attempt count", async ({ skip }) => {
267
+ if (!store.fail || !store.requeue) return skip();
268
+ const [e] = await seed(1);
269
+ const id = idOf(e);
270
+ await store.fail(id, { message: "fatal" }, { deadLetter: true });
271
+ expect((await store.getPending(10)).length).toBe(0);
272
+ expect(await store.requeue(id)).toBe(true);
273
+ const pending = await store.getPending(10);
274
+ expect(pending.length).toBe(1);
275
+ if (store.countByStatus) expect(await store.countByStatus("dead_letter")).toBe(0);
276
+ });
277
+ it("REFUSES a pending event — requeue is dead-letter-scoped", async ({ skip }) => {
278
+ if (!store.requeue) return skip();
279
+ const [e] = await seed(1);
280
+ expect(await store.requeue(idOf(e))).toBe(false);
281
+ });
282
+ it("is FALSE, not an error, for an unknown id", async ({ skip }) => {
283
+ if (!store.requeue) return skip();
284
+ expect(await store.requeue("no-such-event")).toBe(false);
285
+ });
286
+ });
287
+ describe("countByStatus", () => {
288
+ it("counts pending, and moves the count as events are delivered", async ({ skip }) => {
289
+ if (!store.countByStatus) return skip();
290
+ await seed(3);
291
+ expect(await store.countByStatus("pending")).toBe(3);
292
+ if (!store.claimPending) return skip();
293
+ const [claimed] = await store.claimPending({
294
+ consumerId: "c",
295
+ limit: 1,
296
+ leaseMs: 6e4
297
+ });
298
+ if (!claimed) return skip();
299
+ await store.acknowledge(idOf(claimed), { consumerId: "c" });
300
+ expect(await store.countByStatus("pending")).toBe(2);
301
+ expect(await store.countByStatus("delivered")).toBe(1);
302
+ });
303
+ it("counts dead-lettered rows — the number an alert fires on", async ({ skip }) => {
304
+ if (!store.countByStatus || !store.fail) return skip();
305
+ expect(await store.countByStatus("dead_letter")).toBe(0);
306
+ const [e] = await seed(1);
307
+ await store.fail(idOf(e), { message: "fatal" }, { deadLetter: true });
308
+ expect(await store.countByStatus("dead_letter")).toBe(1);
309
+ });
310
+ });
311
+ describe("oldestPendingAgeMs", () => {
312
+ it("is null on an empty queue, and a non-negative age once something pends", async ({ skip }) => {
313
+ if (!store.oldestPendingAgeMs) return skip();
314
+ expect(await store.oldestPendingAgeMs()).toBeNull();
315
+ await seed(1);
316
+ const age = await store.oldestPendingAgeMs();
317
+ expect(age).not.toBeNull();
318
+ expect(age ?? -1).toBeGreaterThanOrEqual(0);
319
+ });
320
+ it("ignores DELIVERED rows — it answers 'how long undelivered', not 'how old'", async ({ skip }) => {
321
+ if (!store.oldestPendingAgeMs) return skip();
322
+ if (!store.claimPending) return skip();
323
+ await seed(1);
324
+ const [claimed] = await store.claimPending({
325
+ consumerId: "c",
326
+ limit: 1,
327
+ leaseMs: 6e4
328
+ });
329
+ if (!claimed) return skip();
330
+ await store.acknowledge(idOf(claimed), { consumerId: "c" });
331
+ expect(await store.oldestPendingAgeMs()).toBeNull();
332
+ });
333
+ });
244
334
  });
245
335
  }
246
336
  //#endregion
@@ -1,4 +1,5 @@
1
- import { t as Storage } from "../storage-DLUZ04kK.mjs";
1
+
2
+ import { t as Storage } from "../storage-Cx9Sz3Ou.mjs";
2
3
  //#region src/testing/storageContract.d.ts
3
4
  interface StorageContractSetupResult {
4
5
  storage: Storage;
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
2
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
3
  //#region src/testing/storageContract.ts
3
4
  /**
@@ -88,7 +89,8 @@ function runStorageContract(name, setup) {
88
89
  size: bytes.length
89
90
  }, EMPTY_CTX);
90
91
  const read = await storage.read(file.id, EMPTY_CTX);
91
- expect((await readAll(read)).equals(bytes)).toBe(true);
92
+ const actual = await readAll(read);
93
+ expect(actual.equals(bytes)).toBe(true);
92
94
  expect(read.contentType).toBe("application/octet-stream");
93
95
  await storage.delete(file.id, EMPTY_CTX);
94
96
  });
@@ -100,7 +102,8 @@ function runStorageContract(name, setup) {
100
102
  mimeType: "application/octet-stream",
101
103
  size: bytes.length
102
104
  }, EMPTY_CTX);
103
- expect(await storage.delete(file.id, EMPTY_CTX)).toBe(true);
105
+ const first = await storage.delete(file.id, EMPTY_CTX);
106
+ expect(first).toBe(true);
104
107
  let second = "threw";
105
108
  try {
106
109
  second = await storage.delete(file.id, EMPTY_CTX);
@@ -168,8 +171,10 @@ function runStorageContract(name, setup) {
168
171
  mimeType: "application/octet-stream",
169
172
  size: bytes.length
170
173
  }, EMPTY_CTX);
171
- expect((await readAll(await storage.read(file.id, EMPTY_CTX))).equals(bytes)).toBe(true);
172
- expect(await storage.delete(file.id, EMPTY_CTX)).toBe(true);
174
+ const before = await readAll(await storage.read(file.id, EMPTY_CTX));
175
+ expect(before.equals(bytes)).toBe(true);
176
+ const removed = await storage.delete(file.id, EMPTY_CTX);
177
+ expect(removed).toBe(true);
173
178
  let rejected = false;
174
179
  try {
175
180
  if (!(await readAll(await storage.read(file.id, EMPTY_CTX))).equals(bytes)) rejected = true;
@@ -188,7 +193,8 @@ function runStorageContract(name, setup) {
188
193
  }, EMPTY_CTX);
189
194
  const result = await storage.read(file.id, EMPTY_CTX);
190
195
  expect(result.kind === "stream" || result.kind === "buffer").toBe(true);
191
- expect((await readAll(result)).equals(bytes)).toBe(true);
196
+ const actual = await readAll(result);
197
+ expect(actual.equals(bytes)).toBe(true);
192
198
  await storage.delete(file.id, EMPTY_CTX);
193
199
  });
194
200
  it("read() with a mid-object range slices correctly (when adapter supports ranges)", async () => {
@@ -1,3 +1,4 @@
1
+
1
2
  import { FastifyInstance, FastifyRequest } from "fastify";
2
3
  import { Span, Tracer } from "@opentelemetry/api";
3
4
  //#region src/plugins/tracing.d.ts
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
2
  import { AsyncLocalStorage } from "node:async_hooks";
2
3
  //#region src/context/transactionContext.ts
3
4
  /**
@@ -1,3 +1,4 @@
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
2
  //#region src/utils/typeGuards.ts
2
3
  /** Check if fastify has the events plugin registered */
3
4
  function hasEvents(instance) {
@@ -1,5 +1,6 @@
1
- import { $ as OpenApiSchemas, A as RequestIdOptions, At as RouteDefinition, Bt as RouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as AuthenticatorContext, H as CrudController, Ht as AuthHelpers, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, K as ResolvedTenantPurge, Kt as JwtContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthPluginOptions, W as ResourcePermissions, Wt as Authenticator, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kn as BaseControllerOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as TokenPair, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-Q5gzEIWY.mjs";
2
- import { i as RequestScope } from "../types-DjC-KUCz.mjs";
3
- import { a as UserBase, d as JWTPayload, f as ObjectId, i as PermissionContext, l as AnyRecord, m as UserOrganization, n as DataPolicy, p as UserLike, r as PermissionCheck, t as AuthorizationDecision, u as ArcRequest } from "../types-ChvvVNP8.mjs";
4
- import { n as ElevationOptions, t as ElevationEvent } from "../elevation-C9s5hFP_.mjs";
5
- export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, DataPolicy, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
1
+
2
+ import { $ as OpenApiSchemas, A as RequestIdOptions, An as BaseControllerOptions, At as RouteDefinition, Bt as RawRouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as Authenticator, H as CrudController, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, Jt as TokenPair, K as ResolvedTenantPurge, Kt as AuthenticatorContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthHelpers, Vt as RouteHandler, W as ResourcePermissions, Wt as AuthPluginOptions, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as JwtContext, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-CpzxqePp.mjs";
3
+ import { i as RequestScope } from "../types-ChuShpCP.mjs";
4
+ import { a as UserBase, d as JWTPayload, f as ObjectId, i as PermissionContext, l as AnyRecord, m as UserOrganization, n as DataPolicy, p as UserLike, r as PermissionCheck, t as AuthorizationDecision, u as ArcRequest } from "../types-B7XeLEgK.mjs";
5
+ import { n as ElevationOptions, t as ElevationEvent } from "../elevation-BXCfVcNG.mjs";
6
+ export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, DataPolicy, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RawRouteHandler, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
@@ -1 +1,2 @@
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
2
  export {};
@@ -1,2 +1,3 @@
1
- import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-DLUZ04kK.mjs";
1
+
2
+ import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-Cx9Sz3Ou.mjs";
2
3
  export { Storage, StorageContext, StorageFile, StorageReadRange, StorageReadResult, StorageUploadInput };
@@ -1 +1,2 @@
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
1
2
  export {};
@@ -1,4 +1,5 @@
1
- import { i as RequestScope } from "./types-DjC-KUCz.mjs";
1
+
2
+ import { i as RequestScope } from "./types-ChuShpCP.mjs";
2
3
  import { FastifyRequest } from "fastify";
3
4
  //#region src/types/base.d.ts
4
5
  declare module "fastify" {
@@ -53,7 +54,7 @@ interface JWTPayload {
53
54
  [key: string]: unknown;
54
55
  }
55
56
  /**
56
- * Typed Fastify request with Arc decorations. Use in `raw: true` handlers
57
+ * Typed Fastify request with Arc decorations. Use in `rawHandler` routes
57
58
  * instead of `(req as any).user`.
58
59
  *
59
60
  * @example
@@ -241,8 +242,27 @@ type PermissionCheck<TDoc = Record<string, unknown>> = ((context: PermissionCont
241
242
  interface PermissionCheckMeta {
242
243
  /** Set by allowPublic() — marks the endpoint as publicly accessible */
243
244
  _isPublic?: boolean;
245
+ /**
246
+ * Set by requireAuth() — a pure identity gate (must be signed in) that adds NO
247
+ * row-level or environmental condition. Marked so `allOf` does not mistake it
248
+ * for an opaque runtime branch and needlessly taint the composite `conditional`.
249
+ */
250
+ _requiresAuth?: boolean;
244
251
  /** Set by requireRoles() — the roles required for access */
245
252
  _roles?: readonly string[];
253
+ /**
254
+ * Set by `requirePlatformRole()` — this gate consults PLATFORM roles only and
255
+ * can never be satisfied by an organization role.
256
+ *
257
+ * It exists because that property is otherwise unprovable from outside a
258
+ * check. `requireRoles(["ops"])` accepts an ORG role named `ops` by default,
259
+ * and `requireOrgRole("manager")` grants any org's manager — both return a
260
+ * bare allow with no policy, so a surface that must be platform-wide cannot
261
+ * tell them apart from a genuine operator gate by inspecting the decision.
262
+ * A global surface (see `jobsPlugin`'s `managementRoutes`) requires this
263
+ * marker at boot rather than trusting the caller's intent.
264
+ */
265
+ _platformOnly?: boolean;
246
266
  /** Set by requireOrgMembership() — org-level permission type */
247
267
  _orgPermission?: string;
248
268
  /** Set by requireOrgRole() — the org roles required for access */
@@ -290,6 +310,18 @@ interface PermissionCheckMeta {
290
310
  scopes?: readonly string[];
291
311
  dpop: boolean;
292
312
  };
313
+ /**
314
+ * Set by `allOf(...)` when its surfaced role/scope meta is NECESSARY but NOT
315
+ * SUFFICIENT — the conjunction also contains an opaque runtime branch
316
+ * (ownership / custom / dynamic / flow-mode / quota …) or a contradictory
317
+ * constraint that flat metadata can't capture. `describePermission` propagates
318
+ * it as `conditional: true`, so `explainAccess` reports a role/scope MATCH as
319
+ * `conditional` (server decides) instead of a definitive — and unsound —
320
+ * `allow`. A role/scope MISMATCH is still a definitive `deny` (a necessary
321
+ * condition failed). Without this flag a composed gate could tell a UI "you can"
322
+ * when the runtime check will refuse.
323
+ */
324
+ _conditional?: boolean;
293
325
  }
294
326
  //#endregion
295
327
  export { UserBase as a, normalizeRoles as c, JWTPayload as d, ObjectId as f, PermissionContext as i, AnyRecord as l, UserOrganization as m, DataPolicy as n, getUserId as o, UserLike as p, PermissionCheck as r, getUserRoles as s, AuthorizationDecision as t, ArcRequest as u };
@@ -1,3 +1,4 @@
1
+
1
2
  import { FastifyRequest } from "fastify";
2
3
  //#region src/encryption/types.d.ts
3
4
  /** JWE key-management algorithm — the protected-header `alg`. */
@@ -1,3 +1,4 @@
1
+
1
2
  import { PurgeActor, PurgeEvidence, PurgeResourceResult, PurgeStrategyKind as PurgeStrategyKind$1, PurgeVerificationSummary } from "@classytic/primitives/retention";
2
3
  //#region src/cleanup/types.d.ts
3
4
  /** Ambient context every recipe method receives. Host-injected — never global. */
@@ -1,5 +1,6 @@
1
- import { n as DomainEvent, r as EventHandler } from "./EventTransport-QmY7A258.mjs";
2
- import { n as ErrorMapper } from "./errorHandler-C6yv4ZTW.mjs";
1
+
2
+ import { n as DomainEvent, r as EventHandler } from "./EventTransport-2uVV34WU.mjs";
3
+ import { n as ErrorMapper } from "./errorHandler-CpYUJmPA.mjs";
3
4
  import { FastifyInstance, FastifyPluginAsync } from "fastify";
4
5
  import { LockAdapter } from "@classytic/repo-core/lock";
5
6
  //#region src/plugins/health.d.ts
@@ -319,6 +320,26 @@ interface LoadResourcesOptions<TContext = unknown> {
319
320
  */
320
321
  declare function loadResources<TContext = unknown>(dir: string, options?: LoadResourcesOptions<TContext>): Promise<ResourceLike[]>;
321
322
  //#endregion
323
+ //#region src/factory/module/disposers.d.ts
324
+ /** Teardown callback registered by a setup phase via `defer`. */
325
+ type ModuleDisposer = () => void | Promise<void>;
326
+ /**
327
+ * Second argument to a module's setup phases (`plugins`, `bootstrap`,
328
+ * `afterResources`).
329
+ */
330
+ interface ModuleSetupContext {
331
+ /**
332
+ * Register a teardown callback for the resource just acquired. Call it
333
+ * IMMEDIATELY after acquisition — that is what makes partial-init teardown
334
+ * exact rather than defensive.
335
+ *
336
+ * Disposers run LIFO, after this module's `onClose`, on both the rollback
337
+ * and shutdown paths, and exactly once. A throwing disposer is logged and
338
+ * never blocks the ones behind it.
339
+ */
340
+ defer(disposer: ModuleDisposer): void;
341
+ }
342
+ //#endregion
322
343
  //#region src/factory/module/types.d.ts
323
344
  /**
324
345
  * A module's contribution of some arm (event handlers, scheduled jobs, …).
@@ -409,6 +430,13 @@ interface ArcModule<TExports = unknown> {
409
430
  * *importing* a dependent until its dependency is ready. Use a thunk to gate
410
431
  * whether a module is imported at all (region/tier packs); use `dependsOn`
411
432
  * to order modules that are already selected.
433
+ *
434
+ * **Convention: every HARD sibling dependency belongs here** — including
435
+ * ones read lazily. `lazyRequiredModuleExports` defers the export READ to
436
+ * first use (and eagerly validates the module is composed), but only
437
+ * `dependsOn` orders composition so the sibling's engine exists before
438
+ * yours initializes. Omit the edge only for genuinely optional
439
+ * integrations (`lazyModuleExports` / `getOptionalModuleExports`).
412
440
  */
413
441
  readonly dependsOn?: readonly string[];
414
442
  /**
@@ -422,8 +450,18 @@ interface ArcModule<TExports = unknown> {
422
450
  * published ecosystem packages: `plugins` = "register infra"; `bootstrap` =
423
451
  * "initialise engines (and return the public export)". Registering a plugin
424
452
  * inside `bootstrap` still works — this slot just makes the two distinct.
453
+ *
454
+ * Receives `{ defer }` to register teardown for anything it acquires, and
455
+ * may RETURN a single disposer as shorthand for the one-resource case:
456
+ *
457
+ * ```ts
458
+ * plugins: async (fastify) => {
459
+ * const conn = await connect();
460
+ * return () => conn.close(); // equivalent to defer(...)
461
+ * },
462
+ * ```
425
463
  */
426
- plugins?: (fastify: FastifyInstance) => void | Promise<void>;
464
+ plugins?: (fastify: FastifyInstance, context: ModuleSetupContext) => ModuleDisposer | undefined | Promise<ModuleDisposer | undefined>;
427
465
  /**
428
466
  * Domain error mappers this module ships (see `defineErrorMapper`). Merged
429
467
  * into the app's error handler at boot, AFTER any host-declared mappers —
@@ -460,8 +498,23 @@ interface ArcModule<TExports = unknown> {
460
498
  * // arc-invoice bootstrap, composed AFTER arc-accounting:
461
499
  * const acct = getModuleExports<AccountingEngine>(fastify, "accounting");
462
500
  * ```
501
+ *
502
+ * The return value is the module's export, so teardown here goes through
503
+ * `defer` (the second argument) rather than a returned disposer — register
504
+ * each resource the moment it exists and partial-init cleanup becomes exact
505
+ * instead of a chain of `?.` guards:
506
+ *
507
+ * ```ts
508
+ * bootstrap: async (fastify, { defer }) => {
509
+ * const client = await openClient();
510
+ * defer(() => client.close());
511
+ * const engine = await createEngine(client); // if this throws, only
512
+ * defer(() => engine.stop()); // the client is released
513
+ * return engine;
514
+ * },
515
+ * ```
463
516
  */
464
- bootstrap?: (fastify: FastifyInstance) => TExports | Promise<TExports>;
517
+ bootstrap?: (fastify: FastifyInstance, context: ModuleSetupContext) => TExports | Promise<TExports>;
465
518
  /**
466
519
  * App-level resource NAMES this module authoritatively SUPERSEDES. When the
467
520
  * host also lists (or `resourceDir`-discovers) a resource of the same name,
@@ -476,17 +529,42 @@ interface ArcModule<TExports = unknown> {
476
529
  * Semantics:
477
530
  * - Purely a supersede-the-app-fork switch — it filters the *app* resource
478
531
  * list, never the module's own `resources` (those always register).
479
- * - Static + declarative: read at compose time, so it works regardless of
480
- * whether `resources` is an array or a post-bootstrap factory (no early
481
- * factory resolution, no boot-order coupling).
482
- * - Idempotent + tolerant: an `owns` entry with no matching app resource is
483
- * a silent no-op — a module may pre-declare a name before any fork exists.
532
+ * - Idempotent + tolerant on the APP side: an `owns` entry with no matching
533
+ * app resource is a silent no-op — a module may pre-declare a name before
534
+ * any fork exists.
484
535
  * - Collected across ALL modules; the app-side supersession is their union.
485
536
  *
486
- * Contract: if you `owns` a name, PROVIDE it (in this module's `resources`).
487
- * Dropping the app fork without supplying a replacement removes the route.
537
+ * **Prefer `owns: "provided"`** (2.32) — arc derives the list from the
538
+ * resources this module actually returned, so the claim and the reality
539
+ * cannot disagree:
540
+ *
541
+ * ```ts
542
+ * defineModule({ name: "order", owns: "provided", resources: () => [...] })
543
+ * ```
544
+ *
545
+ * That is the whole arm for most modules. Resources are resolved exactly
546
+ * once, names are validated (duplicates within a module are rejected), the
547
+ * effective `owns` is derived, and supersession runs afterward — one atomic
548
+ * phase arc owns, rather than a rule each module re-states.
549
+ *
550
+ * **The explicit-array form is ENFORCED at boot: if you `owns` a name,
551
+ * PROVIDE it** — in THIS module's own `resources`. Arc fails boot when a
552
+ * claimed name is not among them, naming the module, the unmet names, and
553
+ * what it did supply. Without that check, a typo or a resource deleted
554
+ * without updating the list booted "successfully" with the app's route
555
+ * silently removed and nothing serving it. Two real cases, both silent 404s:
556
+ * a POS module claiming a checkout resource it mounted only when the host
557
+ * supplied a pipeline, and a device gateway claiming a name it never
558
+ * provided at all.
559
+ *
560
+ * Keep the explicit array only when the list is genuinely NOT the module's
561
+ * own resource set — e.g. pre-declaring a name before the fork exists.
562
+ *
563
+ * A sibling module supplying the name does NOT satisfy the claim — ownership
564
+ * is local to the module that declares it, which is the colocation this arm
565
+ * exists for.
488
566
  */
489
- readonly owns?: readonly string[];
567
+ readonly owns?: readonly string[] | "provided";
490
568
  /**
491
569
  * The module's resources. Array or factory — the factory form runs AFTER all
492
570
  * bootstraps, so `defineResource(...)` receives fully-booted engines. Flows
@@ -498,8 +576,12 @@ interface ArcModule<TExports = unknown> {
498
576
  * Post-registration wiring — runs in the `afterResources` phase, once this
499
577
  * module's routes are mounted. Use for cross-resource event subscriptions the
500
578
  * module owns.
579
+ *
580
+ * Receives `{ defer }`, and may return a disposer, on the same terms as
581
+ * `plugins` — handy for wiring that must be unwound (a subscription, a
582
+ * listener) without hoisting a handle into module scope.
501
583
  */
502
- afterResources?: (fastify: FastifyInstance) => void | Promise<void>;
584
+ afterResources?: (fastify: FastifyInstance, context: ModuleSetupContext) => ModuleDisposer | undefined | Promise<ModuleDisposer | undefined>;
503
585
  /**
504
586
  * Readiness checks this module contributes to the app's
505
587
  * `healthPlugin` (`/_health/ready`). Collected across ALL modules in
@@ -569,9 +651,42 @@ interface ArcModule<TExports = unknown> {
569
651
  */
570
652
  readonly scheduledJobs?: ModuleContribution<ScheduleDefinition>;
571
653
  /**
572
- * Teardown — registered as a Fastify `onClose` hook. Destroy engines, stop
573
- * timers, flush the module's outbox. Modules close in REVERSE list order
574
- * (last composed, first closed), mirroring init.
654
+ * Teardown — destroy engines, stop timers, flush the module's outbox.
655
+ * Modules close in REVERSE composition order (last composed, first closed),
656
+ * mirroring init, BEFORE the app-level `onClose` and before infra plugins'
657
+ * own close hooks (so teardown still sees a live DB/Redis).
658
+ *
659
+ * Boot is transactional: if any phase fails mid-boot — including THIS
660
+ * module's own `plugins`/`bootstrap` — arc immediately closes every module
661
+ * that entered an init phase, this closer included, in reverse order, then
662
+ * rethrows the original boot error. A closer runs AT MOST ONCE across the
663
+ * rollback and shutdown paths, and one throwing closer never blocks the
664
+ * rest (best-effort; failures are logged).
665
+ *
666
+ * Because rollback fires after a PARTIAL init — your `bootstrap` opened a
667
+ * client and threw on the next line, or your `plugins` ran but `bootstrap`
668
+ * never did — write closers defensively. Guard everything init may not have
669
+ * reached: `client?.close()`, not `client.close()`.
670
+ *
671
+ * ```ts
672
+ * let client: Client | undefined;
673
+ * defineModule({
674
+ * name: "billing",
675
+ * bootstrap: async () => {
676
+ * client = await openClient(); // allocated…
677
+ * await migrate(client); // …and this may throw
678
+ * },
679
+ * onClose: async () => { await client?.close(); }, // still runs
680
+ * });
681
+ * ```
682
+ *
683
+ * Prefer `defer` (the setup phases' second argument) for anything acquired
684
+ * step-by-step: it registers each teardown at the point of acquisition, so
685
+ * the runtime knows exactly what was reached and the `?.` guards above
686
+ * disappear. `onClose` remains the right slot for tearing down what
687
+ * `bootstrap` RETURNED — the module's engine — and runs FIRST, ahead of the
688
+ * module's deferred disposers (see `disposers.ts` for the ordering rule).
689
+ * The two compose; either alone is complete.
575
690
  */
576
691
  onClose?: (fastify: FastifyInstance) => void | Promise<void>;
577
692
  }
@@ -618,4 +733,4 @@ type ArcModuleInput<TExports = unknown> = ArcModule<TExports> | Promise<ArcModul
618
733
  */
619
734
  interface ArcModuleRegistry {}
620
735
  //#endregion
621
- export { _default$1 as _, ModuleContribution as a, ResourceModule as c, ScheduleStats as d, SchedulesPluginOptions as f, HealthOptions as g, HealthCheck as h, EventHandlerDefinition as i, loadResources as l, schedulesPlugin as m, ArcModuleInput as n, LoadResourcesOptions as o, _default as p, ArcModuleRegistry as r, ResourceLike as s, ArcModule as t, ScheduleDefinition as u, healthPlugin as v };
736
+ 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 };
@@ -1,3 +1,4 @@
1
+
1
2
  //#region src/utils/headers.d.ts
2
3
  /**
3
4
  * Canonical header typing + accessor.