@classytic/arc 2.38.0 → 2.40.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 (183) hide show
  1. package/dist/{BaseController-CYaDvmOB.mjs → BaseController-BqJtbiUA.mjs} +107 -23
  2. package/dist/{EventTransport-Cx60_s-6.d.mts → EventTransport-BPOQsEer.d.mts} +8 -0
  3. package/dist/{EventTransport-Gq7JWbWw.mjs → EventTransport-BUkTwY_x.mjs} +13 -2
  4. package/dist/{HookSystem-C0ikzqQf.mjs → HookSystem-96nzZLmk.mjs} +2 -2
  5. package/dist/{ResourceRegistry-DPcX--zk.mjs → ResourceRegistry-B9WnuG2Q.mjs} +2 -2
  6. package/dist/{actionPermissions-jlFZ1it4.mjs → actionPermissions-4OGbe3Hf.mjs} +6 -6
  7. package/dist/audit/index.d.mts +1 -1
  8. package/dist/audit/index.mjs +2 -2
  9. package/dist/auth/audit.mjs +1 -1
  10. package/dist/auth/index.d.mts +1 -1
  11. package/dist/auth/index.mjs +6 -6
  12. package/dist/auth/redis-session.mjs +1 -1
  13. package/dist/{authorizationDecision-BP8GHtjc.mjs → authorizationDecision-D_LFvMlm.mjs} +2 -2
  14. package/dist/{buildHandler-DjXOqIvX.mjs → buildHandler-BG_CHXqQ.mjs} +4 -4
  15. package/dist/cache/index.mjs +4 -4
  16. package/dist/{caching-a3ZEcJJs.mjs → caching-Bqcn3Brl.mjs} +2 -2
  17. package/dist/cleanup/index.d.mts +2 -2
  18. package/dist/cleanup/index.mjs +21 -5
  19. package/dist/cli/commands/describe.d.mts +1 -1
  20. package/dist/cli/commands/describe.mjs +3 -3
  21. package/dist/cli/commands/docs.mjs +3 -3
  22. package/dist/cli/commands/doctor.mjs +1 -1
  23. package/dist/cli/commands/generate.mjs +2 -2
  24. package/dist/cli/commands/init.mjs +2 -2
  25. package/dist/cli/commands/introspect.mjs +2 -2
  26. package/dist/cli/index.mjs +3 -3
  27. package/dist/{constants-BONghE1Y.mjs → constants-BQ2FbhPB.mjs} +1 -1
  28. package/dist/context/index.d.mts +34 -6
  29. package/dist/context/index.mjs +12 -19
  30. package/dist/core/index.d.mts +2 -2
  31. package/dist/core/index.mjs +7 -7
  32. package/dist/{core-8She7FgF.mjs → core-CsZR037F.mjs} +3 -3
  33. package/dist/{createActionRouter-Bqi6Bhhx.mjs → createActionRouter-BR93gCKg.mjs} +7 -7
  34. package/dist/{createAggregationRouter-BsYO7ktd.mjs → createAggregationRouter-CF40Aw95.mjs} +7 -7
  35. package/dist/createMcpServer-BVTs2S5G.d.mts +48 -0
  36. package/dist/{defineAggregation-0KBvqEEC.mjs → defineAggregation-BDA2Tw7v.mjs} +1 -1
  37. package/dist/{defineResource-BMdyRelW.mjs → defineResource-DaIySrQr.mjs} +20 -22
  38. package/dist/discovery/index.mjs +1 -1
  39. package/dist/docs/index.d.mts +1 -1
  40. package/dist/docs/index.mjs +3 -3
  41. package/dist/{elevation-C6woysGC.mjs → elevation-CZy6n50b.mjs} +4 -4
  42. package/dist/encryption/index.mjs +3 -3
  43. package/dist/{errorHandler-DI5AN2b5.d.mts → errorHandler--4rSMMvq.d.mts} +7 -1
  44. package/dist/{errorHandler-BdmIWPba.mjs → errorHandler-By9keRvX.mjs} +3 -3
  45. package/dist/{errors-2ExmOgIJ.mjs → errors-BHD3L8of.mjs} +1 -1
  46. package/dist/{eventPlugin-DJwpGMzx.mjs → eventPlugin-DB26V7uX.mjs} +5 -5
  47. package/dist/{eventPlugin-DFvHa4Nf.d.mts → eventPlugin-DfHaKEWQ.d.mts} +1 -1
  48. package/dist/events/index.d.mts +4 -4
  49. package/dist/events/index.mjs +5 -5
  50. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  51. package/dist/events/transports/redis-stream-entry.mjs +21 -20
  52. package/dist/events/transports/redis.d.mts +1 -1
  53. package/dist/events/transports/redis.mjs +11 -8
  54. package/dist/factory/index.d.mts +4 -4
  55. package/dist/factory/index.mjs +5 -5
  56. package/dist/{fastify-augmentation-jYA2oHyL.d.mts → fastify-augmentation-HncZMmze.d.mts} +3 -3
  57. package/dist/{fields-Cha2QCKH.mjs → fields-fA-Dkou5.mjs} +1 -1
  58. package/dist/{generate-BUP4Oydg.mjs → generate-BSSdtiOy.mjs} +4 -3
  59. package/dist/{headers-DeXNifVj.mjs → headers-BFEEshDS.mjs} +2 -2
  60. package/dist/{health-Bh0N_FLk.mjs → health-BlGHjkqZ.mjs} +1 -1
  61. package/dist/hooks/index.d.mts +1 -1
  62. package/dist/hooks/index.mjs +2 -2
  63. package/dist/idempotency/index.mjs +4 -4
  64. package/dist/idempotency/redis.mjs +1 -1
  65. package/dist/{index-B78WZom8.d.mts → index-D3sIPcD4.d.mts} +47 -11
  66. package/dist/{index-D2uIUSQk.d.mts → index-DbMglURO.d.mts} +2 -2
  67. package/dist/{index-BRoDtjek.d.mts → index-SCsOJxWB.d.mts} +2 -2
  68. package/dist/index.d.mts +3 -3
  69. package/dist/index.mjs +11 -11
  70. package/dist/{init-Dg_Am2o_.mjs → init-pl88PHzb.mjs} +9 -9
  71. package/dist/integrations/event-gateway.d.mts +1 -1
  72. package/dist/integrations/event-gateway.mjs +2 -2
  73. package/dist/integrations/index.d.mts +1 -1
  74. package/dist/integrations/index.mjs +1 -1
  75. package/dist/integrations/jobs/index.mjs +2 -2
  76. package/dist/integrations/mcp/index.d.mts +3 -46
  77. package/dist/integrations/mcp/index.mjs +2 -2
  78. package/dist/integrations/mcp/testing.d.mts +22 -2
  79. package/dist/integrations/mcp/testing.mjs +55 -33
  80. package/dist/integrations/streamline.mjs +3 -3
  81. package/dist/integrations/webhooks.d.mts +2 -2
  82. package/dist/integrations/webhooks.mjs +3 -3
  83. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  84. package/dist/integrations/websocket-redis.mjs +1 -1
  85. package/dist/integrations/websocket.mjs +13 -6
  86. package/dist/{interface-DYPVscCU.mjs → interface-DjtFnZxH.mjs} +1 -1
  87. package/dist/{jsonBody-DVui0uJl.mjs → jsonBody-Dx6jH_Nb.mjs} +2 -2
  88. package/dist/{keys-BSuxGS-B.mjs → keys-Ds3ekBiu.mjs} +1 -1
  89. package/dist/{loadResources-CNd1DWnK.mjs → loadResources-E5UFlRrT.mjs} +2 -2
  90. package/dist/{loadResourcesFromEntry-DNj8cpiY.mjs → loadResourcesFromEntry-C2jrJbWG.mjs} +2 -2
  91. package/dist/logger/index.mjs +1 -1
  92. package/dist/{mcpPlugin-KWsezMx7.mjs → mcpPlugin-D-3OLxNz.mjs} +75 -31
  93. package/dist/{memory-BiPVaCPg.mjs → memory-CNvjxzB2.mjs} +2 -2
  94. package/dist/{metrics-CsWaIVTa.mjs → metrics-P37sNV5H.mjs} +2 -2
  95. package/dist/middleware/index.d.mts +1 -1
  96. package/dist/middleware/index.mjs +3 -3
  97. package/dist/migrations/index.mjs +2 -2
  98. package/dist/{module-BQXHxMEk.mjs → module-CNcwCKUJ.mjs} +1 -1
  99. package/dist/{multipartBody-u6VrS_iH.mjs → multipartBody-Bl_gU3Wg.mjs} +1 -1
  100. package/dist/{openapi-Dxc8RLy_.mjs → openapi-BYrL8zvY.mjs} +6 -6
  101. package/dist/outbox-admin/index.d.mts +1 -1
  102. package/dist/outbox-admin/index.mjs +4 -4
  103. package/dist/{outbox-module-DyIqA5vo.mjs → outbox-module-6DMC_WNu.mjs} +40 -21
  104. package/dist/{outbox-module-CQXu_O6o.d.mts → outbox-module-CzsKRBvO.d.mts} +11 -2
  105. package/dist/permissions/index.mjs +5 -5
  106. package/dist/{permissions-H7Zcb82Z.mjs → permissions-C0GpDsB_.mjs} +6 -6
  107. package/dist/{pipe-SunhEaZ6.mjs → pipe-4_XamfxI.mjs} +2 -2
  108. package/dist/pipeline/index.d.mts +1 -1
  109. package/dist/pipeline/index.mjs +2 -2
  110. package/dist/plugins/index.d.mts +5 -5
  111. package/dist/plugins/index.mjs +17 -16
  112. package/dist/plugins/response-cache.mjs +2 -2
  113. package/dist/plugins/tracing-entry.mjs +2 -2
  114. package/dist/{pluralize-Da7IT_qJ.mjs → pluralize-BaQ62hOW.mjs} +1 -1
  115. package/dist/{preloadResources-DlhH81py.mjs → preloadResources-BAx5s9EF.mjs} +70 -21
  116. package/dist/{preloadResources-BbBkNkqK.d.mts → preloadResources-DVgPCNcD.d.mts} +7 -7
  117. package/dist/presets/filesUpload.d.mts +1 -1
  118. package/dist/presets/filesUpload.mjs +5 -5
  119. package/dist/presets/index.d.mts +1 -1
  120. package/dist/presets/index.mjs +2 -2
  121. package/dist/presets/multiTenant.d.mts +1 -1
  122. package/dist/presets/multiTenant.mjs +3 -3
  123. package/dist/presets/search.d.mts +1 -1
  124. package/dist/presets/search.mjs +2 -2
  125. package/dist/{presets-CNyfaxGL.mjs → presets-CAaqVVcW.mjs} +4 -4
  126. package/dist/{projection-DSfEoooA.mjs → projection-qbCOEvsh.mjs} +2 -2
  127. package/dist/{queryCachePlugin-oTsmVyEQ.mjs → queryCachePlugin-2Pp9w-8D.mjs} +5 -5
  128. package/dist/rateLimitKey-umrS6-vu.mjs +41 -0
  129. package/dist/{realtime-CPmjjgqd.mjs → realtime-zzoNLDtj.mjs} +9 -9
  130. package/dist/{redis-stream-eh0eOtjh.d.mts → redis-stream-DSsPyYzd.d.mts} +7 -2
  131. package/dist/{registerRoute-XvcI_3sh.mjs → registerRoute-_6zHiz0x.mjs} +6 -6
  132. package/dist/registry/index.d.mts +2 -2
  133. package/dist/registry/index.mjs +3 -3
  134. package/dist/{registry-UFEJQN8R.mjs → registry-CEpoRpLP.mjs} +2 -2
  135. package/dist/{renewingLease-DuRGVOMe.mjs → renewingLease-B5Grm0pI.mjs} +1 -1
  136. package/dist/{replyHelpers-BHebarf0.mjs → replyHelpers-9h-a592u.mjs} +2 -2
  137. package/dist/{requestContext-DpmntRZy.mjs → requestContext-D4PuGhEw.mjs} +1 -1
  138. package/dist/{rolldown-runtime-Add2L50I.mjs → rolldown-runtime-DzE6ikSH.mjs} +1 -1
  139. package/dist/{runtimeCapabilities-D793Pxci.mjs → runtimeCapabilities-Bo7rSKjm.mjs} +1 -1
  140. package/dist/{schedules-DthM8Qag.mjs → schedules-Dm21pqHU.mjs} +8 -4
  141. package/dist/{schemaConverter-Db-MMsH6.mjs → schemaConverter-DENbixyA.mjs} +1 -1
  142. package/dist/{schemaIR-CIXShch7.mjs → schemaIR-C2qUNzC6.mjs} +2 -2
  143. package/dist/schemas/index.mjs +1 -1
  144. package/dist/scim/index.mjs +2 -2
  145. package/dist/scope/index.mjs +5 -5
  146. package/dist/{scope-D03sk17n.mjs → scope-CdYxQVxt.mjs} +4 -4
  147. package/dist/{simpleEqualityMatcher-B7qVjQBv.mjs → simpleEqualityMatcher-Cldubvul.mjs} +1 -1
  148. package/dist/{sse-D4MYqkic.mjs → sse-DbyXoQun.mjs} +5 -5
  149. package/dist/{sseStream-BuMNPR6r.mjs → sseStream-BfZtL5-m.mjs} +2 -2
  150. package/dist/{store-helpers-CIF4RG8s.mjs → store-helpers-BfyOd5i6.mjs} +1 -1
  151. package/dist/{streaming-DkpW1HwJ.mjs → streaming-CFwCzEvU.mjs} +1 -1
  152. package/dist/{subscribe-helpers-CBZOi6pk.mjs → subscribe-helpers-KgVEcn8L.mjs} +2 -2
  153. package/dist/sync/changelog.mjs +1 -1
  154. package/dist/testing/cleanupStoreContract.mjs +1 -1
  155. package/dist/testing/index.d.mts +4 -4
  156. package/dist/testing/index.mjs +10 -10
  157. package/dist/testing/outboxStoreContract.mjs +14 -3
  158. package/dist/testing/storageContract.mjs +1 -1
  159. package/dist/{transactionContext-DMmvcfdo.mjs → transactionContext-46KyUADP.mjs} +1 -1
  160. package/dist/{typeGuards-DHTnEs5X.mjs → typeGuards-t_CVgRXN.mjs} +1 -1
  161. package/dist/types/index.d.mts +1 -1
  162. package/dist/types/index.mjs +1 -1
  163. package/dist/types/storage.mjs +1 -1
  164. package/dist/{types-LQFwC37d.d.mts → types-9rvxn5Ym.d.mts} +46 -1
  165. package/dist/{types-BRU-Kd3t.d.mts → types-BVlowlm7.d.mts} +2 -2
  166. package/dist/{types-BqLyyH0o.mjs → types-CNQyOrLk.mjs} +4 -4
  167. package/dist/usage/index.mjs +0 -0
  168. package/dist/{userHelpers-CXySQFRg.mjs → userHelpers-DLueLQdQ.mjs} +1 -1
  169. package/dist/utils/index.d.mts +4 -4
  170. package/dist/utils/index.mjs +11 -11
  171. package/dist/{utils-BbGG8XNT.mjs → utils-CUFBrhq-.mjs} +14 -6
  172. package/dist/{validate-CidSeUrx.mjs → validate-DV3hRbsz.mjs} +2 -2
  173. package/dist/{versioning-C2r0eOZ4.mjs → versioning-BXwmj5SD.mjs} +3 -3
  174. package/dist/{versioning-B843jOYX.d.mts → versioning-P3V6ATyC.d.mts} +1 -1
  175. package/dist/workScope-CoGpuzDX.mjs +42 -0
  176. package/package.json +48 -39
  177. package/skills/arc/references/mcp.md +20 -0
  178. package/skills/arc/references/testing.md +0 -12
  179. package/skills/arc-code-review/references/anti-patterns.md +1 -1
  180. package/skills/arc-code-review/references/migration-recipes.md +0 -60
  181. package/skills/arc-code-review/references/mongokit-migration.md +0 -27
  182. package/skills/arc-module-publishing/references/package-manifest.md +3 -1
  183. package/dist/rateLimitKey-CewdR-m6.mjs +0 -15
@@ -1,15 +1,15 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { h as SYSTEM_FIELDS, o as DEFAULT_TENANT_FIELD } from "./constants-BONghE1Y.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { h as SYSTEM_FIELDS, o as DEFAULT_TENANT_FIELD } from "./constants-BQ2FbhPB.mjs";
3
3
  import { arcLog } from "./logger/index.mjs";
4
- import { a as conjoinPolicyFilters } from "./authorizationDecision-BP8GHtjc.mjs";
5
- import { d as createDomainError, f as createError, i as NotFoundError, p as isArcError, r as ForbiddenError } from "./errors-2ExmOgIJ.mjs";
6
- import { n as getUserRoles, t as getUserId } from "./userHelpers-CXySQFRg.mjs";
7
- import { b as isElevated, c as getOrgId, n as PUBLIC_SCOPE, x as isMember } from "./types-BqLyyH0o.mjs";
8
- import { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-B7qVjQBv.mjs";
9
- import { t as buildQueryKey } from "./keys-BSuxGS-B.mjs";
10
- import { t as transactionContext } from "./transactionContext-DMmvcfdo.mjs";
11
- import { A as scheduleBackground, E as ArcQueryParser } from "./utils-BbGG8XNT.mjs";
12
- import { a as resolveEffectiveRoles, n as applyFieldWritePermissions } from "./fields-Cha2QCKH.mjs";
4
+ import { a as conjoinPolicyFilters } from "./authorizationDecision-D_LFvMlm.mjs";
5
+ import { d as createDomainError, f as createError, i as NotFoundError, p as isArcError, r as ForbiddenError, u as ValidationError } from "./errors-BHD3L8of.mjs";
6
+ import { n as getUserRoles, t as getUserId } from "./userHelpers-DLueLQdQ.mjs";
7
+ import { b as isElevated, c as getOrgId, n as PUBLIC_SCOPE, x as isMember } from "./types-CNQyOrLk.mjs";
8
+ import { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-Cldubvul.mjs";
9
+ import { t as buildQueryKey } from "./keys-Ds3ekBiu.mjs";
10
+ import { t as transactionContext } from "./transactionContext-46KyUADP.mjs";
11
+ import { A as scheduleBackground, E as ArcQueryParser } from "./utils-CUFBrhq-.mjs";
12
+ import { a as resolveEffectiveRoles, n as applyFieldWritePermissions } from "./fields-fA-Dkou5.mjs";
13
13
  import { policyRecordToFilter } from "@classytic/repo-core/filter";
14
14
  import { retryingTransaction } from "@classytic/repo-core/repository";
15
15
  import { isProductionEnv } from "@classytic/primitives/environment";
@@ -650,7 +650,7 @@ async function executeAfterHook(ctx, op, data, meta) {
650
650
  * Every mixin used to spell out its own narrowing cast inline:
651
651
  *
652
652
  * ```ts
653
- * const repo = this.repository as unknown as {
653
+ * const repo = this.repository as {
654
654
  * getTree?: (options?: unknown) => Promise<AnyRecord[]>;
655
655
  * };
656
656
  * ```
@@ -717,7 +717,7 @@ async function runDistinctVerb(repository, field, resolveScope) {
717
717
  const repo = withRepoFeatures(repository);
718
718
  if (typeof repo.distinct !== "function") throw createError(501, "_distinct is not supported: the resource's storage adapter does not implement repo.distinct()");
719
719
  const { filter, options } = resolveScope();
720
- return await repo.distinct(field, filter, options);
720
+ return { values: await repo.distinct(field, filter, options) };
721
721
  }
722
722
  /**
723
723
  * `?_exists=true` → `repo.exists(filter)`. Returns the RAW repo result —
@@ -789,6 +789,27 @@ function noteDeniedJoins(kind) {
789
789
  notedDeniedJoins = true;
790
790
  log.warn(`client requested ${kind} but no allowlist is configured — DENIED (2.24 default flip; it was allow-all). Set schemaOptions.query.allowedPopulate / allowedLookups to enable client-driven joins. (dev-only note, shown once)`);
791
791
  }
792
+ /**
793
+ * A `$lookup` reaches ANOTHER collection, and the tenant filter arc conjoins applies to the
794
+ * BASE one only. The joined side carries whatever predicate the lookup itself names and
795
+ * nothing more — no tenant scope, no soft-delete filter — so a resource that opens
796
+ * `allowedLookups` can return rows from a collection the caller could not query directly.
797
+ *
798
+ * Unreachable by default (2.24 denies `lookup=` with no allowlist) and no resource in this
799
+ * fleet configures one, so this is a WARNING at the moment a deployment first opts in
800
+ * rather than machinery for a door nobody has opened. Warned once per collection set: the
801
+ * decision is made when the allowlist is written, not per request.
802
+ *
803
+ * Scope the join with the lookup's own `where`, which mongokit compiles into a `$match`
804
+ * inside the lookup pipeline.
805
+ */
806
+ const notedUnscopedJoins = /* @__PURE__ */ new Set();
807
+ function noteUnscopedJoin(lookups) {
808
+ const key = lookups.map((l) => l.from).filter((f) => typeof f === "string").sort().join(",");
809
+ if (key.length === 0 || notedUnscopedJoins.has(key)) return;
810
+ notedUnscopedJoins.add(key);
811
+ log.warn(`client-driven $lookup on [${key}] — the JOINED side carries no tenant or soft-delete filter (arc scopes the base collection only). Narrow each lookup with its own \`where\`, or the join can surface rows the caller cannot query directly. (shown once)`);
812
+ }
792
813
  var QueryResolver = class {
793
814
  queryParser;
794
815
  maxLimit;
@@ -912,6 +933,12 @@ var QueryResolver = class {
912
933
  query = rest;
913
934
  }
914
935
  const parsed = this.queryParser.parse(query);
936
+ /**
937
+ * Client-supplied keys only, and BEFORE the policy conjunction below: trusted policy
938
+ * legitimately filters on hidden fields (`requireOwnership('userId')` emits exactly that),
939
+ * so checking the conjoined result would reject the framework's own restrictions.
940
+ */
941
+ this.assertNoBlockedReadKeys(parsed.filters, parsed.sort);
915
942
  const arcContext = meta ?? req.metadata;
916
943
  const limit = Math.min(Math.max(1, parsed.limit || this.defaultLimit), this.maxLimit);
917
944
  const page = parsed.after ? void 0 : parsed.page ? Math.max(1, parsed.page) : 1;
@@ -950,20 +977,14 @@ var QueryResolver = class {
950
977
  if (!select) return exclusion();
951
978
  if (typeof select === "object" && !Array.isArray(select)) {
952
979
  const sanitized = {};
953
- for (const [field, val] of Object.entries(select)) if (!blockedFields.includes(field)) sanitized[field] = val;
980
+ for (const [field, val] of Object.entries(select)) if (!this.isBlockedProjection(field, blockedFields)) sanitized[field] = val;
954
981
  return Object.keys(sanitized).length > 0 ? sanitized : exclusion();
955
982
  }
956
983
  if (Array.isArray(select)) {
957
- const sanitized = select.filter((f) => {
958
- const fieldName = f.replace(/^-/, "");
959
- return !blockedFields.includes(fieldName);
960
- });
984
+ const sanitized = select.filter((f) => !this.isBlockedProjection(f, blockedFields));
961
985
  return sanitized.length > 0 ? sanitized : exclusion();
962
986
  }
963
- const sanitized = select.split(/[\s,]+/).filter(Boolean).filter((f) => {
964
- const fieldName = f.replace(/^-/, "");
965
- return !blockedFields.includes(fieldName);
966
- });
987
+ const sanitized = select.split(/[\s,]+/).filter(Boolean).filter((f) => !this.isBlockedProjection(f, blockedFields));
967
988
  return sanitized.length > 0 ? sanitized.join(" ") : exclusion();
968
989
  }
969
990
  /** Sanitize populate fields */
@@ -1011,6 +1032,7 @@ var QueryResolver = class {
1011
1032
  if (!allowedLookups.includes(lookup.from)) return false;
1012
1033
  return true;
1013
1034
  });
1035
+ if (sanitized.length > 0) noteUnscopedJoin(sanitized);
1014
1036
  return sanitized.length > 0 ? sanitized : void 0;
1015
1037
  }
1016
1038
  /**
@@ -1023,6 +1045,68 @@ var QueryResolver = class {
1023
1045
  const blocked = collectReadBlockedFields(schemaOptions);
1024
1046
  return blocked ? Array.from(blocked) : [];
1025
1047
  }
1048
+ /**
1049
+ * Is this projection path blocked — by its own name, or by a hidden ANCESTOR?
1050
+ *
1051
+ * `hidden: { secret: true }` hides the object, and an exact-name match left its children
1052
+ * reachable: `?select=secret.value` projected the very field the rule exists to withhold.
1053
+ * A rule on a parent covers everything under it, which is what "hidden" already means to
1054
+ * whoever wrote it.
1055
+ */
1056
+ isBlockedProjection(path, blockedFields) {
1057
+ const field = path.replace(/^-/, "");
1058
+ return blockedFields.some((blocked) => field === blocked || field.startsWith(`${blocked}.`));
1059
+ }
1060
+ /**
1061
+ * Refuse a read that FILTERS or SORTS on a `hidden` field.
1062
+ *
1063
+ * `hidden` was enforced on the surfaces that RETURN a value — the response, `select=`,
1064
+ * `_distinct`, aggregations — but a filter key never returns the field, it INTERROGATES it:
1065
+ * `?passwordHash[like]=^ab` narrows the result set, so the row count answers a question about
1066
+ * a value the caller may not read. A resource with no `allowedFilterFields` was a blind
1067
+ * existence oracle over every hidden field it declared.
1068
+ *
1069
+ * Rejected rather than dropped, unlike `select`. Dropping a projection narrows what comes
1070
+ * back; dropping a filter WIDENS the rows, which is the silent-permissive shape this
1071
+ * framework fails loudly on everywhere else.
1072
+ *
1073
+ * Compound keys (`$or`, `$and`) are walked, because the offending key can be nested inside
1074
+ * one. A dotted path is blocked by its own name or by a hidden ancestor, so a rule on
1075
+ * `secret` also covers `secret.value`.
1076
+ */
1077
+ assertNoBlockedReadKeys(filters, sort) {
1078
+ const blocked = collectReadBlockedFields(this.schemaOptions);
1079
+ if (!blocked) return;
1080
+ const offenders = /* @__PURE__ */ new Set();
1081
+ const isBlocked = (key) => {
1082
+ if (blocked.has(key)) return true;
1083
+ const dot = key.indexOf(".");
1084
+ return dot > 0 && blocked.has(key.slice(0, dot));
1085
+ };
1086
+ const walk = (node, depth) => {
1087
+ if (depth > 8 || !node || typeof node !== "object") return;
1088
+ if (Array.isArray(node)) {
1089
+ for (const item of node) walk(item, depth + 1);
1090
+ return;
1091
+ }
1092
+ for (const [key, value] of Object.entries(node)) {
1093
+ if (key.startsWith("$")) {
1094
+ walk(value, depth + 1);
1095
+ continue;
1096
+ }
1097
+ if (isBlocked(key)) offenders.add(key);
1098
+ }
1099
+ };
1100
+ walk(filters, 0);
1101
+ const sortKeys = typeof sort === "string" ? sort.split(",").map((s) => s.trim().replace(/^-/, "")).filter(Boolean) : Object.keys(sort ?? {});
1102
+ for (const key of sortKeys) if (isBlocked(key)) offenders.add(key);
1103
+ if (offenders.size === 0) return;
1104
+ const fields = [...offenders];
1105
+ throw new ValidationError(`Cannot filter or sort on hidden field(s): ${fields.join(", ")}`, fields.map((field) => ({
1106
+ field,
1107
+ message: "Field is not readable"
1108
+ })));
1109
+ }
1026
1110
  };
1027
1111
  //#endregion
1028
1112
  //#region src/core/BaseCrudController.ts
@@ -1550,7 +1634,7 @@ var BaseCrudController = class {
1550
1634
  }
1551
1635
  async list(req) {
1552
1636
  const dispatch = this.dispatchResourceVerb(req);
1553
- if (dispatch) return dispatch;
1637
+ if (dispatch !== null) return dispatch;
1554
1638
  const options = this.queryResolver.resolve(req, this.meta(req));
1555
1639
  const cached = await this.withListCache(req, options);
1556
1640
  if (cached) return cached;
@@ -70,7 +70,15 @@ declare class MemoryEventTransport implements EventTransport {
70
70
  private handlerDispatch;
71
71
  private handlerConcurrency;
72
72
  constructor(options?: MemoryEventTransportOptions);
73
+ /**
74
+ * One work scope per publish, around the WHOLE handler set — so every
75
+ * handler of this event, parallel or not, shares one `requestScopedCache()`
76
+ * and one correlation id. Inherits an active request scope (a live
77
+ * in-request dispatch keeps the request's reads); a relay- or job-driven
78
+ * publish gets its own.
79
+ */
73
80
  publish(event: DomainEvent): Promise<void>;
81
+ private dispatch;
74
82
  /**
75
83
  * Reference `publishMany` implementation — delegates to `publish()` in order.
76
84
  *
@@ -1,4 +1,5 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { r as workSeedFromEvent, t as runWorkScope } from "./workScope-CoGpuzDX.mjs";
2
3
  import { createEvent, matchEventPattern } from "@classytic/primitives/events";
3
4
  //#region src/events/EventTransport.ts
4
5
  /**
@@ -89,7 +90,17 @@ var MemoryEventTransport = class {
89
90
  if (cap !== void 0 && (!Number.isInteger(cap) || cap < 1)) throw new TypeError(`MemoryEventTransport: handlerConcurrency must be a positive integer, received ${String(cap)}`);
90
91
  this.handlerConcurrency = cap ?? Number.POSITIVE_INFINITY;
91
92
  }
92
- async publish(event) {
93
+ /**
94
+ * One work scope per publish, around the WHOLE handler set — so every
95
+ * handler of this event, parallel or not, shares one `requestScopedCache()`
96
+ * and one correlation id. Inherits an active request scope (a live
97
+ * in-request dispatch keeps the request's reads); a relay- or job-driven
98
+ * publish gets its own.
99
+ */
100
+ publish(event) {
101
+ return runWorkScope(workSeedFromEvent(event), () => this.dispatch(event));
102
+ }
103
+ async dispatch(event) {
93
104
  const allHandlers = /* @__PURE__ */ new Set();
94
105
  for (const [pattern, handlers] of this.handlers) if (matchEventPattern(pattern, event.type)) for (const h of handlers) allHandlers.add(h);
95
106
  const failures = [];
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as buildRequestScopeProjection } from "./projection-DSfEoooA.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as buildRequestScopeProjection } from "./projection-qbCOEvsh.mjs";
3
3
  //#region src/hooks/HookSystem.ts
4
4
  /**
5
5
  * Hook System
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import "./constants-BONghE1Y.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import "./constants-BQ2FbhPB.mjs";
3
3
  //#region src/registry/ResourceRegistry.ts
4
4
  /**
5
5
  * Resource Registry
@@ -1,9 +1,9 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { n as getUserRoles } from "./userHelpers-CXySQFRg.mjs";
3
- import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-BqLyyH0o.mjs";
4
- import { a as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-Cha2QCKH.mjs";
5
- import { t as buildRequestScopeProjection } from "./projection-DSfEoooA.mjs";
6
- import { t as executePipeline } from "./pipe-SunhEaZ6.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { n as getUserRoles } from "./userHelpers-DLueLQdQ.mjs";
3
+ import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-CNQyOrLk.mjs";
4
+ import { a as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-fA-Dkou5.mjs";
5
+ import { t as buildRequestScopeProjection } from "./projection-qbCOEvsh.mjs";
6
+ import { t as executePipeline } from "./pipe-4_XamfxI.mjs";
7
7
  import { isPaginatedResult, toCanonicalList } from "@classytic/repo-core/pagination";
8
8
  //#region src/core/fastifyAdapter.ts
9
9
  /** Type guard for Mongoose-like documents with toObject() */
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { d as UserBase } from "../base-C-NL2fTi.mjs";
3
- import "../index-B78WZom8.mjs";
3
+ import "../index-D3sIPcD4.mjs";
4
4
  import { FastifyPluginAsync } from "fastify";
5
5
  import { RepositoryLike } from "@classytic/repo-core/adapter";
6
6
  //#region src/audit/stores/interface.d.ts
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as transactionContext } from "../transactionContext-DMmvcfdo.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as transactionContext } from "../transactionContext-46KyUADP.mjs";
3
3
  import { and, anyOf, eq, gte, lt, lte } from "@classytic/repo-core/filter";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/audit/repository-audit-adapter.ts
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
2
  //#region src/auth/audit.ts
3
3
  function compileGlobs(patterns) {
4
4
  if (patterns.length === 0) return () => false;
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { l as PermissionCheck } from "../base-C-NL2fTi.mjs";
3
- import { Ft as AuthPluginOptions, Pt as AuthHelpers } from "../index-B78WZom8.mjs";
3
+ import { Ft as AuthPluginOptions, Pt as AuthHelpers } from "../index-D3sIPcD4.mjs";
4
4
  import { t as ExternalOpenApiPaths } from "../externalPaths-CmGOxOtD.mjs";
5
5
  import { a as SessionManagerOptions, c as createSessionManager, i as SessionData, n as MemorySessionStoreOptions, o as SessionManagerResult, r as SessionCookieOptions, s as SessionStore, t as MemorySessionStore } from "../sessionManager-qimlKV0P.mjs";
6
6
  import { FastifyPluginAsync, FastifyReply as FastifyReply$1, FastifyRequest } from "fastify";
@@ -1,9 +1,9 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { d as createDomainError, l as UnauthorizedError, p as isArcError, t as ArcError } from "../errors-2ExmOgIJ.mjs";
3
- import { n as requireSingleHeaderValue } from "../headers-DeXNifVj.mjs";
4
- import { n as getUserRoles, r as normalizeRoles } from "../userHelpers-CXySQFRg.mjs";
5
- import { o as toJsonSchema } from "../schemaConverter-Db-MMsH6.mjs";
6
- import { E as requireOrgRole, T as requireOrgMembership, k as requireTeamMembership } from "../permissions-H7Zcb82Z.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { d as createDomainError, l as UnauthorizedError, p as isArcError, t as ArcError } from "../errors-BHD3L8of.mjs";
3
+ import { n as requireSingleHeaderValue } from "../headers-BFEEshDS.mjs";
4
+ import { n as getUserRoles, r as normalizeRoles } from "../userHelpers-DLueLQdQ.mjs";
5
+ import { o as toJsonSchema } from "../schemaConverter-DENbixyA.mjs";
6
+ import { E as requireOrgRole, T as requireOrgMembership, k as requireTeamMembership } from "../permissions-C0GpDsB_.mjs";
7
7
  import { isProductionEnv } from "@classytic/primitives/environment";
8
8
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
9
9
  import fp from "fastify-plugin";
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
2
  import sjson from "secure-json-parse";
3
3
  //#region src/auth/redis-session.ts
4
4
  /**
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { t as ArcError } from "./errors-2ExmOgIJ.mjs";
3
+ import { t as ArcError } from "./errors-BHD3L8of.mjs";
4
4
  //#region src/permissions/filter-merge.ts
5
5
  /**
6
6
  * Policy-filter composition — logical AND, never last-writer-wins.
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { a as conjoinPolicyFilters } from "./authorizationDecision-BP8GHtjc.mjs";
3
- import { g as toRepositoryFilter } from "./BaseController-CYaDvmOB.mjs";
4
- import { i as compileAggRequest, t as adapterSupportsAggregate } from "./validate-CidSeUrx.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { a as conjoinPolicyFilters } from "./authorizationDecision-D_LFvMlm.mjs";
3
+ import { g as toRepositoryFilter } from "./BaseController-BqJtbiUA.mjs";
4
+ import { i as compileAggRequest, t as adapterSupportsAggregate } from "./validate-DV3hRbsz.mjs";
5
5
  //#region src/core/aggregation/buildHandler.ts
6
6
  /**
7
7
  * Framework-agnostic aggregation execution. Runs safety guards,
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-BSuxGS-B.mjs";
3
- import { t as MemoryCacheStore } from "../memory-BiPVaCPg.mjs";
4
- import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-oTsmVyEQ.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-Ds3ekBiu.mjs";
3
+ import { t as MemoryCacheStore } from "../memory-CNvjxzB2.mjs";
4
+ import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-2Pp9w-8D.mjs";
5
5
  //#region src/cache/redis.ts
6
6
  /**
7
7
  * Redis-backed cache store.
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-Add2L50I.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DzE6ikSH.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/caching.ts
5
5
  var caching_exports = /* @__PURE__ */ __exportAll({
@@ -1,7 +1,7 @@
1
1
 
2
- import { L as RequestWithExtras } from "../index-B78WZom8.mjs";
2
+ import { L as RequestWithExtras } from "../index-D3sIPcD4.mjs";
3
3
  import { A as CleanupStepResult, B as VerificationResult, C as CleanupResult, D as CleanupRunStore, E as CleanupRunStatus, F as PurgeEvidence, I as PurgeResourceResult, L as PurgeStrategyKind, M as CleanupWriteFence, N as DEFAULT_CLEANUP_LIMITS, O as CleanupRunTransitionPatch, P as PurgeActor, R as PurgeVerificationSummary, S as CleanupRecipe, T as CleanupRunCreateResult, _ as CleanupPermissions, a as CleanupEvidenceStore, b as CleanupPlanItem, c as CleanupInput, d as CleanupLease, f as CleanupLimits, g as CleanupPermissionCheck, h as CleanupOutcomeStatus, i as CleanupContext, j as CleanupStepRunStatus, k as CleanupStepProgress, l as CleanupJob, m as CleanupManifest, n as CLEANUP_TERMINAL_STATUSES, o as CleanupExecutionContext, p as CleanupLogger, r as CleanupCancelRequest, s as CleanupFinalizationPayload, t as Availability, u as CleanupJobQueue, v as CleanupPlan, w as CleanupRun, x as CleanupProgressSummary, y as CleanupPlanDraft, z as VerificationCheck } from "../types-DVLddu2q.mjs";
4
- import { t as ArcModule } from "../types-BRU-Kd3t.mjs";
4
+ import { t as ArcModule } from "../types-BVlowlm7.mjs";
5
5
  import { PurgeStrategyKind as PurgeStrategyKind$1 } from "@classytic/primitives/retention";
6
6
  import { canonicalJson } from "@classytic/primitives/canonical";
7
7
  import { CleanupStep } from "@classytic/repo-core/cleanup";
@@ -1,7 +1,7 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { g as getUserId } from "../types-BqLyyH0o.mjs";
3
- import { t as defineResource } from "../defineResource-BMdyRelW.mjs";
4
- import { t as defineModule } from "../module-BQXHxMEk.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { g as getUserId } from "../types-CNQyOrLk.mjs";
3
+ import { t as defineResource } from "../defineResource-DaIySrQr.mjs";
4
+ import { t as defineModule } from "../module-CNcwCKUJ.mjs";
5
5
  import { createPurgeEvidence } from "@classytic/primitives/retention";
6
6
  import { canonicalJson, sha256Hex } from "@classytic/primitives/canonical";
7
7
  //#region src/cleanup/errors.ts
@@ -723,9 +723,25 @@ function createCleanupService(deps) {
723
723
  const logger = deps.logger;
724
724
  const generateId = deps.generateId ?? (() => globalThis.crypto.randomUUID());
725
725
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
726
+ /**
727
+ * Undefined-valued keys are DROPPED, not spread.
728
+ *
729
+ * `limits` is `Partial<CleanupLimits>`, which permits an EXPLICIT `undefined`
730
+ * — and a plain spread lets that overwrite the default rather than fall back
731
+ * to it. Every limit is then read in a comparison, and `n > undefined` is
732
+ * always `false`, so `{ limits: { maxPlanItems: undefined } }` did not raise
733
+ * the cap: it silently REMOVED it, on a framework whose whole job is deleting
734
+ * data. Same for `maxReasonLength` / `maxParamDepth`, and `maxResults` turned
735
+ * `slice(0, undefined)` into "keep everything".
736
+ *
737
+ * A host reaches this by forwarding an optional config through
738
+ * (`limits: { maxPlanItems: cfg.planCap }`) — no cast, no obvious mistake.
739
+ * `permissions/presets.ts` already filters for exactly this reason; this is
740
+ * the same rule applied to the destructive path.
741
+ */
726
742
  const limits = {
727
743
  ...DEFAULT_CLEANUP_LIMITS,
728
- ...deps.limits
744
+ ...Object.fromEntries(Object.entries(deps.limits ?? {}).filter(([, v]) => v !== void 0))
729
745
  };
730
746
  const leaseMs = deps.leaseMs ?? 3e5;
731
747
  const progressThrottleMs = deps.progressThrottleMs ?? 0;
@@ -1,5 +1,5 @@
1
1
 
2
- import { Nt as RateLimitConfig, V as ResourceDefinition, et as RouteSchemaOptions } from "../../index-B78WZom8.mjs";
2
+ import { Nt as RateLimitConfig, V as ResourceDefinition, et as RouteSchemaOptions } from "../../index-D3sIPcD4.mjs";
3
3
  //#region src/cli/commands/describe.d.ts
4
4
  interface DescribedResource {
5
5
  name: string;
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as CRUD_OPERATIONS } from "../../constants-BONghE1Y.mjs";
3
- import { n as stringifyMeasureMap } from "../../ResourceRegistry-DPcX--zk.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "../../constants-BQ2FbhPB.mjs";
3
+ import { n as stringifyMeasureMap } from "../../ResourceRegistry-B9WnuG2Q.mjs";
4
4
  import { resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  //#region src/cli/commands/describe.ts
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as buildOpenApiSpec } from "../../openapi-Dxc8RLy_.mjs";
3
- import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-DNj8cpiY.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as buildOpenApiSpec } from "../../openapi-BYrL8zvY.mjs";
3
+ import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-C2jrJbWG.mjs";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
6
6
  //#region src/cli/commands/docs.ts
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
2
  import { dirname, join, parse, resolve } from "node:path";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  //#region src/cli/commands/doctor.ts
@@ -1,3 +1,3 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as generate } from "../../generate-BUP4Oydg.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as generate } from "../../generate-BSSdtiOy.mjs";
3
3
  export { generate };
@@ -1,3 +1,3 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as init } from "../../init-Dg_Am2o_.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as init } from "../../init-pl88PHzb.mjs";
3
3
  export { init };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
2
- import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-DNj8cpiY.mjs";
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
+ import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-C2jrJbWG.mjs";
3
3
  //#region src/cli/commands/introspect.ts
4
4
  function describePermission(value) {
5
5
  if (!value) return "none";
@@ -1,8 +1,8 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
2
  import { describe } from "./commands/describe.mjs";
3
3
  import { exportDocs } from "./commands/docs.mjs";
4
4
  import { doctor } from "./commands/doctor.mjs";
5
- import { t as generate } from "../generate-BUP4Oydg.mjs";
6
- import { t as init } from "../init-Dg_Am2o_.mjs";
5
+ import { t as generate } from "../generate-BSSdtiOy.mjs";
6
+ import { t as init } from "../init-pl88PHzb.mjs";
7
7
  import { introspect } from "./commands/introspect.mjs";
8
8
  export { describe, doctor, exportDocs, generate, init, introspect };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.38.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.40.0 | MIT | https://classytic.com */
2
2
  //#region src/constants.ts
3
3
  /**
4
4
  * Arc Framework Constants — Single Source of Truth
@@ -1,13 +1,19 @@
1
1
 
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { CacheAdapter } from "@classytic/repo-core/cache";
4
+ import { DomainEvent } from "@classytic/primitives/events";
4
5
  //#region src/context/requestContext.d.ts
6
+ /** What started this unit of work. See `workScope.ts`. */
7
+ type WorkKind = "request" | "event" | "job";
5
8
  /**
6
9
  * Shape of the request-scoped context store.
7
- * Populated by Arc's onRequest hook in arcCorePlugin.
10
+ * Populated by Arc's onRequest hook in arcCorePlugin, or by `runWorkScope`
11
+ * for an event dispatch or a job run.
8
12
  */
9
13
  interface RequestStore {
10
- /** Unique request identifier */
14
+ /** Absent on stores created before scopes had kinds; treat as `'request'`. */
15
+ kind?: WorkKind;
16
+ /** Unique request identifier — the correlation key of an event or job scope. */
11
17
  requestId?: string;
12
18
  /** Authenticated user (if any) */
13
19
  user?: {
@@ -88,14 +94,15 @@ declare function getTraceHeaders(): Record<string, string> | undefined;
88
94
  //#endregion
89
95
  //#region src/context/requestScopedCache.d.ts
90
96
  /**
91
- * The cache for the CURRENT request, or `undefined` outside one.
97
+ * The cache for the CURRENT unit of work — a request, an event dispatch or a
98
+ * job run (see `runWorkScope`) — or `undefined` outside one.
92
99
  *
93
- * Lazily created on first use, so a request that never touches a cached
100
+ * Lazily created on first use, so a scope that never touches a cached
94
101
  * repository allocates nothing.
95
102
  */
96
103
  declare function requestScopedCache(): CacheAdapter | undefined;
97
104
  /**
98
- * Whether the current request has allocated a cache yet. Diagnostics and
105
+ * Whether the current scope has allocated a cache yet. Diagnostics and
99
106
  * tests only — never branch application behaviour on this.
100
107
  */
101
108
  declare function hasRequestScopedCache(): boolean;
@@ -124,4 +131,25 @@ declare const transactionContext: {
124
131
  storage: AsyncLocalStorage<unknown>;
125
132
  };
126
133
  //#endregion
127
- export { type DbSession, type RequestStore, getTraceHeaders, hasRequestScopedCache, requestContext, requestScopedCache, transactionContext };
134
+ //#region src/context/workScope.d.ts
135
+ interface WorkSeed {
136
+ readonly kind: WorkKind;
137
+ /** Becomes `requestId` — the correlation key for anything published inside. */
138
+ readonly id: string;
139
+ readonly organizationId?: string;
140
+ }
141
+ /** Enter a scope for `fn`, or run it inside the active one. */
142
+ declare function runWorkScope<T>(seed: WorkSeed, fn: () => T): T;
143
+ /**
144
+ * A per-scope memo slot: created once on first use, the same instance after.
145
+ * `undefined` outside a scope.
146
+ */
147
+ declare function scopedValue<T>(slot: symbol, create: () => T): T | undefined;
148
+ /**
149
+ * The scope an event dispatch runs in. The correlation id is the scope id so a
150
+ * child event published from a handler chains to the same correlation; the
151
+ * tenant rides `meta`, never the payload.
152
+ */
153
+ declare function workSeedFromEvent(event: DomainEvent): WorkSeed;
154
+ //#endregion
155
+ export { type DbSession, type RequestStore, type WorkKind, type WorkSeed, getTraceHeaders, hasRequestScopedCache, requestContext, requestScopedCache, runWorkScope, scopedValue, transactionContext, workSeedFromEvent };