@classytic/arc 2.31.0 → 2.33.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 (194) hide show
  1. package/README.md +1 -1
  2. package/bin/arc.js +58 -17
  3. package/dist/{BaseController-De7GqHck.mjs → BaseController-CaQC5EXX.mjs} +298 -35
  4. package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
  5. package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
  6. package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
  7. package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-DoALUlOO.mjs} +18 -15
  8. package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-CUeCmpUd.mjs} +1 -1
  9. package/dist/audit/index.d.mts +2 -2
  10. package/dist/audit/index.mjs +2 -2
  11. package/dist/auth/audit.mjs +1 -1
  12. package/dist/auth/index.d.mts +2 -2
  13. package/dist/auth/index.mjs +44 -11
  14. package/dist/auth/redis-session.mjs +1 -1
  15. package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
  16. package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
  17. package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-DLE9xX-V.mjs} +6 -5
  18. package/dist/cache/index.mjs +4 -4
  19. package/dist/{caching-BFHA6Scf.mjs → caching-BcGmvnHh.mjs} +2 -2
  20. package/dist/cleanup/index.d.mts +52 -18
  21. package/dist/cleanup/index.mjs +281 -32
  22. package/dist/cli/commands/describe.d.mts +1 -1
  23. package/dist/cli/commands/describe.mjs +4 -6
  24. package/dist/cli/commands/docs.mjs +3 -3
  25. package/dist/cli/commands/doctor.mjs +1 -1
  26. package/dist/cli/commands/generate.mjs +2 -2
  27. package/dist/cli/commands/init.mjs +2 -2
  28. package/dist/cli/commands/introspect.mjs +2 -2
  29. package/dist/cli/index.mjs +3 -3
  30. package/dist/{constants-D3Dv4LAV.mjs → constants-BxCoxmiN.mjs} +1 -1
  31. package/dist/context/index.mjs +3 -3
  32. package/dist/core/index.d.mts +2 -2
  33. package/dist/core/index.mjs +7 -7
  34. package/dist/{core-BGpIxINN.mjs → core-dxhcHhUD.mjs} +3 -3
  35. package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DVpoA0do.mjs} +14 -11
  36. package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B_B48JZ-.mjs} +10 -8
  37. package/dist/{createApp-Df89BEni.mjs → createApp-CLGegqiG.mjs} +287 -62
  38. package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
  39. package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-DR9vsUYy.mjs} +318 -35
  40. package/dist/discovery/index.mjs +1 -1
  41. package/dist/docs/index.d.mts +1 -1
  42. package/dist/docs/index.mjs +3 -3
  43. package/dist/{elevation-D2l1mh4g.mjs → elevation-B0tlY8y1.mjs} +4 -4
  44. package/dist/encryption/index.mjs +5 -4
  45. package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
  46. package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
  47. package/dist/{errors-Cj42AntO.mjs → errors-Cd_yy7aW.mjs} +1 -1
  48. package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
  49. package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dar8qRaj.mjs} +68 -11
  50. package/dist/events/index.d.mts +18 -297
  51. package/dist/events/index.mjs +7 -758
  52. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  53. package/dist/events/transports/redis-stream-entry.mjs +14 -10
  54. package/dist/events/transports/redis.d.mts +1 -1
  55. package/dist/events/transports/redis.mjs +1 -1
  56. package/dist/factory/index.d.mts +20 -5
  57. package/dist/factory/index.mjs +131 -8
  58. package/dist/fastify-augmentation-DVsTAy6X.d.mts +179 -0
  59. package/dist/{fields-CB5Cuzk9.mjs → fields-CojeUkS4.mjs} +6 -11
  60. package/dist/{generate-EcA6ui4i.mjs → generate-DiB0nHPR.mjs} +2 -2
  61. package/dist/{headers-3zvE0rM3.mjs → headers-CveNO9gR.mjs} +2 -2
  62. package/dist/{health-DjZYjM7H.mjs → health-CasG2A28.mjs} +1 -1
  63. package/dist/hooks/index.d.mts +1 -1
  64. package/dist/hooks/index.mjs +2 -2
  65. package/dist/idempotency/index.d.mts +45 -1
  66. package/dist/idempotency/index.mjs +95 -11
  67. package/dist/idempotency/redis.mjs +1 -1
  68. package/dist/{index-CEJNZFtX.d.mts → index-B-DvUzsr.d.mts} +82 -3
  69. package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
  70. package/dist/{index-Bc2ox8_U.d.mts → index-Brin0ODU.d.mts} +4 -4
  71. package/dist/{index-CFThn1K9.d.mts → index-C5NnMEKF.d.mts} +70 -4
  72. package/dist/{index-C-481Zf2.d.mts → index-ugr1tQlW.d.mts} +17 -1
  73. package/dist/index.d.mts +6 -6
  74. package/dist/index.mjs +11 -11
  75. package/dist/{init-BEiNZBsJ.mjs → init-mdJ-v4Xp.mjs} +24 -16
  76. package/dist/integrations/event-gateway.d.mts +1 -1
  77. package/dist/integrations/event-gateway.mjs +2 -2
  78. package/dist/integrations/index.d.mts +2 -2
  79. package/dist/integrations/index.mjs +1 -1
  80. package/dist/integrations/jobs/index.d.mts +1 -1
  81. package/dist/integrations/jobs/index.mjs +100 -98
  82. package/dist/integrations/mcp/index.d.mts +12 -3
  83. package/dist/integrations/mcp/index.mjs +2 -2
  84. package/dist/integrations/mcp/testing.d.mts +1 -1
  85. package/dist/integrations/mcp/testing.mjs +2 -2
  86. package/dist/integrations/streamline.mjs +3 -3
  87. package/dist/integrations/webhooks.d.mts +124 -1
  88. package/dist/integrations/webhooks.mjs +180 -2
  89. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  90. package/dist/integrations/websocket-redis.mjs +1 -1
  91. package/dist/integrations/websocket.mjs +38 -35
  92. package/dist/{interface-DYzM0q-u.mjs → interface-DznJrm9R.mjs} +1 -1
  93. package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
  94. package/dist/{keys-C0_pa1tY.mjs → keys-WeKvY1dE.mjs} +1 -1
  95. package/dist/{loadResources-yawi9tAE.mjs → loadResources-Bj-aSsXi.mjs} +4 -3
  96. package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
  97. package/dist/logger/index.mjs +1 -1
  98. package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-aJg5TqHi.mjs} +76 -36
  99. package/dist/{memory-DR6wpa9F.mjs → memory-qYSB7rMQ.mjs} +5 -5
  100. package/dist/{metrics-CUdw_wLB.mjs → metrics-D7uchLjN.mjs} +2 -2
  101. package/dist/middleware/index.d.mts +1 -1
  102. package/dist/middleware/index.mjs +3 -3
  103. package/dist/migrations/index.mjs +3 -3
  104. package/dist/{module-0pFJA6YW.mjs → module-CwKsgZXp.mjs} +1 -1
  105. package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-4w-S3-PU.mjs} +3 -3
  106. package/dist/{openapi-B2L4js1v.mjs → openapi-BN5cQaO2.mjs} +6 -6
  107. package/dist/outbox-admin/index.d.mts +85 -0
  108. package/dist/outbox-admin/index.mjs +115 -0
  109. package/dist/outbox-module-CXB79bvt.mjs +839 -0
  110. package/dist/outbox-module-D-VFq37T.d.mts +368 -0
  111. package/dist/permissions/index.d.mts +3 -3
  112. package/dist/permissions/index.mjs +6 -6
  113. package/dist/{permissions-L--QDAir.mjs → permissions-BAfwgBxc.mjs} +120 -8
  114. package/dist/{pipe-DRDNsI2T.mjs → pipe-BzzoEhHj.mjs} +3 -5
  115. package/dist/pipeline/index.d.mts +1 -1
  116. package/dist/pipeline/index.mjs +2 -2
  117. package/dist/{pipeline-UPQFatit.mjs → pipeline-q69DZzgG.mjs} +17 -15
  118. package/dist/plugins/index.d.mts +6 -6
  119. package/dist/plugins/index.mjs +16 -16
  120. package/dist/plugins/response-cache.mjs +2 -2
  121. package/dist/plugins/tracing-entry.mjs +2 -2
  122. package/dist/{pluralize-O0FZgESl.mjs → pluralize-B7xf8qZF.mjs} +1 -1
  123. package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
  124. package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
  125. package/dist/presets/filesUpload.d.mts +2 -2
  126. package/dist/presets/filesUpload.mjs +6 -6
  127. package/dist/presets/index.d.mts +2 -2
  128. package/dist/presets/index.mjs +2 -2
  129. package/dist/presets/multiTenant.d.mts +1 -1
  130. package/dist/presets/multiTenant.mjs +3 -3
  131. package/dist/presets/search.d.mts +2 -2
  132. package/dist/presets/search.mjs +2 -2
  133. package/dist/{presets-C4-g4_4P.mjs → presets-D-h0uWtP.mjs} +6 -5
  134. package/dist/projection-BKM0Ui-s.mjs +42 -0
  135. package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +50 -8
  136. package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
  137. package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
  138. package/dist/{realtime-C5NoxWFY.mjs → realtime-CTkv3O59.mjs} +10 -10
  139. package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
  140. package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-DNnd3vkz.mjs} +10 -8
  141. package/dist/registry/index.d.mts +3 -3
  142. package/dist/registry/index.mjs +3 -3
  143. package/dist/{registry-4213U4zb.mjs → registry-CLxCR5LF.mjs} +2 -2
  144. package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
  145. package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
  146. package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
  147. package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-uYE35JxI.mjs} +1 -1
  148. package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
  149. package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
  150. package/dist/{schedules-DdtlICTf.mjs → schedules-CAcZgia0.mjs} +3 -3
  151. package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
  152. package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CDvclAew.mjs} +2 -2
  153. package/dist/schemas/index.mjs +1 -1
  154. package/dist/scim/index.mjs +2 -2
  155. package/dist/scope/index.mjs +5 -5
  156. package/dist/{scope-DHx5Fous.mjs → scope-BaWcqBvr.mjs} +4 -4
  157. package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
  158. package/dist/{sse-CUTo9y0X.mjs → sse-Cf0jUHsh.mjs} +7 -6
  159. package/dist/{sseStream-B_5XcVVd.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
  160. package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-B88p99Z2.mjs} +1 -1
  161. package/dist/{streaming-PSZwi8JV.mjs → streaming-DsT1B5zJ.mjs} +1 -1
  162. package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-CLIOHsOK.mjs} +2 -2
  163. package/dist/sync/changelog.mjs +1 -1
  164. package/dist/testing/cleanupStoreContract.d.mts +1 -1
  165. package/dist/testing/cleanupStoreContract.mjs +12 -6
  166. package/dist/testing/index.d.mts +52 -13
  167. package/dist/testing/index.mjs +110 -40
  168. package/dist/testing/outboxStoreContract.d.mts +11 -2
  169. package/dist/testing/outboxStoreContract.mjs +110 -21
  170. package/dist/testing/storageContract.mjs +11 -6
  171. package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
  172. package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-jjKJDN13.mjs} +1 -1
  173. package/dist/types/index.d.mts +3 -3
  174. package/dist/types/index.mjs +1 -1
  175. package/dist/types/storage.mjs +1 -1
  176. package/dist/{types-CBgvkp8O.d.mts → types-BcTumAz_.d.mts} +128 -18
  177. package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
  178. package/dist/{types-Cys0ko9Z.mjs → types-CoqIRrkY.mjs} +4 -4
  179. package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
  180. package/dist/usage/index.d.mts +1 -1
  181. package/dist/usage/index.mjs +0 -0
  182. package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
  183. package/dist/utils/index.d.mts +57 -5
  184. package/dist/utils/index.mjs +12 -11
  185. package/dist/{utils-vL8BrDE2.mjs → utils-h1V_sT-t.mjs} +38 -7
  186. package/dist/{validate-_KbifpHF.mjs → validate-CdQlz52F.mjs} +2 -2
  187. package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
  188. package/dist/{versioning-CHDTor3P.mjs → versioning-rZbyLVD-.mjs} +4 -4
  189. package/package.json +31 -20
  190. package/skills/arc/SKILL.md +96 -4
  191. package/skills/arc/references/api-reference.md +19 -0
  192. package/skills/arc/references/production.md +2 -2
  193. package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
  194. package/dist/projection-BHw6xlOQ.mjs +0 -18
@@ -1,19 +1,19 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { r as evaluatePermissionDecision } from "./authorizationDecision-ChvUQGG1.mjs";
3
- import { p as isArcError, t as ArcError } from "./errors-Cj42AntO.mjs";
4
- import { n as requireSingleHeaderValue } from "./headers-3zvE0rM3.mjs";
5
- import { b as isElevated, c as getOrgId, g as getUserId, x as isMember } from "./types-Cys0ko9Z.mjs";
6
- import { t as BaseController } from "./BaseController-De7GqHck.mjs";
7
- import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CB5Cuzk9.mjs";
8
- import { t as executePipeline } from "./pipe-DRDNsI2T.mjs";
9
- import { t as buildRequestScopeProjection } from "./projection-BHw6xlOQ.mjs";
10
- import { r as resolvePipelineSteps } from "./pipeline-UPQFatit.mjs";
11
- import { n as validateAggregations } from "./validate-_KbifpHF.mjs";
12
- import { n as executeAggregation } from "./buildHandler-CDrCJzmB.mjs";
13
- import { t as pluralize } from "./pluralize-O0FZgESl.mjs";
14
- import { t as resolveActionPermission } from "./actionPermissions-BE5NwaQk.mjs";
15
- import { t as createIdempotencyResult } from "./interface-DYzM0q-u.mjs";
16
- import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-Bm2enbnh.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { r as evaluatePermissionDecision } from "./authorizationDecision-B3mP_QTy.mjs";
3
+ import { p as isArcError, t as ArcError } from "./errors-Cd_yy7aW.mjs";
4
+ import { n as requireSingleHeaderValue } from "./headers-CveNO9gR.mjs";
5
+ import { b as isElevated, c as getOrgId, g as getUserId, x as isMember } from "./types-CoqIRrkY.mjs";
6
+ import { t as BaseController } from "./BaseController-CaQC5EXX.mjs";
7
+ import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CojeUkS4.mjs";
8
+ import { t as executePipeline } from "./pipe-BzzoEhHj.mjs";
9
+ import { t as buildRequestScopeProjection } from "./projection-BKM0Ui-s.mjs";
10
+ import { r as resolvePipelineSteps } from "./pipeline-q69DZzgG.mjs";
11
+ import { n as validateAggregations } from "./validate-CdQlz52F.mjs";
12
+ import { n as executeAggregation } from "./buildHandler-DLE9xX-V.mjs";
13
+ import { t as pluralize } from "./pluralize-B7xf8qZF.mjs";
14
+ import { t as resolveActionPermission } from "./actionPermissions-CUeCmpUd.mjs";
15
+ import { t as createIdempotencyResult } from "./interface-DznJrm9R.mjs";
16
+ import { r as shouldRejectAdditionalProperties, t as normalizeSchemaIR } from "./schemaIR-CDvclAew.mjs";
17
17
  import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
18
18
  import { createHash, randomUUID } from "node:crypto";
19
19
  import fp from "fastify-plugin";
@@ -332,18 +332,32 @@ function expandOperatorKeys(input) {
332
332
  */
333
333
  function buildScope(auth) {
334
334
  if (!auth) return { kind: "public" };
335
+ /**
336
+ * `context` (and `teamId`, member-only) are carried through, not dropped.
337
+ *
338
+ * A resolver could already return them — `McpAuthResult` has an index
339
+ * signature — and this function silently discarded both, so a resource
340
+ * gated by `requireScopeContext('branchId')` worked over HTTP and DENIED
341
+ * every MCP call, because the dimension it authorises against was never
342
+ * populated. Fail-closed, but it made the tool permanently unusable rather
343
+ * than wrong, which is why nothing caught it.
344
+ */
345
+ const context = normalizeScopeContext(auth.context);
335
346
  if (auth.clientId && auth.organizationId) return {
336
347
  kind: "service",
337
348
  clientId: auth.clientId,
338
349
  organizationId: auth.organizationId,
339
- scopes: auth.scopes
350
+ scopes: auth.scopes,
351
+ ...context ? { context } : {}
340
352
  };
341
353
  if (auth.organizationId) return {
342
354
  kind: "member",
343
355
  userId: auth.userId,
344
356
  userRoles: auth.roles ?? [],
345
357
  organizationId: auth.organizationId,
346
- orgRoles: auth.orgRoles ?? []
358
+ orgRoles: auth.orgRoles ?? [],
359
+ ...typeof auth.teamId === "string" ? { teamId: auth.teamId } : {},
360
+ ...context ? { context } : {}
347
361
  };
348
362
  return {
349
363
  kind: "authenticated",
@@ -351,6 +365,23 @@ function buildScope(auth) {
351
365
  userRoles: auth.roles ?? []
352
366
  };
353
367
  }
368
+ /**
369
+ * Accept a resolver's `context` only as a string→string map.
370
+ *
371
+ * The resolver is host code reached through an index signature, so the value
372
+ * is `unknown`. Coercing arbitrary values (numbers, objects, null) into
373
+ * authorisation dimensions would make `requireScopeContext('branchId')`
374
+ * compare against something that was never a branch id; non-string entries are
375
+ * dropped rather than stringified. Returns `undefined` for an empty result so
376
+ * a scope reports "carries no context" rather than "carries an empty one" —
377
+ * different facts to a caller.
378
+ */
379
+ function normalizeScopeContext(value) {
380
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
381
+ const out = {};
382
+ for (const [k, v] of Object.entries(value)) if (typeof v === "string") out[k] = v;
383
+ return Object.keys(out).length > 0 ? Object.freeze(out) : void 0;
384
+ }
354
385
  //#endregion
355
386
  //#region src/integrations/mcp/tool-helpers.ts
356
387
  /**
@@ -367,12 +398,13 @@ function buildScope(auth) {
367
398
  */
368
399
  async function evaluatePermission(check, session, resource, action, input) {
369
400
  if (!check) return null;
401
+ const user = session ? {
402
+ id: session.userId,
403
+ _id: session.userId,
404
+ ...session
405
+ } : null;
370
406
  return evaluatePermissionDecision(check, {
371
- user: session ? {
372
- id: session.userId,
373
- _id: session.userId,
374
- ...session
375
- } : null,
407
+ user,
376
408
  scope: buildScope(session),
377
409
  resource,
378
410
  action,
@@ -465,13 +497,14 @@ function toCallToolSuccess(value) {
465
497
  */
466
498
  function toCallToolError(input) {
467
499
  let contract;
468
- if (input instanceof Error) if (isArcError(input) || isHttpError(input)) contract = toErrorContract(input);
469
- else contract = {
470
- code: "arc.internal_error",
471
- message: input.message || "Internal Server Error",
472
- status: 500
473
- };
474
- else contract = {
500
+ if (input instanceof Error) {
501
+ if (isArcError(input) || isHttpError(input)) contract = toErrorContract(input);
502
+ else contract = {
503
+ code: "arc.internal_error",
504
+ message: input.message || "Internal Server Error",
505
+ status: 500
506
+ };
507
+ } else contract = {
475
508
  code: input.code,
476
509
  message: input.message,
477
510
  ...input.status !== void 0 ? { status: input.status } : {},
@@ -588,7 +621,7 @@ function invocationFingerprint(resourceName, actionName, input, session) {
588
621
  return `mcp:${resourceName}:${actionName}:${createHash("sha256").update(JSON.stringify(sortKeys(input))).digest("hex").substring(0, 16)}:u=${session?.userId ?? session?.clientId ?? "anon"}`;
589
622
  }
590
623
  /** TTLs matching the HTTP idempotency plugin's defaults. */
591
- const MCP_IDEMPOTENCY_TTL_MS = 1440 * 60 * 1e3;
624
+ const MCP_IDEMPOTENCY_TTL_MS = 864e5;
592
625
  const MCP_IDEMPOTENCY_LOCK_MS = 1e4;
593
626
  /**
594
627
  * Invoke a controller method through the MCP synthetic-context path.
@@ -847,9 +880,12 @@ function resolveCrudDescription(override, meta) {
847
880
  * MCP agent the same field-projection it already has on list.
848
881
  */
849
882
  const PROJECTION_FIELD = z.string().optional().describe("Comma-separated field list to project (e.g. 'name,price'). Prefix with '-' to exclude (e.g. '-description').");
883
+ /** Default page cap, used only when the resource declares none. */
884
+ const DEFAULT_MCP_MAX_LIMIT = 100;
885
+ /** `limit` is per-resource — see `maxLimit` on the options. */
886
+ const limitField = (maxLimit) => z.number().int().min(1).max(maxLimit).optional().describe(`Items per page (max ${maxLimit})`);
850
887
  const PAGINATION_SHAPE = {
851
888
  page: z.number().int().min(1).optional().describe("Page number (1-based). Ignored when `cursor` is set."),
852
- limit: z.number().int().min(1).max(100).optional().describe("Items per page (max 100)"),
853
889
  cursor: z.string().optional().describe("Opaque keyset cursor from a previous page's `next`. Pass it to fetch the following page; when set, `page` is ignored (cursor/keyset pagination — stable under concurrent inserts)."),
854
890
  sort: z.string().optional().describe("Sort field, prefix with - for descending"),
855
891
  search: z.string().optional().describe("Full-text search query"),
@@ -937,9 +973,12 @@ function opDescription(op, fieldName) {
937
973
  }
938
974
  /** Build list/query shape with filterable fields, operators, and pagination */
939
975
  function buildListShape(fieldRules, options) {
940
- const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators } = options;
976
+ const { filterableFields = [], hiddenFields = [], extraHideFields = [], allowedOperators, maxLimit = DEFAULT_MCP_MAX_LIMIT } = options;
941
977
  const allHidden = /* @__PURE__ */ new Set([...hiddenFields, ...extraHideFields]);
942
- const shape = { ...PAGINATION_SHAPE };
978
+ const shape = {
979
+ ...PAGINATION_SHAPE,
980
+ limit: limitField(maxLimit)
981
+ };
943
982
  shape._count = z.boolean().optional().describe("Return only { count } of records matching the filters — no documents fetched. Cheapest way to answer 'how many'.");
944
983
  shape._exists = z.boolean().optional().describe("Return only whether ANY record matches the filters (boolean existence check).");
945
984
  shape._distinct = z.string().optional().describe("Return the distinct values of this field across records matching the filters.");
@@ -1434,7 +1473,6 @@ function jsonSchemaPropertyToZod(prop) {
1434
1473
  case "array": return applyDescription(arrayToZod(prop), prop);
1435
1474
  case "object": return applyDescription(objectToZod(prop), prop);
1436
1475
  case "null": continue;
1437
- default: break;
1438
1476
  }
1439
1477
  return applyDescription(z.unknown(), prop);
1440
1478
  }
@@ -1825,6 +1863,7 @@ function resourceToTools(resource, config = {}) {
1825
1863
  const filterableFields = resource.schemaOptions?.filterableFields ?? resource.queryParser?.allowedFilterFields;
1826
1864
  const sortableFields = resource.queryParser?.allowedSortFields;
1827
1865
  const allowedOperators = resource.queryParser?.allowedOperators;
1866
+ const maxLimit = resource.queryParser?.maxLimit;
1828
1867
  const hasSoftDelete = resource._appliedPresets?.includes("softDelete") ?? false;
1829
1868
  const tools = [];
1830
1869
  const prefix = config.toolNamePrefix;
@@ -1845,6 +1884,7 @@ function resourceToTools(resource, config = {}) {
1845
1884
  extraHideFields: config.hideFields,
1846
1885
  filterableFields,
1847
1886
  allowedOperators,
1887
+ maxLimit,
1848
1888
  adapterBodies
1849
1889
  });
1850
1890
  if (wiring?.idempotencyStore && op !== "list" && op !== "get") inputSchema._idempotencyKey = z.string().optional().describe("Optional idempotency key — retrying with the same key and input replays the first successful result instead of re-executing.");
@@ -2061,7 +2101,7 @@ function getOps(r, override) {
2061
2101
  }
2062
2102
  //#endregion
2063
2103
  //#region src/integrations/mcp/sessionCache.ts
2064
- const DEFAULT_TTL_MS = 1800 * 1e3;
2104
+ const DEFAULT_TTL_MS = 18e5;
2065
2105
  const DEFAULT_MAX_SESSIONS = 1e3;
2066
2106
  var McpSessionCache = class {
2067
2107
  sessions = /* @__PURE__ */ new Map();
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
3
3
  //#region src/cache/memory.ts
4
4
  var memory_exports = /* @__PURE__ */ __exportAll({ MemoryCacheStore: () => MemoryCacheStore });
5
5
  /**
@@ -26,11 +26,11 @@ var MemoryCacheStore = class {
26
26
  constructor(options = {}) {
27
27
  this.defaultTtlSeconds = options.defaultTtlSeconds ?? 60;
28
28
  this.maxEntries = clamp(options.maxEntries ?? 1e3, 1, 1e5);
29
- this.maxEntryBytes = clamp(options.maxEntryBytes ?? 256 * 1024, 1024, 10 * 1024 * 1024);
30
- this.maxMemoryBytes = options.maxMemoryBytes ?? 50 * 1024 * 1024;
29
+ this.maxEntryBytes = clamp(options.maxEntryBytes ?? 262144, 1024, 10485760);
30
+ this.maxMemoryBytes = options.maxMemoryBytes ?? 52428800;
31
31
  this.evictionWatermark = clamp(options.evictionWatermark ?? .9, .5, 1);
32
32
  this.logger = options.logger ?? console;
33
- const cleanupIntervalMs = clamp(options.cleanupIntervalMs ?? 3e4, 1e3, 600 * 1e3);
33
+ const cleanupIntervalMs = clamp(options.cleanupIntervalMs ?? 3e4, 1e3, 6e5);
34
34
  this.cleanupTimer = setInterval(() => this.cleanupExpired(), cleanupIntervalMs);
35
35
  if (this.cleanupTimer.unref) this.cleanupTimer.unref();
36
36
  }
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/metrics.ts
5
5
  var metrics_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
1
 
2
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-CpzxqePp.mjs";
2
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-BcR2bFZQ.mjs";
3
3
  import { RouteHandlerMethod } from "fastify";
4
4
  //#region src/middleware/middleware.d.ts
5
5
  interface NamedMiddleware {
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as CRUD_OPERATIONS } from "../constants-D3Dv4LAV.mjs";
3
- import { t as multipartBody } from "../multipartBody-wOgj_fnj.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "../constants-BxCoxmiN.mjs";
3
+ import { t as multipartBody } from "../multipartBody-4w-S3-PU.mjs";
4
4
  //#region src/middleware/middleware.ts
5
5
  /**
6
6
  * Named Middleware — Priority-based, conditional middleware execution.
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as startRenewingLease } from "../renewingLease-Dll8mGgc.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { t as startRenewingLease } from "../renewingLease-BnWtbA2U.mjs";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  //#region src/migrations/index.ts
5
5
  /**
@@ -88,7 +88,7 @@ var MongoMigrationStore = class {
88
88
  }
89
89
  };
90
90
  const MIGRATION_LOCK_NAME = "arc:migrations";
91
- const DEFAULT_LOCK_LEASE_MS = 600 * 1e3;
91
+ const DEFAULT_LOCK_LEASE_MS = 6e5;
92
92
  /**
93
93
  * Source checksum for drift detection — SHA-256 over the migration's
94
94
  * `up`/`down` source. Stable for a given build; a transpile-target change
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  //#region src/factory/module/index.ts
3
3
  /**
4
4
  * Identity helper for authoring a typed module — mirrors `defineResource`.
@@ -1,9 +1,9 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
2
  import sjson from "secure-json-parse";
3
3
  //#region src/middleware/multipartBody.ts
4
- const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
4
+ const DEFAULT_MAX_FILE_SIZE = 10485760;
5
5
  const DEFAULT_MAX_FILES = 5;
6
- const DEFAULT_MAX_TOTAL_BYTES = 25 * 1024 * 1024;
6
+ const DEFAULT_MAX_TOTAL_BYTES = 26214400;
7
7
  const DEFAULT_FILES_KEY = "_files";
8
8
  /**
9
9
  * Build a matcher for MIME allow-lists that supports exact (e.g. `image/png`),
@@ -1,9 +1,9 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { n as getUserRoles } from "./userHelpers-ChYJ6CdK.mjs";
3
- import { n as convertRouteSchema } from "./schemaConverter-D--wdJM-.mjs";
4
- import { t as pluralize } from "./pluralize-O0FZgESl.mjs";
5
- import { t as resolveActionPermission } from "./actionPermissions-BE5NwaQk.mjs";
6
- import { t as buildActionBodySchema } from "./createActionRouter-CDXEdasl.mjs";
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { n as getUserRoles } from "./userHelpers-CWF5pbZD.mjs";
3
+ import { n as convertRouteSchema } from "./schemaConverter-XH1RZRyu.mjs";
4
+ import { t as pluralize } from "./pluralize-B7xf8qZF.mjs";
5
+ import { t as resolveActionPermission } from "./actionPermissions-CUeCmpUd.mjs";
6
+ import { t as buildActionBodySchema } from "./createActionRouter-DVpoA0do.mjs";
7
7
  import fp from "fastify-plugin";
8
8
  //#region src/docs/openapi/canonical-schemas.ts
9
9
  /**
@@ -0,0 +1,85 @@
1
+
2
+ import { l as PermissionCheck } from "../base-Csjc2oF-.mjs";
3
+ import { t as ArcModule } from "../types-BcTumAz_.mjs";
4
+ import { OutboxStore } from "@classytic/primitives/outbox";
5
+ //#region src/outbox-admin/module.d.ts
6
+ /**
7
+ * A store that can answer an operator's questions.
8
+ *
9
+ * Only the four reads this surface actually performs are required — `fail` and
10
+ * `purge` belong to the relay and retention, not here.
11
+ */
12
+ type OutboxAdminStore = OutboxStore & Required<Pick<OutboxStore, "getDeadLettered" | "requeue" | "countByStatus" | "oldestPendingAgeMs">>;
13
+ interface OutboxAdminPermissions {
14
+ /**
15
+ * Reading health and the dead-letter list.
16
+ *
17
+ * MUST be platform-only — see `allowUnverifiedOperatorPermission`.
18
+ */
19
+ view: PermissionCheck;
20
+ /**
21
+ * Replaying a dead-lettered event. Defaults to `view` when omitted — but
22
+ * replay RE-DELIVERS an event that already failed five times, so a
23
+ * deployment that separates read access from operational action should gate
24
+ * it distinctly. Also platform-only.
25
+ */
26
+ replay?: PermissionCheck;
27
+ }
28
+ interface OutboxAdminModuleDeps {
29
+ store: OutboxAdminStore;
30
+ permissions: OutboxAdminPermissions;
31
+ /** Route prefix. Default `/admin/outbox`. */
32
+ prefix?: string;
33
+ /** Module name. Default `outbox-admin`. */
34
+ moduleName?: string;
35
+ /**
36
+ * Name the outbox module was composed under, used to resolve the relay and
37
+ * declared as a `dependsOn` edge. Default `outbox`.
38
+ */
39
+ outboxModuleName?: string;
40
+ /** Resource names this module supersedes. */
41
+ owns?: readonly string[];
42
+ /** Cap on `?limit`. Default 500 — a dead-letter list is for triage, not export. */
43
+ maxDeadLetterLimit?: number;
44
+ /**
45
+ * Accept gates arc cannot prove are platform-only.
46
+ *
47
+ * By default both gates must carry `_platformOnly` (i.e. come from
48
+ * `requirePlatformRole`), and boot FAILS otherwise. The reason is the same
49
+ * one that hardened `integrations/jobs` in 2.31: these routes are global by
50
+ * construction. `getDeadLettered(limit)` takes no filter and an outbox row
51
+ * carries no tenant identity, so there is nothing to scope by. A gate that
52
+ * grants per organization therefore READS as scoped while serving everyone —
53
+ * `requireOrgRole('manager')` would let org A's manager read org B's failed
54
+ * events. Neither returns a policy, so no per-request check can tell a real
55
+ * operator gate from an org one.
56
+ *
57
+ * Set this only for a custom check you have verified consults platform
58
+ * identity alone.
59
+ */
60
+ allowUnverifiedOperatorPermission?: boolean;
61
+ /**
62
+ * Include each dead-lettered event's domain PAYLOAD in the listing.
63
+ * Default `false`.
64
+ *
65
+ * The payload is the original event body — in an ERP that is order totals,
66
+ * customer records and payment references. Triage needs the id, the event
67
+ * name, the attempt count and why it failed; it does not need the contents.
68
+ * Opt in only where the surface is genuinely platform-operator-only and you
69
+ * accept that the response carries business data.
70
+ */
71
+ exposePayload?: boolean;
72
+ /**
73
+ * Include the failure STACK TRACE alongside the error message.
74
+ * Default `false` — a stack names internal paths and framework versions.
75
+ */
76
+ exposeStack?: boolean;
77
+ /**
78
+ * Pending-event age (ms) past which the outbox reports unhealthy even with an
79
+ * empty dead-letter queue. Default 5 minutes.
80
+ */
81
+ relayLagUnhealthyMs?: number;
82
+ }
83
+ declare function createOutboxAdminModule(deps: OutboxAdminModuleDeps): ArcModule<void>;
84
+ //#endregion
85
+ export { type OutboxAdminModuleDeps, type OutboxAdminPermissions, type OutboxAdminStore, createOutboxAdminModule };
@@ -0,0 +1,115 @@
1
+ /*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
2
+ import { i as NotFoundError, u as ValidationError } from "../errors-Cd_yy7aW.mjs";
3
+ import { t as defineResource } from "../defineResource-DR9vsUYy.mjs";
4
+ import { n as getModuleExports, t as defineModule } from "../module-CwKsgZXp.mjs";
5
+ //#region src/outbox-admin/module.ts
6
+ /**
7
+ * Strip the domain payload and the stack unless explicitly opted in.
8
+ *
9
+ * Triage is answered by WHICH event failed, HOW MANY times, WHEN and WHY. The
10
+ * payload answers none of those and carries the business record; the stack
11
+ * names internal paths. Both are off by default, on the same reasoning that
12
+ * made `exposeResult` / `exposeFailureReason` opt-in for jobs.
13
+ */
14
+ function redactDeadLettered(e, opts) {
15
+ return {
16
+ event: {
17
+ type: e.event.type,
18
+ meta: e.event.meta,
19
+ ...opts.exposePayload ? { payload: e.event.payload } : {}
20
+ },
21
+ error: {
22
+ message: e.error.message,
23
+ ...e.error.code !== void 0 ? { code: e.error.code } : {},
24
+ ...opts.exposeStack && e.error.stack !== void 0 ? { stack: e.error.stack } : {}
25
+ },
26
+ attempts: e.attempts,
27
+ firstFailedAt: e.firstFailedAt,
28
+ lastFailedAt: e.lastFailedAt,
29
+ ...e.handlerName !== void 0 ? { handlerName: e.handlerName } : {}
30
+ };
31
+ }
32
+ function createOutboxAdminModule(deps) {
33
+ const moduleName = deps.moduleName ?? "outbox-admin";
34
+ const outboxModuleName = deps.outboxModuleName ?? "outbox";
35
+ const maxLimit = deps.maxDeadLetterLimit ?? 500;
36
+ const view = deps.permissions.view;
37
+ const replay = deps.permissions.replay ?? deps.permissions.view;
38
+ const lagUnhealthyMs = deps.relayLagUnhealthyMs ?? 3e5;
39
+ if (!deps.allowUnverifiedOperatorPermission) {
40
+ for (const [label, gate] of [["view", view], ["replay", replay]]) if (!gate._platformOnly) throw new Error(`[arc/outbox-admin] permissions.${label} must be platform-only. These routes are global (an outbox row carries no tenant identity and getDeadLettered takes no filter), so an org-role gate would let a member of one organization read another's failed events and their payloads. Use requirePlatformRole('platform-ops') — or requireRoles([...], { includeOrgRoles: false }) with allowUnverifiedOperatorPermission: true if you have verified the check yourself.`);
41
+ }
42
+ const resource = defineResource({
43
+ name: moduleName,
44
+ displayName: "Outbox Admin",
45
+ tag: "Operations",
46
+ prefix: deps.prefix ?? "/admin/outbox",
47
+ customRoutesOnly: true,
48
+ routes: [
49
+ {
50
+ method: "GET",
51
+ path: "/health",
52
+ summary: "Outbox health — pending, dead-letter and relay lag",
53
+ permissions: view,
54
+ rawHandler: async (_req, reply) => {
55
+ const [pending, deadLetter, relayLagMs] = await Promise.all([
56
+ deps.store.countByStatus("pending"),
57
+ deps.store.countByStatus("dead_letter"),
58
+ deps.store.oldestPendingAgeMs()
59
+ ]);
60
+ const healthy = deadLetter === 0 && (relayLagMs === null || relayLagMs < lagUnhealthyMs);
61
+ return reply.send({
62
+ pending,
63
+ deadLetter,
64
+ relayLagMs,
65
+ healthy
66
+ });
67
+ }
68
+ },
69
+ {
70
+ method: "GET",
71
+ path: "/dead-letter",
72
+ summary: "List dead-lettered events with their errors",
73
+ permissions: view,
74
+ rawHandler: async (req, reply) => {
75
+ const raw = req.query?.limit;
76
+ const parsed = Number(raw ?? 100);
77
+ const limit = Math.min(Math.max(Number.isFinite(parsed) ? parsed : 100, 1), maxLimit);
78
+ const events = await deps.store.getDeadLettered(limit);
79
+ return reply.send({
80
+ count: events.length,
81
+ limit,
82
+ events: events.map((e) => redactDeadLettered(e, deps))
83
+ });
84
+ }
85
+ },
86
+ {
87
+ method: "POST",
88
+ path: "/dead-letter/:id/replay",
89
+ summary: "Requeue a dead-lettered event and drain the relay",
90
+ permissions: replay,
91
+ rawHandler: async (req, reply) => {
92
+ const eventId = req.params?.id;
93
+ if (!eventId) throw new ValidationError("event id is required");
94
+ if (!await deps.store.requeue(eventId)) throw new NotFoundError(`Dead-lettered event "${eventId}" — not found, or not currently dead-lettered`);
95
+ const { relay } = getModuleExports(req.server, outboxModuleName);
96
+ const relayDelivered = await relay.relay();
97
+ return reply.send({
98
+ eventId,
99
+ requeued: true,
100
+ relayDelivered,
101
+ note: "relayDelivered counts the entire drained batch, not this event alone"
102
+ });
103
+ }
104
+ }
105
+ ]
106
+ });
107
+ return defineModule({
108
+ name: moduleName,
109
+ dependsOn: [outboxModuleName],
110
+ ...deps.owns ? { owns: [...deps.owns] } : {},
111
+ resources: () => [resource]
112
+ });
113
+ }
114
+ //#endregion
115
+ export { createOutboxAdminModule };