@classytic/arc 2.20.0 → 2.22.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 (143) hide show
  1. package/dist/{BaseController-D-MHZ27_.mjs → BaseController-C6FlRWxF.mjs} +2 -2
  2. package/dist/{EventTransport-IEn7Tn-Q.d.mts → EventTransport-QmY7A258.d.mts} +0 -1
  3. package/dist/{QueryCache-TRfXau3W.d.mts → QueryCache-Z1B38koh.d.mts} +0 -1
  4. package/dist/{actionPermissions-C9RMiIpc.mjs → actionPermissions-ze70xXJi.mjs} +1 -1
  5. package/dist/audit/index.d.mts +9 -5
  6. package/dist/auth/audit.d.mts +7 -4
  7. package/dist/auth/audit.mjs +5 -0
  8. package/dist/auth/index.d.mts +35 -14
  9. package/dist/auth/index.mjs +6 -3
  10. package/dist/auth/redis-session.d.mts +1 -2
  11. package/dist/cache/index.d.mts +2 -3
  12. package/dist/cli/commands/describe.d.mts +1 -2
  13. package/dist/cli/commands/docs.mjs +1 -1
  14. package/dist/cli/commands/init.mjs +1 -1
  15. package/dist/cli/index.mjs +1 -1
  16. package/dist/context/index.d.mts +2 -2
  17. package/dist/core/index.d.mts +3 -3
  18. package/dist/core/index.mjs +4 -4
  19. package/dist/{core-CWhyGkiM.mjs → core-b6fgFgnd.mjs} +129 -17
  20. package/dist/{createActionRouter-HlQso0jO.mjs → createActionRouter-h1cEEaEX.mjs} +10 -6
  21. package/dist/{createAggregationRouter-5LzPzgw1.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
  22. package/dist/{createApp-D6uwzCKu.mjs → createApp-Ci7yG5gv.mjs} +390 -192
  23. package/dist/discovery/index.d.mts +0 -1
  24. package/dist/docs/index.d.mts +1 -2
  25. package/dist/docs/index.mjs +1 -1
  26. package/dist/{elevation-xGUBq6p_.d.mts → elevation-C9s5hFP_.d.mts} +0 -1
  27. package/dist/encryption/index.d.mts +2 -144
  28. package/dist/encryption/index.mjs +9 -5
  29. package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
  30. package/dist/{errorHandler-BtUUDPEa.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -1
  31. package/dist/{errors-DOHkjQlD.d.mts → errors-DHcHVAgk.d.mts} +0 -1
  32. package/dist/{eventPlugin-DbBFd3x_.d.mts → eventPlugin-A6EbthXR.d.mts} +10 -6
  33. package/dist/events/index.d.mts +9 -6
  34. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  35. package/dist/events/transports/redis.d.mts +1 -2
  36. package/dist/factory/index.d.mts +30 -9
  37. package/dist/factory/index.mjs +35 -4
  38. package/dist/{fastify-augmentation-CCQJEDgh.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -3
  39. package/dist/fields-D3lhgzhD.d.mts +121 -0
  40. package/dist/health-ScyjIVo5.mjs +202 -0
  41. package/dist/hooks/index.d.mts +1 -1
  42. package/dist/idempotency/index.d.mts +4 -3
  43. package/dist/idempotency/index.mjs +2 -0
  44. package/dist/idempotency/redis.d.mts +1 -1
  45. package/dist/{types-BHMKlMBU.d.mts → index-BEu4wjbL.d.mts} +241 -80
  46. package/dist/{index-DUa8WHz3.d.mts → index-D8MKYGDa.d.mts} +88 -7
  47. package/dist/{index-_--GyzcO.d.mts → index-DbtgCOCj.d.mts} +1324 -1228
  48. package/dist/{index-CdFkIMEd.d.mts → index-ogIHvOjo.d.mts} +36 -7
  49. package/dist/index.d.mts +8 -7
  50. package/dist/index.mjs +6 -6
  51. package/dist/{init-YH_nslQR.mjs → init-DleK3p9f.mjs} +272 -139
  52. package/dist/integrations/event-gateway.d.mts +2 -3
  53. package/dist/integrations/index.d.mts +2 -2
  54. package/dist/integrations/jobs.d.mts +0 -1
  55. package/dist/integrations/mcp/index.d.mts +6 -7
  56. package/dist/integrations/mcp/index.mjs +1 -1
  57. package/dist/integrations/mcp/testing.d.mts +1 -2
  58. package/dist/integrations/mcp/testing.mjs +1 -1
  59. package/dist/integrations/streamline.d.mts +2 -2
  60. package/dist/integrations/webhooks.d.mts +8 -5
  61. package/dist/integrations/webhooks.mjs +9 -5
  62. package/dist/integrations/websocket-pushref-redis.d.mts +1 -2
  63. package/dist/integrations/websocket-redis.d.mts +1 -2
  64. package/dist/integrations/websocket.d.mts +2 -2
  65. package/dist/jsonBody-BlzAoGCe.mjs +43 -0
  66. package/dist/{loadResources-B8UmpITt.mjs → loadResources-DpU8pfjV.mjs} +4 -3
  67. package/dist/logger/index.d.mts +26 -1
  68. package/dist/logger/index.mjs +39 -1
  69. package/dist/{mcpPlugin-CuW6L_uD.mjs → mcpPlugin-ByA2tf6D.mjs} +8 -4
  70. package/dist/middleware/index.d.mts +1 -2
  71. package/dist/{module-JYH9V0I8.d.mts → module-B4u10iOJ.d.mts} +71 -1
  72. package/dist/{openapi-Dq6RbEGK.mjs → openapi-BRpMZD7C.mjs} +3 -3
  73. package/dist/permissions/index.d.mts +4 -3
  74. package/dist/permissions/index.mjs +2 -2
  75. package/dist/{permissions-CgbOLJZG.mjs → permissions-BJMuKMl7.mjs} +33 -1
  76. package/dist/pipeline/index.d.mts +1 -2
  77. package/dist/plugins/index.d.mts +96 -7
  78. package/dist/plugins/index.mjs +86 -268
  79. package/dist/plugins/response-cache.d.mts +5 -3
  80. package/dist/plugins/response-cache.mjs +1 -0
  81. package/dist/plugins/tracing-entry.d.mts +1 -1
  82. package/dist/plugins/tracing-entry.mjs +7 -4
  83. package/dist/presets/filesUpload.d.mts +4 -5
  84. package/dist/presets/filesUpload.mjs +1 -1
  85. package/dist/presets/index.d.mts +1 -2
  86. package/dist/presets/index.mjs +1 -1
  87. package/dist/presets/multiTenant.d.mts +1 -2
  88. package/dist/presets/search.d.mts +2 -3
  89. package/dist/presets/search.mjs +1 -1
  90. package/dist/{presets-DWsYQdbV.mjs → presets-gw0ciIUD.mjs} +1 -1
  91. package/dist/{pushref-registry-DnCqL3aa.d.mts → pushref-registry-BbcnpNTm.d.mts} +4 -2
  92. package/dist/{queryCachePlugin-xzFyKAjT.d.mts → queryCachePlugin-CfYWq-Ak.d.mts} +1 -2
  93. package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
  94. package/dist/{redis-C3W0qbz8.d.mts → redis-UDtLaNj6.d.mts} +0 -1
  95. package/dist/{redis-stream-DsP1vqZB.d.mts → redis-stream-iVR2l7aS.d.mts} +1 -2
  96. package/dist/{registerRoute-DGSREwPw.mjs → registerRoute-xHc42fwB.mjs} +38 -18
  97. package/dist/registry/index.d.mts +2 -2
  98. package/dist/requestId-5BhTVzBv.mjs +113 -0
  99. package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
  100. package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
  101. package/dist/schemas/index.d.mts +0 -1
  102. package/dist/scim/index.d.mts +0 -1
  103. package/dist/scim/index.mjs +6 -7
  104. package/dist/scope/index.d.mts +2 -3
  105. package/dist/scope/index.mjs +2 -76
  106. package/dist/scope-k15RXfcq.mjs +64 -0
  107. package/dist/{sessionManager-BXG-HGwG.d.mts → sessionManager-BHOM3N_0.d.mts} +10 -6
  108. package/dist/{storage-Co_PQpeQ.d.mts → storage-DLUZ04kK.d.mts} +8 -4
  109. package/dist/testing/index.d.mts +83 -4
  110. package/dist/testing/index.mjs +74 -4
  111. package/dist/testing/storageContract.d.mts +1 -2
  112. package/dist/{tracing-D8jlGXP7.d.mts → tracing-CsGKQJGg.d.mts} +0 -1
  113. package/dist/types/index.d.mts +4 -4
  114. package/dist/types/storage.d.mts +1 -1
  115. package/dist/{types-C9b2HR5W.d.mts → types-Bc42CUyk.d.mts} +1 -2
  116. package/dist/types-Cc91nXuA.d.mts +132 -0
  117. package/dist/{fields-C7ahdGYf.d.mts → types-Csxga7d3.d.mts} +2 -123
  118. package/dist/{types-LNebCoJx.d.mts → types-DH0GupI7.d.mts} +8 -4
  119. package/dist/usage/index.d.mts +142 -0
  120. package/dist/usage/index.mjs +0 -0
  121. package/dist/utils/index.d.mts +220 -144
  122. package/dist/utils/index.mjs +3 -3
  123. package/dist/{utils-ChjYQpjn.mjs → utils-FhJ88TJE.mjs} +129 -39
  124. package/dist/{versioning-D_eBCfNr.d.mts → versioning-DLwklPlG.d.mts} +1 -7
  125. package/dist/{websocket-MlYlrYq0.d.mts → websocket-uuWqM6UY.d.mts} +5 -4
  126. package/llms.txt +39 -0
  127. package/package.json +453 -447
  128. package/skills/arc/SKILL.md +14 -8
  129. package/skills/arc/references/api-reference.md +34 -0
  130. package/skills/arc/references/auth.md +21 -4
  131. package/skills/arc/references/mcp.md +1 -1
  132. package/skills/arc/references/production.md +44 -2
  133. package/skills/arc-code-review/SKILL.md +1 -1
  134. package/skills/arc-code-review/references/anti-patterns.md +71 -18
  135. package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
  136. package/skills/arc-code-review/references/migration-recipes.md +7 -7
  137. package/skills/arc-code-review/references/mongokit-migration.md +4 -3
  138. package/skills/arc-module-publishing/SKILL.md +243 -0
  139. package/skills/arc-module-publishing/references/authoring-modules.md +327 -0
  140. package/skills/arc-module-publishing/references/package-manifest.md +157 -0
  141. package/skills/arc-module-publishing/references/publishing-workflow.md +182 -0
  142. package/dist/preloadResources-CHw3_oA_.mjs +0 -45
  143. package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
@@ -1,5 +1,4 @@
1
1
  import { FastifyPluginAsync } from "fastify";
2
-
3
2
  //#region src/discovery/index.d.ts
4
3
  /** A discovered resource — must have toPlugin() method */
5
4
  interface DiscoverableResource {
@@ -1,7 +1,6 @@
1
- import { p as RegistryEntry } from "../index-_--GyzcO.mjs";
1
+ import { p as RegistryEntry } from "../index-DbtgCOCj.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "../externalPaths-BE6wFpYv.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
-
5
4
  //#region src/docs/openapi/types.d.ts
6
5
  /**
7
6
  * OpenAPI 3.0 type primitives used by arc's spec emitter.
@@ -1,5 +1,5 @@
1
1
  import { t as getUserRoles } from "../types-XTApa8b6.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-Dq6RbEGK.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-BRpMZD7C.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,5 +1,4 @@
1
1
  import { FastifyPluginAsync, FastifyRequest } from "fastify";
2
-
3
2
  //#region src/scope/elevation.d.ts
4
3
  interface ElevationOptions {
5
4
  /** Roles that can use elevation (default: ['superadmin']) */
@@ -1,135 +1,5 @@
1
- import { FastifyPluginAsync, FastifyRequest } from "fastify";
2
-
3
- //#region src/encryption/types.d.ts
4
- /** JWE key-management algorithm — the protected-header `alg`. */
5
- type JweAlg = "RSA-OAEP-256" | "RSA-OAEP-384" | "RSA-OAEP-512" | "ECDH-ES" | "ECDH-ES+A256KW" | "A256GCMKW" | "dir";
6
- /** JWE content-encryption algorithm — the protected-header `enc`. AEAD only. */
7
- type JweEnc = "A128GCM" | "A192GCM" | "A256GCM";
8
- /** Encryption strategy for a route's response. */
9
- type EncryptionMode = "jwe" | "fields";
10
- /**
11
- * Opaque key material. For `jwe` mode this is a `jose` `KeyLike` /
12
- * `CryptoKey` / `Uint8Array`; for `fields` mode a 32-byte secret
13
- * (`Buffer` / `Uint8Array`). Kept `unknown` at the contract layer so the
14
- * `KeyProvider` interface carries no hard `jose` type dependency — hosts
15
- * without `jose` installed can still implement field-mode providers.
16
- */
17
- type KeyMaterial = unknown;
18
- /** A key paired with its identifier — what encrypt operations consume. */
19
- interface ActiveKey {
20
- /** Key id, stamped into the JWE `kid` header / field envelope for rotation. */
21
- readonly kid: string;
22
- /** The key material (public key for JWE, 32-byte secret for fields). */
23
- readonly key: KeyMaterial;
24
- /** Optional per-key `alg` override (else the plugin default applies). */
25
- readonly alg?: JweAlg;
26
- }
27
- /** Context handed to the key provider so resolution can be request-scoped. */
28
- interface KeyContext {
29
- readonly request: FastifyRequest;
30
- }
31
- type MaybePromise<T> = T | Promise<T>;
32
- /**
33
- * Pluggable key resolution. Arc owns the request-time plumbing — `kid` →
34
- * key, algorithm allowlist enforcement; the host owns the trust store —
35
- * which keys are valid and where they live (file / env / AWS KMS / GCP KMS
36
- * / Vault). Encryption and decryption are deliberately separate operations
37
- * because they often use DIFFERENT keys (JWE: a public key encrypts the
38
- * outbound response, a private key by `kid` decrypts the inbound request —
39
- * exactly Visa's two-key-pair model).
40
- *
41
- * Rotation mirrors `@fastify/secure-session`: the active key encrypts; any
42
- * key resolvable by `kid` can decrypt, so you can roll keys without dropping
43
- * in-flight traffic.
44
- */
45
- interface KeyProvider {
46
- /** Active key for encrypting OUTBOUND responses. */
47
- encryptionKey(ctx: KeyContext): MaybePromise<ActiveKey>;
48
- /** Resolve a DECRYPTION key by the inbound message's `kid`. */
49
- decryptionKey(kid: string, ctx: KeyContext): MaybePromise<KeyMaterial | undefined>;
50
- }
51
- /**
52
- * Per-resource encryption directive — the typed slice plugins register on
53
- * `ResourceExtensions`. Declared on a resource via
54
- * `defineResource({ extensions: { encryption: { … } } })`; arc threads it
55
- * to request time on `request.routeOptions.config.arcExtensions.encryption`.
56
- */
57
- interface EncryptionDirective {
58
- /** Strategy. Defaults to the plugin-level `mode` (default `'jwe'`). */
59
- readonly mode?: EncryptionMode;
60
- /**
61
- * For `mode: 'fields'` — dot-paths of fields to encrypt, resolved against
62
- * the serialized response body (e.g. `'cardNumber'`, `'account.iban'`).
63
- * Ignored for `mode: 'jwe'` (the whole body is encrypted).
64
- */
65
- readonly fields?: readonly string[];
66
- /**
67
- * Explicit opt-out — set `false` to disable encryption for this resource
68
- * even when a plugin-level default (`routes`) would otherwise apply.
69
- */
70
- readonly enabled?: boolean;
71
- }
72
- /**
73
- * Plugin-level options. `keyProvider` is the only required field; everything
74
- * else has a secure default.
75
- */
76
- interface EncryptionOptions {
77
- /**
78
- * Master on/off. Default `true`. Set `false` to register the plugin as a
79
- * typed no-op (config-gated environments) — same pattern as
80
- * `idempotencyPlugin`.
81
- */
82
- readonly enabled?: boolean;
83
- /** Key provider — resolves `kid` → key for encrypt/decrypt. Required. */
84
- readonly keyProvider: KeyProvider;
85
- /** Default mode when a route opts in without naming one. Default `'jwe'`. */
86
- readonly mode?: EncryptionMode;
87
- /** JWE key-management algorithm. Default `'RSA-OAEP-256'`. */
88
- readonly alg?: JweAlg;
89
- /** JWE content-encryption algorithm. Default `'A256GCM'`. */
90
- readonly enc?: JweEnc;
91
- /**
92
- * Inbound-decryption algorithm allowlists — block algorithm-substitution
93
- * downgrade attacks. Default `[alg]` / `[enc]`. Set explicitly only when a
94
- * client legitimately uses more than one algorithm.
95
- */
96
- readonly allowedAlgs?: readonly JweAlg[];
97
- readonly allowedEncs?: readonly JweEnc[];
98
- /**
99
- * Fallback opt-in for routes that don't declare
100
- * `extensions.encryption`. Return a directive (or `true` for the default
101
- * mode) to encrypt, `false`/`undefined` to skip. The per-resource
102
- * `extensions.encryption` ALWAYS wins when present. Use this to scope ALE
103
- * to sensitive prefixes without touching every resource.
104
- *
105
- * @example confine ALE to `/payments` and `/accounts`
106
- * ```ts
107
- * routes: (req) => /^\/(payments|accounts)\b/.test(req.url)
108
- * ```
109
- */
110
- readonly routes?: (request: FastifyRequest) => EncryptionDirective | boolean | undefined;
111
- /** Decrypt inbound `application/jose` request bodies. Default `true`. */
112
- readonly decryptRequests?: boolean;
113
- /** Media type that signals an encrypted REQUEST body. Default `'application/jose'`. */
114
- readonly contentType?: string;
115
- /** Content-Type set on full-body JWE RESPONSES. Default `'application/jose'`. */
116
- readonly responseContentType?: string;
117
- /**
118
- * Response header advertising that the body is encrypted, so generic
119
- * client SDKs know to decrypt. Default `'x-encrypted'`; set `false` to omit.
120
- */
121
- readonly flagHeader?: string | false;
122
- }
123
- /** Request decorations added by the plugin. */
124
- declare module "fastify" {
125
- interface FastifyRequest {
126
- /** `true` when the inbound body was decrypted from a JWE by this plugin. */
127
- requestDecrypted?: boolean;
128
- /** `true` when the outbound body was encrypted by this plugin. */
129
- responseEncrypted?: boolean;
130
- }
131
- }
132
- //#endregion
1
+ import { a as JweAlg, c as KeyMaterial, i as EncryptionOptions, l as KeyProvider, n as EncryptionDirective, o as JweEnc, r as EncryptionMode, s as KeyContext, t as ActiveKey } from "../types-Cc91nXuA.mjs";
2
+ import { FastifyPluginAsync } from "fastify";
133
3
  //#region src/encryption/encryptionPlugin.d.ts
134
4
  declare const encryptionPlugin: FastifyPluginAsync<EncryptionOptions>;
135
5
  declare const _default: FastifyPluginAsync<EncryptionOptions>;
@@ -207,16 +77,4 @@ interface StaticKeyProviderOptions {
207
77
  */
208
78
  declare function createStaticKeyProvider(options: StaticKeyProviderOptions): KeyProvider;
209
79
  //#endregion
210
- //#region src/encryption/index.d.ts
211
- declare module "../types/resource.js" {
212
- interface ResourceExtensions {
213
- /**
214
- * Encrypt this resource's responses — full-body JWE or specific fields.
215
- * Threaded to request time on
216
- * `request.routeOptions.config.arcExtensions.encryption`.
217
- */
218
- encryption?: EncryptionDirective;
219
- }
220
- }
221
- //#endregion
222
80
  export { type ActiveKey, type EncryptionDirective, type EncryptionMode, type EncryptionOptions, type JweAlg, type JweEnc, type KeyContext, type KeyMaterial, type KeyProvider, type ParsedFieldEnvelope, type StaticKeyProviderOptions, createStaticKeyProvider, decryptField, _default as default, encryptField, encryptionPlugin, parseFieldEnvelope };
@@ -1,3 +1,5 @@
1
+ import { t as ArcError } from "../errors-DkO8Nk6-.mjs";
2
+ import { t as parseJsonBody } from "../jsonBody-BlzAoGCe.mjs";
1
3
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
4
  import fp from "fastify-plugin";
3
5
  //#region src/encryption/fieldCipher.ts
@@ -182,17 +184,19 @@ const encryptionPlugin = async (fastify, opts) => {
182
184
  }
183
185
  if (decryptRequests) fastify.addContentTypeParser(contentType, { parseAs: "string" }, async (request, body) => {
184
186
  const provider = keyProvider;
185
- const plaintext = await jweDecrypt(body, (kid) => provider.decryptionKey(kid, { request }), allowedAlgs, allowedEncs);
186
- request.requestDecrypted = true;
187
- if (plaintext.length === 0) return void 0;
187
+ let plaintext;
188
188
  try {
189
- return JSON.parse(plaintext);
189
+ plaintext = await jweDecrypt(body, (kid) => provider.decryptionKey(kid, { request }), allowedAlgs, allowedEncs);
190
190
  } catch (cause) {
191
- throw Object.assign(/* @__PURE__ */ new Error("[arc/encryption] decrypted request body is not valid JSON"), {
191
+ throw new ArcError("Invalid encrypted request body", {
192
+ code: "arc.bad_request",
192
193
  statusCode: 400,
193
194
  cause
194
195
  });
195
196
  }
197
+ request.requestDecrypted = true;
198
+ if (plaintext.length === 0) return void 0;
199
+ return parseJsonBody(plaintext, "Decrypted request body is not valid JSON");
196
200
  });
197
201
  fastify.addHook("preSerialization", async (request, reply, payload) => {
198
202
  const directive = resolveDirective(request, defaultMode, fallback);
@@ -91,7 +91,7 @@ function mongooseValidationDetails(errors) {
91
91
  }
92
92
  async function errorHandlerPluginFn(fastify, options = {}) {
93
93
  const isProduction = process.env.NODE_ENV === "production";
94
- const { includeStack = !isProduction, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
94
+ const { includeStack = !isProduction, exposeInternalMessages = !isProduction, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
95
95
  fastify.setErrorHandler(async (error, request, reply) => {
96
96
  if (onError) try {
97
97
  await onError(error, request);
@@ -112,6 +112,7 @@ async function errorHandlerPluginFn(fastify, options = {}) {
112
112
  ...Object.keys(meta).length > 0 ? { meta } : {}
113
113
  };
114
114
  const status = wire.status ?? 500;
115
+ if (status >= 500 && wire.code === "arc.internal_error" && !exposeInternalMessages) wire.message = "Internal Server Error";
115
116
  if (status >= 500) request.log.error({
116
117
  err: error,
117
118
  status
@@ -120,11 +121,31 @@ async function errorHandlerPluginFn(fastify, options = {}) {
120
121
  err: error,
121
122
  status
122
123
  }, "Client error");
123
- if (isArcError(error) && error.cause) request.log.error({ cause: error.cause }, "Error cause chain");
124
+ if (isArcError(error) && error.cause) {
125
+ const causeLevel = status >= 500 ? "error" : "warn";
126
+ request.log[causeLevel]({ cause: error.cause }, "Error cause chain");
127
+ }
124
128
  return reply.status(status).send(wire);
125
129
  });
126
130
  }
127
131
  /**
132
+ * Is `code` a caller-defined DOMAIN error code that is safe to surface on the
133
+ * wire, as opposed to a framework/runtime code that must never leak?
134
+ *
135
+ * Rejects Fastify internals (`FST_*`) and Node system errnos (`ECONNREFUSED`,
136
+ * `ENOENT`, …). Domain codes in the wild use separators (`PURCHASE_NOT_FOUND`,
137
+ * `access.forbidden`) so they pass; a bare all-caps single word that collides
138
+ * with the Node-errno shape (e.g. `EXPIRED`) is conservatively treated as a
139
+ * runtime code — use a separatored code (`TOKEN_EXPIRED`) to surface it, or
140
+ * throw via `createDomainError` which bypasses this heuristic entirely.
141
+ */
142
+ function isDomainErrorCode(code) {
143
+ if (typeof code !== "string" || code.length === 0) return false;
144
+ if (code.startsWith("FST_")) return false;
145
+ if (/^E[A-Z]{2,}$/.test(code)) return false;
146
+ return true;
147
+ }
148
+ /**
128
149
  * Single-pass error → `ErrorContract` classifier. Dispatch order is fixed:
129
150
  *
130
151
  * 1. Class-based mappers (`instanceof`) — user-registered domain errors
@@ -167,7 +188,7 @@ function classify(error, ctx) {
167
188
  };
168
189
  }
169
190
  if (typeof fastifyErr.statusCode === "number") return {
170
- code: statusToArcCode(fastifyErr.statusCode),
191
+ code: (isDomainErrorCode(fastifyErr.code) ? fastifyErr.code : void 0) ?? statusToArcCode(fastifyErr.statusCode),
171
192
  message: error.message || "Error",
172
193
  status: fastifyErr.statusCode
173
194
  };
@@ -1,6 +1,5 @@
1
1
  import { ErrorDetail } from "@classytic/repo-core/errors";
2
2
  import { FastifyInstance, FastifyRequest } from "fastify";
3
-
4
3
  //#region src/plugins/errorHandler.d.ts
5
4
  /**
6
5
  * Class-based error mapper — `instanceof` check converts a thrown class
@@ -19,6 +18,19 @@ interface ErrorMapper<T extends Error = Error> {
19
18
  interface ErrorHandlerOptions {
20
19
  /** Include `meta.stack` on the wire (defaults to `NODE_ENV !== 'production'`). */
21
20
  includeStack?: boolean;
21
+ /**
22
+ * Ship the raw `error.message` of UNCLASSIFIED 500s on the wire
23
+ * (defaults to `NODE_ENV !== 'production'`).
24
+ *
25
+ * Only the `arc.internal_error` fallback is affected — an unclassified
26
+ * throw carries whatever some driver/library put in the message (connection
27
+ * strings, file paths, SQL fragments), which must not reach clients in
28
+ * production. Deliberate 5xx contracts are untouched: `ArcError` /
29
+ * domain-coded throws (`payment.gateway.timeout`, …) keep their message
30
+ * because the thrower chose it for the client. The raw message is always
31
+ * logged via `request.log.error({ err })` regardless of this flag.
32
+ */
33
+ exposeInternalMessages?: boolean;
22
34
  /** Custom callback fired for every error — log to Sentry / Datadog / etc. */
23
35
  onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
24
36
  /** Map by `error.name` string. Lower priority than `errorMappers`. */
@@ -1,5 +1,4 @@
1
1
  import { HttpError } from "@classytic/repo-core/errors";
2
-
3
2
  //#region src/utils/errors.d.ts
4
3
  interface ErrorOptions {
5
4
  code?: string;
@@ -1,6 +1,5 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-IEn7Tn-Q.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-QmY7A258.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
-
4
3
  //#region src/events/defineEvent.d.ts
5
4
  interface EventSchema {
6
5
  type: "object";
@@ -249,7 +248,8 @@ interface EventPluginOptions {
249
248
  * failed events are published to the `$deadLetter` event type by default.
250
249
  */
251
250
  deadLetterQueue?: {
252
- /** Custom store function. If omitted, publishes to '$deadLetter' event type. */store?: (event: DomainEvent, errors: Error[]) => void | Promise<void>;
251
+ /** Custom store function. If omitted, publishes to '$deadLetter' event type. */
252
+ store?: (event: DomainEvent, errors: Error[]) => void | Promise<void>;
253
253
  };
254
254
  /** Callback after successful publish (for metrics/tracking) */
255
255
  onPublish?: (event: DomainEvent) => void;
@@ -302,9 +302,13 @@ interface EventPluginOptions {
302
302
  declare module "fastify" {
303
303
  interface FastifyInstance {
304
304
  events: {
305
- /** Publish an event */publish: <T>(type: string, payload: T, meta?: Partial<DomainEvent["meta"]>) => Promise<void>; /** Subscribe to events */
306
- subscribe: (pattern: string, handler: EventHandler) => Promise<() => void>; /** Get transport name */
307
- transportName: string; /** Event registry for introspection (undefined when no registry configured) */
305
+ /** Publish an event */
306
+ publish: <T>(type: string, payload: T, meta?: Partial<DomainEvent["meta"]>) => Promise<void>;
307
+ /** Subscribe to events */
308
+ subscribe: (pattern: string, handler: EventHandler) => Promise<() => void>;
309
+ /** Get transport name */
310
+ transportName: string;
311
+ /** Event registry for introspection (undefined when no registry configured) */
308
312
  registry?: EventRegistry;
309
313
  };
310
314
  }
@@ -1,9 +1,8 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions, t as DeadLetteredEvent } from "../EventTransport-IEn7Tn-Q.mjs";
2
- import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-DbBFd3x_.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions, t as DeadLetteredEvent } from "../EventTransport-QmY7A258.mjs";
2
+ import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-A6EbthXR.mjs";
3
3
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
4
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-DsP1vqZB.mjs";
4
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-iVR2l7aS.mjs";
5
5
  import { RepositoryLike } from "@classytic/repo-core/adapter";
6
-
7
6
  //#region src/events/eventTypes.d.ts
8
7
  /**
9
8
  * Event Type Constants and Helpers
@@ -38,14 +37,18 @@ type CrudEventSuffix = (typeof CRUD_EVENT_SUFFIXES)[number];
38
37
  declare function crudEventType(resource: string, suffix: CrudEventSuffix): string;
39
38
  /** Arc framework lifecycle events — emitted automatically by the framework */
40
39
  declare const ARC_LIFECYCLE_EVENTS: Readonly<{
41
- /** Emitted when a resource plugin is registered */readonly RESOURCE_REGISTERED: "arc.resource.registered"; /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
40
+ /** Emitted when a resource plugin is registered */
41
+ readonly RESOURCE_REGISTERED: "arc.resource.registered";
42
+ /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
42
43
  readonly READY: "arc.ready";
43
44
  }>;
44
45
  /** Type for Arc lifecycle event names */
45
46
  type ArcLifecycleEvent = (typeof ARC_LIFECYCLE_EVENTS)[keyof typeof ARC_LIFECYCLE_EVENTS];
46
47
  /** Cache-specific event types for observability and external triggers */
47
48
  declare const CACHE_EVENTS: Readonly<{
48
- /** Emitted when a resource's cache version is bumped */readonly VERSION_BUMPED: "arc.cache.version.bumped"; /** Emitted when a tag version is bumped */
49
+ /** Emitted when a resource's cache version is bumped */
50
+ readonly VERSION_BUMPED: "arc.cache.version.bumped";
51
+ /** Emitted when a tag version is bumped */
49
52
  readonly TAG_VERSION_BUMPED: "arc.cache.tag.bumped";
50
53
  }>;
51
54
  /** Type for cache event names */
@@ -1,2 +1,2 @@
1
- import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-DsP1vqZB.mjs";
1
+ import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-iVR2l7aS.mjs";
2
2
  export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
@@ -1,5 +1,4 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-IEn7Tn-Q.mjs";
2
-
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-QmY7A258.mjs";
3
2
  //#region src/events/transports/redis.d.ts
4
3
  interface RedisLike {
5
4
  publish(channel: string, message: string): Promise<number>;
@@ -1,14 +1,15 @@
1
- import { a as getModuleExports, c as LoadResourcesOptions, d as loadResources, i as defineModule, l as ResourceLike, n as ArcModuleInput, o as orderModules, r as ArcModuleRegistry, s as resolveModule, t as ArcModule, u as ResourceModule } from "../module-JYH9V0I8.mjs";
2
- import { a as CustomPluginAuthOption, c as RawBodyOptions, d as preloadResourcesAsync, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as preloadResources } from "../types-BHMKlMBU.mjs";
1
+ import { a as getModuleExports, c as LoadResourcesOptions, d as loadResources, i as defineModule, l as ResourceLike, n as ArcModuleInput, o as orderModules, r as ArcModuleRegistry, s as resolveModule, t as ArcModule, u as ResourceModule } from "../module-B4u10iOJ.mjs";
2
+ import "../fastify-augmentation-9_fuetB4.mjs";
3
+ import { a as AuthOption, c as CustomPluginAuthOption, d as preloadResourcesAsync, i as UnderPressureOptions, l as JwtAuthOption, n as MultipartOptions, o as BetterAuthOption, r as RawBodyOptions, s as CustomAuthenticatorOption, t as CreateAppOptions, u as preloadResources } from "../index-BEu4wjbL.mjs";
4
+ import { g as HealthCheck } from "../versioning-DLwklPlG.mjs";
3
5
  import { FastifyInstance, FastifyServerOptions } from "fastify";
4
-
5
6
  //#region src/factory/createApp.d.ts
6
7
  /**
7
8
  * Create a production-ready Fastify application with Arc framework.
8
9
  *
9
10
  * Boot order:
10
11
  * ```
11
- * 0. Logger, validation, preset merge
12
+ * 0. Logger, validation → beforeBoot() (DB connect etc.), preset merge
12
13
  * 1. Create Fastify instance
13
14
  * 2. Security plugins (Helmet, CORS, Rate Limit) — opt-out
14
15
  * 3. Utility plugins (Under Pressure, Sensible, Multipart, Raw Body)
@@ -32,6 +33,29 @@ declare const ArcFactory: {
32
33
  testing(options: Omit<CreateAppOptions, "preset">): Promise<FastifyInstance>;
33
34
  };
34
35
  //#endregion
36
+ //#region src/factory/createWorker.d.ts
37
+ interface WorkerHealthOptions {
38
+ /** Port for the worker's ONLY listener (liveness/readiness probes). */
39
+ port: number;
40
+ /** Bind host. Default `0.0.0.0`. */
41
+ host?: string;
42
+ /** Readiness checks — same contract as the API role's healthPlugin. */
43
+ checks?: HealthCheck[];
44
+ }
45
+ interface CreateWorkerOptions {
46
+ /** Opt-in probe listener. Omit = the worker binds NOTHING. */
47
+ health?: WorkerHealthOptions;
48
+ }
49
+ interface ArcWorker {
50
+ /** The underlying Fastify instance (decorations, inject, logs). */
51
+ app: FastifyInstance;
52
+ /** Typed module-export accessor — same contract as `getModuleExports`. */
53
+ exports<TExports = unknown>(name: string): TExports;
54
+ /** Close app → module onClose (reverse) → app onClose. Idempotent. */
55
+ close(): Promise<void>;
56
+ }
57
+ declare function createWorker(options: CreateAppOptions, workerOptions?: CreateWorkerOptions): Promise<ArcWorker>;
58
+ //#endregion
35
59
  //#region src/factory/edge.d.ts
36
60
  interface FetchHandlerOptions {
37
61
  /**
@@ -91,9 +115,6 @@ declare const testingPreset: Partial<CreateAppOptions>;
91
115
  * when using `toFetchHandler()` (routes through Fastify's `.inject()`).
92
116
  */
93
117
  declare const edgePreset: Partial<CreateAppOptions>;
94
- /**
95
- * Get preset by name
96
- */
97
- declare function getPreset(name: "production" | "development" | "testing" | "edge"): Partial<CreateAppOptions>;
118
+ declare function getPreset(name: "production" | "development" | "testing" | "edge" | "worker"): Partial<CreateAppOptions>;
98
119
  //#endregion
99
- export { ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, createApp, defineModule, developmentPreset, edgePreset, getModuleExports, getPreset, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveModule, testingPreset, toFetchHandler };
120
+ export { ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type ArcWorker, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CreateWorkerOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, type WorkerHealthOptions, createApp, createWorker, defineModule, developmentPreset, edgePreset, getModuleExports, getPreset, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveModule, testingPreset, toFetchHandler };
@@ -1,6 +1,37 @@
1
- import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-CHw3_oA_.mjs";
2
- import { a as getModuleExports, c as developmentPreset, d as productionPreset, f as testingPreset, i as defineModule, l as edgePreset, n as createApp, o as orderModules, s as resolveModule, t as ArcFactory, u as getPreset } from "../createApp-D6uwzCKu.mjs";
3
- import { t as loadResources } from "../loadResources-B8UmpITt.mjs";
1
+ import { a as edgePreset, c as testingPreset, d as getModuleExports, f as orderModules, h as preloadResourcesAsync, i as developmentPreset, l as workerPreset, m as preloadResources, n as createApp, o as getPreset, p as resolveModule, s as productionPreset, t as ArcFactory, u as defineModule } from "../createApp-Ci7yG5gv.mjs";
2
+ import { t as healthPlugin } from "../health-ScyjIVo5.mjs";
3
+ import { t as loadResources } from "../loadResources-DpU8pfjV.mjs";
4
+ //#region src/factory/createWorker.ts
5
+ async function createWorker(options, workerOptions = {}) {
6
+ const { auth: _auth, helmet: _helmet, cors: _cors, rateLimit: _rateLimit, multipart: _multipart, rawBody: _rawBody, sensible: _sensible, underPressure: _underPressure, replyHelpers: _replyHelpers, ...runtimeOptions } = options;
7
+ const app = await createApp({
8
+ ...runtimeOptions,
9
+ preset: "worker",
10
+ ...runtimeOptions.arcPlugins ? { arcPlugins: {
11
+ ...workerPreset.arcPlugins,
12
+ ...runtimeOptions.arcPlugins
13
+ } } : {}
14
+ });
15
+ if (workerOptions.health) {
16
+ const { port, host = "0.0.0.0", checks = [] } = workerOptions.health;
17
+ await app.register(healthPlugin, { checks });
18
+ await app.listen({
19
+ port,
20
+ host
21
+ });
22
+ } else await app.ready();
23
+ let closed = false;
24
+ return {
25
+ app,
26
+ exports: (name) => getModuleExports(app, name),
27
+ close: async () => {
28
+ if (closed) return;
29
+ closed = true;
30
+ await app.close();
31
+ }
32
+ };
33
+ }
34
+ //#endregion
4
35
  //#region src/factory/edge.ts
5
36
  /**
6
37
  * Convert a Fastify app into a Web Standards fetch handler.
@@ -47,4 +78,4 @@ function toFetchHandler(app, options = {}) {
47
78
  };
48
79
  }
49
80
  //#endregion
50
- export { ArcFactory, createApp, defineModule, developmentPreset, edgePreset, getModuleExports, getPreset, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveModule, testingPreset, toFetchHandler };
81
+ export { ArcFactory, createApp, createWorker, defineModule, developmentPreset, edgePreset, getModuleExports, getPreset, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveModule, testingPreset, toFetchHandler };
@@ -1,8 +1,7 @@
1
- import { bn as HookSystem, z as ResourceRegistry } from "./index-_--GyzcO.mjs";
1
+ import { bn as HookSystem, z as ResourceRegistry } from "./index-DbtgCOCj.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
3
- import { r as ArcModuleRegistry } from "./module-JYH9V0I8.mjs";
3
+ import { r as ArcModuleRegistry } from "./module-B4u10iOJ.mjs";
4
4
  import { FastifyPluginAsync } from "fastify";
5
-
6
5
  //#region src/core/arcCorePlugin.d.ts
7
6
  interface ArcCorePluginOptions {
8
7
  /** Enable event emission for CRUD operations (requires eventPlugin) */
@@ -0,0 +1,121 @@
1
+ //#region src/permissions/fields.d.ts
2
+ /**
3
+ * Field-Level Permissions
4
+ *
5
+ * Control field visibility and writability per role.
6
+ * Integrated into the response path (read) and sanitization path (write).
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { fields, defineResource } from '@classytic/arc';
11
+ *
12
+ * const userResource = defineResource({
13
+ * name: 'user',
14
+ * adapter: userAdapter,
15
+ * fields: {
16
+ * salary: fields.visibleTo(['admin', 'hr']),
17
+ * internalNotes: fields.writableBy(['admin']),
18
+ * email: fields.redactFor(['viewer']),
19
+ * password: fields.hidden(),
20
+ * },
21
+ * });
22
+ * ```
23
+ */
24
+ type FieldPermissionType = "hidden" | "visibleTo" | "writableBy" | "redactFor";
25
+ interface FieldPermission {
26
+ readonly _type: FieldPermissionType;
27
+ readonly roles?: readonly string[];
28
+ readonly redactValue?: unknown;
29
+ }
30
+ type FieldPermissionMap = Record<string, FieldPermission>;
31
+ declare const fields: {
32
+ /**
33
+ * Field is never included in responses. Not writable via API.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * fields: { password: fields.hidden() }
38
+ * ```
39
+ */
40
+ hidden(): FieldPermission;
41
+ /**
42
+ * Field is only visible to users with specified roles.
43
+ * Other users don't see the field at all.
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * fields: { salary: fields.visibleTo(['admin', 'hr']) }
48
+ * ```
49
+ */
50
+ visibleTo(roles: readonly string[]): FieldPermission;
51
+ /**
52
+ * Field is only writable by users with specified roles.
53
+ * All users can still read the field. Users without the role
54
+ * have the field silently stripped from write operations.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * fields: { role: fields.writableBy(['admin']) }
59
+ * ```
60
+ */
61
+ writableBy(roles: readonly string[]): FieldPermission;
62
+ /**
63
+ * Field is redacted (replaced with a placeholder) for specified roles.
64
+ * Other users see the real value.
65
+ *
66
+ * @param roles - Roles that see the redacted value
67
+ * @param redactValue - Replacement value (default: '***')
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * fields: {
72
+ * email: fields.redactFor(['viewer']),
73
+ * ssn: fields.redactFor(['basic'], '***-**-****'),
74
+ * }
75
+ * ```
76
+ */
77
+ redactFor(roles: readonly string[], redactValue?: unknown): FieldPermission;
78
+ };
79
+ /**
80
+ * Apply field-level READ permissions to a response object.
81
+ * Strips hidden fields, enforces visibility, and applies redaction.
82
+ *
83
+ * @param data - The response object (mutated in place for performance)
84
+ * @param fieldPermissions - Field permission map from resource config
85
+ * @param userRoles - Current user's roles (empty array for unauthenticated)
86
+ * @returns The filtered object
87
+ */
88
+ declare function applyFieldReadPermissions<T extends Record<string, unknown>>(data: T, fieldPermissions: FieldPermissionMap, userRoles: readonly string[]): T;
89
+ /**
90
+ * Result of applying write permissions — includes both the filtered body
91
+ * and the list of fields that were stripped so callers can decide whether
92
+ * to reject the request (secure default) or silently strip (legacy).
93
+ */
94
+ interface FieldWritePermissionResult<T extends Record<string, unknown>> {
95
+ readonly body: T;
96
+ readonly deniedFields: readonly string[];
97
+ }
98
+ /**
99
+ * Apply field-level WRITE permissions to request body.
100
+ *
101
+ * Returns both the filtered body and the list of denied fields. Callers are
102
+ * expected to reject the request when `deniedFields.length > 0` — silently
103
+ * stripping fields hides misconfigurations and real attacks. See
104
+ * `BodySanitizer` for the default policy.
105
+ *
106
+ * @param body - The request body (returns a new filtered copy)
107
+ * @param fieldPermissions - Field permission map from resource config
108
+ * @param userRoles - Current user's roles
109
+ */
110
+ declare function applyFieldWritePermissions<T extends Record<string, unknown>>(body: T, fieldPermissions: FieldPermissionMap, userRoles: readonly string[]): FieldWritePermissionResult<T>;
111
+ /**
112
+ * Resolve effective roles by merging global user roles with org-level roles.
113
+ *
114
+ * Global roles come from `req.user.role` (normalized via getUserRoles()).
115
+ * Org roles come from `req.context.orgRoles` (set by BA adapter's org bridge).
116
+ *
117
+ * When no org context exists, returns global roles only — backward compatible.
118
+ */
119
+ declare function resolveEffectiveRoles(userRoles: readonly string[], orgRoles: readonly string[]): string[];
120
+ //#endregion
121
+ export { applyFieldWritePermissions as a, applyFieldReadPermissions as i, FieldPermissionMap as n, fields as o, FieldPermissionType as r, resolveEffectiveRoles as s, FieldPermission as t };