@classytic/arc 2.6.3 → 2.7.1

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 (135) hide show
  1. package/README.md +84 -1
  2. package/dist/{BaseController-DzRtluEF.mjs → BaseController-CpMfCXdn.mjs} +134 -16
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/adapters/index.mjs +1 -1
  5. package/dist/{adapters-gM-WYjNe.mjs → adapters-BxGgSHjj.mjs} +1 -9
  6. package/dist/applyPermissionResult-D6GPMsvh.mjs +37 -0
  7. package/dist/audit/index.d.mts +1 -1
  8. package/dist/audit/index.mjs +1 -1
  9. package/dist/audit/mongodb.d.mts +1 -1
  10. package/dist/audit/mongodb.mjs +1 -1
  11. package/dist/auth/index.d.mts +4 -4
  12. package/dist/auth/index.mjs +7 -6
  13. package/dist/auth/mongoose.d.mts +191 -0
  14. package/dist/auth/mongoose.mjs +73 -0
  15. package/dist/auth/redis-session.d.mts +1 -1
  16. package/dist/{betterAuthOpenApi-lz0IRbXJ.mjs → betterAuthOpenApi-CCw3YX0g.mjs} +1 -1
  17. package/dist/cache/index.d.mts +2 -2
  18. package/dist/cache/index.mjs +2 -2
  19. package/dist/cli/commands/docs.mjs +2 -2
  20. package/dist/cli/commands/generate.mjs +1 -1
  21. package/dist/cli/commands/init.mjs +7 -5
  22. package/dist/cli/commands/introspect.mjs +1 -1
  23. package/dist/core/index.d.mts +3 -3
  24. package/dist/core/index.mjs +4 -4
  25. package/dist/{core-C1XCMtqM.mjs → core-BWekSEju.mjs} +41 -13
  26. package/dist/{createApp-D2w0LdYJ.mjs → createApp-B_nvKNAQ.mjs} +11 -11
  27. package/dist/{defineResource-wWMBB4GP.mjs → defineResource-DZzyl4a4.mjs} +42 -37
  28. package/dist/docs/index.d.mts +2 -2
  29. package/dist/docs/index.mjs +1 -1
  30. package/dist/dynamic/index.d.mts +2 -2
  31. package/dist/dynamic/index.mjs +2 -2
  32. package/dist/{elevation-BEdACOLB.mjs → elevation-By_p2lnn.mjs} +1 -1
  33. package/dist/elevation-Dm-HTBCt.d.mts +23 -0
  34. package/dist/{errorHandler-Do4vVQ1f.d.mts → errorHandler-COa51ho_.d.mts} +1 -1
  35. package/dist/{errorHandler-r2595m8T.mjs → errorHandler-DXUttWEO.mjs} +1 -1
  36. package/dist/{eventPlugin-DW45v4V5.d.mts → eventPlugin-BgLxJkIB.d.mts} +1 -1
  37. package/dist/{eventPlugin-Ba00swHF.mjs → eventPlugin-DsaNNXzZ.mjs} +1 -1
  38. package/dist/events/index.d.mts +3 -3
  39. package/dist/events/index.mjs +1 -1
  40. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  41. package/dist/events/transports/redis.d.mts +1 -1
  42. package/dist/factory/index.d.mts +1 -1
  43. package/dist/factory/index.mjs +1 -1
  44. package/dist/hooks/index.d.mts +1 -1
  45. package/dist/hooks/index.mjs +1 -1
  46. package/dist/idempotency/index.d.mts +3 -3
  47. package/dist/idempotency/mongodb.d.mts +1 -1
  48. package/dist/idempotency/redis.d.mts +1 -1
  49. package/dist/index-BYpRGXif.d.mts +640 -0
  50. package/dist/{index-gz6iuzCp.d.mts → index-KXM8_JmQ.d.mts} +47 -4
  51. package/dist/{index-CHeJa4Zd.d.mts → index-StgFaQKD.d.mts} +1 -1
  52. package/dist/index.d.mts +8 -8
  53. package/dist/index.mjs +10 -9
  54. package/dist/integrations/event-gateway.d.mts +1 -1
  55. package/dist/integrations/event-gateway.mjs +1 -1
  56. package/dist/integrations/index.d.mts +1 -1
  57. package/dist/integrations/mcp/index.d.mts +2 -2
  58. package/dist/integrations/mcp/index.mjs +1 -1
  59. package/dist/integrations/mcp/testing.d.mts +1 -1
  60. package/dist/integrations/mcp/testing.mjs +1 -1
  61. package/dist/{interface-DYH8AXGe.d.mts → interface-Dwzqt4mn.d.mts} +150 -14
  62. package/dist/{mongodb-pMvOlR5_.d.mts → mongodb-Bq90j-Uj.d.mts} +1 -1
  63. package/dist/{mongodb-kltrBPa1.d.mts → mongodb-DdyYlIXg.d.mts} +1 -1
  64. package/dist/{openapi-CBmZ6EQN.mjs → openapi-C5UhIeWu.mjs} +1 -1
  65. package/dist/org/index.d.mts +2 -2
  66. package/dist/org/index.mjs +1 -1
  67. package/dist/permissions/index.d.mts +4 -4
  68. package/dist/permissions/index.mjs +3 -2
  69. package/dist/{permissions-C8ImI8gC.mjs → permissions-CH4cNwJi.mjs} +358 -64
  70. package/dist/plugins/index.d.mts +4 -4
  71. package/dist/plugins/index.mjs +10 -10
  72. package/dist/plugins/response-cache.mjs +1 -1
  73. package/dist/plugins/tracing-entry.d.mts +1 -1
  74. package/dist/plugins/tracing-entry.mjs +1 -1
  75. package/dist/policies/index.d.mts +1 -1
  76. package/dist/presets/index.d.mts +3 -3
  77. package/dist/presets/index.mjs +1 -1
  78. package/dist/presets/multiTenant.d.mts +53 -3
  79. package/dist/presets/multiTenant.mjs +89 -47
  80. package/dist/{presets-BMfdy34e.mjs → presets-BFrGvvjL.mjs} +2 -2
  81. package/dist/{queryCachePlugin-DcmETvcB.d.mts → queryCachePlugin-Bw8XyJpX.d.mts} +1 -1
  82. package/dist/{queryCachePlugin-XtFplYO9.mjs → queryCachePlugin-CwTpR04-.mjs} +2 -2
  83. package/dist/{redis-D0Qc-9EW.d.mts → redis-CyCntzTO.d.mts} +1 -1
  84. package/dist/{redis-stream-BW9UKLZM.d.mts → redis-stream-We_Ucl9-.d.mts} +1 -1
  85. package/dist/registry/index.d.mts +1 -1
  86. package/dist/registry/index.mjs +2 -2
  87. package/dist/{resourceToTools-nCJWnG1r.mjs → resourceToTools-CkVSSzKg.mjs} +64 -21
  88. package/dist/rpc/index.d.mts +1 -1
  89. package/dist/rpc/index.mjs +1 -1
  90. package/dist/scope/index.d.mts +3 -2
  91. package/dist/scope/index.mjs +4 -3
  92. package/dist/{sse-BF7GR7IB.mjs → sse-Bp3dabF1.mjs} +2 -2
  93. package/dist/testing/index.d.mts +2 -2
  94. package/dist/testing/index.mjs +1 -1
  95. package/dist/types/index.d.mts +4 -3
  96. package/dist/types/index.mjs +1 -1
  97. package/dist/types-AOD8fxIw.mjs +229 -0
  98. package/dist/types-CNEbix8T.d.mts +286 -0
  99. package/dist/{types-B4_TDdPe.d.mts → types-ClmkMDK1.d.mts} +1 -1
  100. package/dist/{types-By-5mIfn.d.mts → types-D0qf0Mf4.d.mts} +9 -9
  101. package/dist/types-DPsC0taJ.d.mts +178 -0
  102. package/dist/utils/index.d.mts +3 -3
  103. package/dist/utils/index.mjs +5 -5
  104. package/package.json +17 -5
  105. package/skills/arc/SKILL.md +253 -6
  106. package/skills/arc/references/multi-tenancy.md +208 -0
  107. package/dist/elevation-C_taLQrM.d.mts +0 -147
  108. package/dist/index-NGZksqM5.d.mts +0 -398
  109. package/dist/types-BNUccdcf.d.mts +0 -101
  110. package/dist/types-BhtYdxZU.mjs +0 -91
  111. /package/dist/{EventTransport-wc5hSLik.d.mts → EventTransport-CUpRK_Lg.d.mts} +0 -0
  112. /package/dist/{HookSystem-COkyWztM.mjs → HookSystem-D7lfx--K.mjs} +0 -0
  113. /package/dist/{ResourceRegistry-C6ngvOnn.mjs → ResourceRegistry-DsHiG9cL.mjs} +0 -0
  114. /package/dist/{caching-BSXB-Xr7.mjs → caching-5DtLwIqb.mjs} +0 -0
  115. /package/dist/{circuitBreaker-JP2GdJ4b.d.mts → circuitBreaker-DwxrljLB.d.mts} +0 -0
  116. /package/dist/{circuitBreaker-BOBOpN2w.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
  117. /package/dist/{errors-CcVbl1-T.d.mts → errors-CCSsMpXE.d.mts} +0 -0
  118. /package/dist/{errors-NoQKsbAT.mjs → errors-Cg58SLNi.mjs} +0 -0
  119. /package/dist/{externalPaths-DpO-s7r8.d.mts → externalPaths-Dg7OLsKo.d.mts} +0 -0
  120. /package/dist/{fields-DFwdaWCq.d.mts → fields-CYuLMJPD.d.mts} +0 -0
  121. /package/dist/{interface-gr-7qo9j.d.mts → interface-B9rHWPxD.d.mts} +0 -0
  122. /package/dist/{interface-D_BWALyZ.d.mts → interface-CnluRL4_.d.mts} +0 -0
  123. /package/dist/{logger-Dz3j1ItV.mjs → logger-DLg8-Ueg.mjs} +0 -0
  124. /package/dist/{memory-BFAYkf8H.mjs → memory-Cp7_cAko.mjs} +0 -0
  125. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  126. /package/dist/{mongodb-BuQ7fNTg.mjs → mongodb-mlgxkYI3.mjs} +0 -0
  127. /package/dist/{pluralize-CcT6qF0a.mjs → pluralize-COpOVar8.mjs} +0 -0
  128. /package/dist/{registry-I-ogLgL9.mjs → registry-B3lRFBWo.mjs} +0 -0
  129. /package/dist/{requestContext-DYtmNpm5.mjs → requestContext-xHIKedG6.mjs} +0 -0
  130. /package/dist/{schemaConverter-DjzHpFam.mjs → schemaConverter-0TyONAwM.mjs} +0 -0
  131. /package/dist/{sessionManager-wbkYj2HL.d.mts → sessionManager-IW4sbIea.d.mts} +0 -0
  132. /package/dist/{tracing-bz_U4EM1.d.mts → tracing-65B51Dw3.d.mts} +0 -0
  133. /package/dist/{typeGuards-Cj5Rgvlg.mjs → typeGuards-CcFZXgU7.mjs} +0 -0
  134. /package/dist/{utils-Dc0WhlIl.mjs → utils-B-l6410F.mjs} +0 -0
  135. /package/dist/{versioning-BzfeHmhj.mjs → versioning-aUUVziBY.mjs} +0 -0
@@ -0,0 +1,229 @@
1
+ //#region src/scope/types.ts
2
+ /** Check if scope is `member` kind */
3
+ function isMember(scope) {
4
+ return scope.kind === "member";
5
+ }
6
+ /** Check if scope is `elevated` kind */
7
+ function isElevated(scope) {
8
+ return scope.kind === "elevated";
9
+ }
10
+ /** Check if scope is `service` kind (machine-to-machine auth) */
11
+ function isService(scope) {
12
+ return scope.kind === "service";
13
+ }
14
+ /** Check if scope has org access (member, service, or elevated) */
15
+ function hasOrgAccess(scope) {
16
+ return scope.kind === "member" || scope.kind === "service" || scope.kind === "elevated";
17
+ }
18
+ /** Check if request is authenticated (any kind except public) */
19
+ function isAuthenticated(scope) {
20
+ return scope.kind !== "public";
21
+ }
22
+ /** Get organizationId from scope (member, service, or elevated — undefined otherwise) */
23
+ function getOrgId(scope) {
24
+ if (scope.kind === "member") return scope.organizationId;
25
+ if (scope.kind === "service") return scope.organizationId;
26
+ if (scope.kind === "elevated") return scope.organizationId;
27
+ }
28
+ /**
29
+ * Get stable client identity from a service scope.
30
+ *
31
+ * Returns the `clientId` for machine-to-machine auth (API keys, service accounts),
32
+ * or `undefined` for any other scope kind. Use this for audit logging, rate limiting,
33
+ * and anywhere you need to distinguish "this specific API client" from "this user".
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const clientId = getClientId(request.scope);
38
+ * if (clientId) {
39
+ * auditLog.record({ actor: clientId, action: 'create' });
40
+ * }
41
+ * ```
42
+ */
43
+ function getClientId(scope) {
44
+ if (scope.kind === "service") return scope.clientId;
45
+ }
46
+ /**
47
+ * Get OAuth-style scope strings from a service scope (e.g. `['jobs:write']`).
48
+ * Returns an empty array for any non-service kind.
49
+ */
50
+ function getServiceScopes(scope) {
51
+ if (scope.kind === "service") return scope.scopes ?? [];
52
+ return [];
53
+ }
54
+ /** Get org roles from scope (empty array if not a member) */
55
+ function getOrgRoles(scope) {
56
+ if (scope.kind === "member") return scope.orgRoles;
57
+ return [];
58
+ }
59
+ /** Get team ID from scope (only available on member kind) */
60
+ function getTeamId(scope) {
61
+ if (scope.kind === "member") return scope.teamId;
62
+ }
63
+ /**
64
+ * Get an app-defined scope dimension by key (e.g. `branchId`, `projectId`).
65
+ *
66
+ * Returns the value when the scope is `member`/`service`/`elevated` AND has
67
+ * `context` set AND the key exists; `undefined` otherwise. Designed to be
68
+ * the single read path for any custom tenancy dimension your app cares about
69
+ * — branch, project, department, region, workspace, etc.
70
+ *
71
+ * Arc itself takes no position on what keys you use — that's your domain.
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * import { getScopeContext } from '@classytic/arc/scope';
76
+ *
77
+ * const branchId = getScopeContext(request.scope, 'branchId');
78
+ * if (!branchId) return reply.code(403).send({ error: 'Branch context required' });
79
+ * ```
80
+ */
81
+ function getScopeContext(scope, key) {
82
+ if (scope.kind === "member" || scope.kind === "service" || scope.kind === "elevated") return scope.context?.[key];
83
+ }
84
+ /**
85
+ * Get the full scope context map (read-only). Returns `undefined` for scope
86
+ * kinds that don't carry context (`public`, `authenticated`).
87
+ */
88
+ function getScopeContextMap(scope) {
89
+ if (scope.kind === "member" || scope.kind === "service" || scope.kind === "elevated") return scope.context;
90
+ }
91
+ /**
92
+ * Get the parent-organization chain for a scope (closest-first, root-last).
93
+ *
94
+ * Returns the `ancestorOrgIds` array when the scope is `member`/`service`/
95
+ * `elevated` and has it set; an empty array otherwise (including for kinds
96
+ * that can't carry org context).
97
+ *
98
+ * Arc takes no position on what the chain represents — your auth function
99
+ * loads it from your own data model. Common use cases: holding company →
100
+ * subsidiaries, MSP → managed tenants, white-label parent → child accounts.
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * import { getAncestorOrgIds } from '@classytic/arc/scope';
105
+ *
106
+ * const ancestors = getAncestorOrgIds(request.scope);
107
+ * if (ancestors.includes('acme-holding')) {
108
+ * // caller has access to a path that includes Acme Holding
109
+ * }
110
+ * ```
111
+ */
112
+ function getAncestorOrgIds(scope) {
113
+ if (scope.kind === "member" || scope.kind === "service" || scope.kind === "elevated") return scope.ancestorOrgIds ?? [];
114
+ return [];
115
+ }
116
+ /**
117
+ * Pure predicate: does this scope grant access to `targetOrgId`?
118
+ *
119
+ * Returns `true` if `targetOrgId` equals the scope's `organizationId` OR
120
+ * appears in `ancestorOrgIds`. Returns `false` otherwise — including for
121
+ * elevated scopes (this is a pure data query, not a permission check; the
122
+ * elevated bypass lives in `requireOrgInScope`, not here).
123
+ *
124
+ * Designed to be the building block for any custom hierarchy logic in your
125
+ * own permission checks. Use `requireOrgInScope` for the route-gating
126
+ * version that includes the elevated bypass.
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * import { isOrgInScope } from '@classytic/arc/scope';
131
+ *
132
+ * // Inside a custom permission check
133
+ * if (!isOrgInScope(request.scope, request.params.orgId)) {
134
+ * return { granted: false, reason: 'Not in your org hierarchy' };
135
+ * }
136
+ * ```
137
+ */
138
+ function isOrgInScope(scope, targetOrgId) {
139
+ if (targetOrgId === void 0 || targetOrgId === null) return false;
140
+ if (scope.kind !== "member" && scope.kind !== "service" && scope.kind !== "elevated") return false;
141
+ if (scope.organizationId === targetOrgId) return true;
142
+ return (scope.ancestorOrgIds ?? []).includes(targetOrgId);
143
+ }
144
+ /**
145
+ * Get userId from scope (available on authenticated, member, elevated).
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * import { getUserId } from '@classytic/arc/scope';
150
+ * const userId = getUserId(request.scope);
151
+ * ```
152
+ */
153
+ function getUserId(scope) {
154
+ if (scope.kind === "public") return void 0;
155
+ return scope.userId;
156
+ }
157
+ /**
158
+ * Get global user roles from scope (available on authenticated and member).
159
+ * These are user-level roles (e.g. superadmin, finance-admin) distinct from
160
+ * org-level roles (scope.orgRoles).
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * import { getUserRoles } from '@classytic/arc/scope';
165
+ * const globalRoles = getUserRoles(request.scope);
166
+ * ```
167
+ */
168
+ function getUserRoles(scope) {
169
+ if (scope.kind === "authenticated") return scope.userRoles ?? [];
170
+ if (scope.kind === "member") return scope.userRoles;
171
+ return [];
172
+ }
173
+ /**
174
+ * Org context — canonical extraction from a Fastify request.
175
+ *
176
+ * Works regardless of auth type (JWT, Better Auth, custom) by reading
177
+ * `request.scope` and `request.user`. Eliminates the need for each resource
178
+ * to re-invent org extraction from headers/user/scope.
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * import { getOrgContext } from '@classytic/arc/scope';
183
+ *
184
+ * handler: async (request, reply) => {
185
+ * const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
186
+ * }
187
+ * ```
188
+ */
189
+ function getOrgContext(request) {
190
+ const scope = request.scope ?? { kind: "public" };
191
+ return {
192
+ userId: getUserId(scope) ?? request.user?.id ?? request.user?._id,
193
+ organizationId: getOrgId(scope) ?? request.user?.organizationId ?? request.headers?.["x-organization-id"],
194
+ roles: getUserRoles(scope),
195
+ orgRoles: getOrgRoles(scope)
196
+ };
197
+ }
198
+ /**
199
+ * Read `request.scope` safely from any object that *might* have one.
200
+ * Falls back to `PUBLIC_SCOPE` when the field is absent or undefined.
201
+ *
202
+ * This is the canonical way for permission checks, presets, and middleware
203
+ * to read scope — never access `request.scope` directly because it can be
204
+ * `undefined` on requests that haven't been touched by an auth adapter yet.
205
+ *
206
+ * Accepts a structural shape (`{ scope?: RequestScope }`) instead of the
207
+ * full Fastify request type so it can be called from any layer without
208
+ * dragging in the Fastify type. The actual runtime is identical.
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * import { getRequestScope } from '@classytic/arc/scope';
213
+ *
214
+ * function myCheck(ctx: PermissionContext) {
215
+ * const scope = getRequestScope(ctx.request);
216
+ * if (isElevated(scope)) return true;
217
+ * // ...
218
+ * }
219
+ * ```
220
+ */
221
+ function getRequestScope(request) {
222
+ return request.scope ?? PUBLIC_SCOPE;
223
+ }
224
+ /** Default public scope — used as initial decoration value */
225
+ const PUBLIC_SCOPE = Object.freeze({ kind: "public" });
226
+ /** Default authenticated scope — used when user is logged in but no org */
227
+ const AUTHENTICATED_SCOPE = Object.freeze({ kind: "authenticated" });
228
+ //#endregion
229
+ export { isElevated as _, getOrgContext as a, isService as b, getRequestScope as c, getServiceScopes as d, getTeamId as f, isAuthenticated as g, hasOrgAccess as h, getClientId as i, getScopeContext as l, getUserRoles as m, PUBLIC_SCOPE as n, getOrgId as o, getUserId as p, getAncestorOrgIds as r, getOrgRoles as s, AUTHENTICATED_SCOPE as t, getScopeContextMap as u, isMember as v, isOrgInScope as y };
@@ -0,0 +1,286 @@
1
+ //#region src/scope/types.d.ts
2
+ /**
3
+ * Request Scope — The One Standard
4
+ *
5
+ * Discriminated union representing the access context of every request.
6
+ * Replaces scattered orgScope/orgRoles/organizationId/bypassRoles.
7
+ *
8
+ * Set once by auth adapters, read everywhere by permissions/presets/guards.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // In a permission check
13
+ * const scope = request.scope;
14
+ * if (isElevated(scope)) return true;
15
+ * if (isMember(scope) && scope.orgRoles.includes('admin')) return true;
16
+ *
17
+ * // Get user identity from scope
18
+ * const userId = getUserId(scope);
19
+ * const globalRoles = getUserRoles(scope);
20
+ * ```
21
+ */
22
+ /**
23
+ * Request scope — 5 kinds, no ambiguity.
24
+ *
25
+ * | Kind | Meaning |
26
+ * |---------------|--------------------------------------------------|
27
+ * | public | No authentication |
28
+ * | authenticated | Logged-in user, no org context |
29
+ * | member | User in an org with specific roles |
30
+ * | service | Machine-to-machine (API key, service account) |
31
+ * | elevated | Platform admin, explicit elevation |
32
+ *
33
+ * **Identity fields by kind:**
34
+ * - `userId` / `userRoles` — available on `authenticated`, `member`, `elevated` (a real human)
35
+ * - `clientId` — available on `service` (a machine identity, NOT a user)
36
+ * - `organizationId` — required on `member` and `service`, optional on `elevated`
37
+ * - `orgRoles` — org-level roles, only on `member` (from membership records)
38
+ * - `scopes` — optional OAuth-style scope strings on `service` (e.g. `['jobs:write', 'memories:read']`)
39
+ *
40
+ * Use `getUserId(scope)` / `getClientId(scope)` / `getOrgId(scope)` instead of
41
+ * narrowing manually — helpers return `undefined` when the field isn't present.
42
+ */
43
+ type RequestScope = {
44
+ kind: "public";
45
+ } | {
46
+ kind: "authenticated";
47
+ userId?: string;
48
+ userRoles?: string[];
49
+ } | {
50
+ kind: "member";
51
+ userId?: string;
52
+ userRoles: string[];
53
+ organizationId: string;
54
+ orgRoles: string[];
55
+ teamId?: string;
56
+ /**
57
+ * App-defined scope dimensions beyond org and team. Use this to carry
58
+ * branch / project / department / region / workspace identifiers that
59
+ * arc itself shouldn't take a position on.
60
+ *
61
+ * Read with `getScopeContext(scope, key)`. Filtered by
62
+ * `multiTenantPreset({ tenantFields: [...] })` and gated by
63
+ * `requireScopeContext(...)`. Populated by your auth function or
64
+ * adapter (e.g. from JWT claims, BA session fields, or request headers).
65
+ *
66
+ * Treat as immutable — `Readonly` enforces that at the type level.
67
+ */
68
+ context?: Readonly<Record<string, string>>;
69
+ /**
70
+ * Parent organizations the caller has access to, ordered closest-first
71
+ * (immediate parent → … → root). Used for explicit hierarchy checks
72
+ * via `isOrgInScope` and `requireOrgInScope` — there's no automatic
73
+ * inheritance, every check is opt-in.
74
+ *
75
+ * Arc takes no position on the source — your auth function loads the
76
+ * chain from your own org table during sign-in or middleware. Empty
77
+ * or absent = caller has no parent orgs (the common case).
78
+ */
79
+ ancestorOrgIds?: readonly string[];
80
+ } | {
81
+ kind: "service";
82
+ clientId: string;
83
+ organizationId: string;
84
+ scopes?: readonly string[]; /** App-defined scope dimensions — see `member.context` for details. */
85
+ context?: Readonly<Record<string, string>>; /** Parent organizations — see `member.ancestorOrgIds` for details. */
86
+ ancestorOrgIds?: readonly string[];
87
+ } | {
88
+ kind: "elevated";
89
+ userId?: string;
90
+ organizationId?: string;
91
+ elevatedBy: string; /** App-defined scope dimensions — see `member.context` for details. */
92
+ context?: Readonly<Record<string, string>>; /** Parent organizations — see `member.ancestorOrgIds` for details. */
93
+ ancestorOrgIds?: readonly string[];
94
+ };
95
+ /** Check if scope is `member` kind */
96
+ declare function isMember(scope: RequestScope): scope is Extract<RequestScope, {
97
+ kind: "member";
98
+ }>;
99
+ /** Check if scope is `elevated` kind */
100
+ declare function isElevated(scope: RequestScope): scope is Extract<RequestScope, {
101
+ kind: "elevated";
102
+ }>;
103
+ /** Check if scope is `service` kind (machine-to-machine auth) */
104
+ declare function isService(scope: RequestScope): scope is Extract<RequestScope, {
105
+ kind: "service";
106
+ }>;
107
+ /** Check if scope has org access (member, service, or elevated) */
108
+ declare function hasOrgAccess(scope: RequestScope): boolean;
109
+ /** Check if request is authenticated (any kind except public) */
110
+ declare function isAuthenticated(scope: RequestScope): boolean;
111
+ /** Get organizationId from scope (member, service, or elevated — undefined otherwise) */
112
+ declare function getOrgId(scope: RequestScope): string | undefined;
113
+ /**
114
+ * Get stable client identity from a service scope.
115
+ *
116
+ * Returns the `clientId` for machine-to-machine auth (API keys, service accounts),
117
+ * or `undefined` for any other scope kind. Use this for audit logging, rate limiting,
118
+ * and anywhere you need to distinguish "this specific API client" from "this user".
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const clientId = getClientId(request.scope);
123
+ * if (clientId) {
124
+ * auditLog.record({ actor: clientId, action: 'create' });
125
+ * }
126
+ * ```
127
+ */
128
+ declare function getClientId(scope: RequestScope): string | undefined;
129
+ /**
130
+ * Get OAuth-style scope strings from a service scope (e.g. `['jobs:write']`).
131
+ * Returns an empty array for any non-service kind.
132
+ */
133
+ declare function getServiceScopes(scope: RequestScope): readonly string[];
134
+ /** Get org roles from scope (empty array if not a member) */
135
+ declare function getOrgRoles(scope: RequestScope): string[];
136
+ /** Get team ID from scope (only available on member kind) */
137
+ declare function getTeamId(scope: RequestScope): string | undefined;
138
+ /**
139
+ * Get an app-defined scope dimension by key (e.g. `branchId`, `projectId`).
140
+ *
141
+ * Returns the value when the scope is `member`/`service`/`elevated` AND has
142
+ * `context` set AND the key exists; `undefined` otherwise. Designed to be
143
+ * the single read path for any custom tenancy dimension your app cares about
144
+ * — branch, project, department, region, workspace, etc.
145
+ *
146
+ * Arc itself takes no position on what keys you use — that's your domain.
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * import { getScopeContext } from '@classytic/arc/scope';
151
+ *
152
+ * const branchId = getScopeContext(request.scope, 'branchId');
153
+ * if (!branchId) return reply.code(403).send({ error: 'Branch context required' });
154
+ * ```
155
+ */
156
+ declare function getScopeContext(scope: RequestScope, key: string): string | undefined;
157
+ /**
158
+ * Get the full scope context map (read-only). Returns `undefined` for scope
159
+ * kinds that don't carry context (`public`, `authenticated`).
160
+ */
161
+ declare function getScopeContextMap(scope: RequestScope): Readonly<Record<string, string>> | undefined;
162
+ /**
163
+ * Get the parent-organization chain for a scope (closest-first, root-last).
164
+ *
165
+ * Returns the `ancestorOrgIds` array when the scope is `member`/`service`/
166
+ * `elevated` and has it set; an empty array otherwise (including for kinds
167
+ * that can't carry org context).
168
+ *
169
+ * Arc takes no position on what the chain represents — your auth function
170
+ * loads it from your own data model. Common use cases: holding company →
171
+ * subsidiaries, MSP → managed tenants, white-label parent → child accounts.
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * import { getAncestorOrgIds } from '@classytic/arc/scope';
176
+ *
177
+ * const ancestors = getAncestorOrgIds(request.scope);
178
+ * if (ancestors.includes('acme-holding')) {
179
+ * // caller has access to a path that includes Acme Holding
180
+ * }
181
+ * ```
182
+ */
183
+ declare function getAncestorOrgIds(scope: RequestScope): readonly string[];
184
+ /**
185
+ * Pure predicate: does this scope grant access to `targetOrgId`?
186
+ *
187
+ * Returns `true` if `targetOrgId` equals the scope's `organizationId` OR
188
+ * appears in `ancestorOrgIds`. Returns `false` otherwise — including for
189
+ * elevated scopes (this is a pure data query, not a permission check; the
190
+ * elevated bypass lives in `requireOrgInScope`, not here).
191
+ *
192
+ * Designed to be the building block for any custom hierarchy logic in your
193
+ * own permission checks. Use `requireOrgInScope` for the route-gating
194
+ * version that includes the elevated bypass.
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * import { isOrgInScope } from '@classytic/arc/scope';
199
+ *
200
+ * // Inside a custom permission check
201
+ * if (!isOrgInScope(request.scope, request.params.orgId)) {
202
+ * return { granted: false, reason: 'Not in your org hierarchy' };
203
+ * }
204
+ * ```
205
+ */
206
+ declare function isOrgInScope(scope: RequestScope, targetOrgId: string): boolean;
207
+ /**
208
+ * Get userId from scope (available on authenticated, member, elevated).
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * import { getUserId } from '@classytic/arc/scope';
213
+ * const userId = getUserId(request.scope);
214
+ * ```
215
+ */
216
+ declare function getUserId(scope: RequestScope): string | undefined;
217
+ /**
218
+ * Get global user roles from scope (available on authenticated and member).
219
+ * These are user-level roles (e.g. superadmin, finance-admin) distinct from
220
+ * org-level roles (scope.orgRoles).
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * import { getUserRoles } from '@classytic/arc/scope';
225
+ * const globalRoles = getUserRoles(request.scope);
226
+ * ```
227
+ */
228
+ declare function getUserRoles(scope: RequestScope): string[];
229
+ /**
230
+ * Org context — canonical extraction from a Fastify request.
231
+ *
232
+ * Works regardless of auth type (JWT, Better Auth, custom) by reading
233
+ * `request.scope` and `request.user`. Eliminates the need for each resource
234
+ * to re-invent org extraction from headers/user/scope.
235
+ *
236
+ * @example
237
+ * ```typescript
238
+ * import { getOrgContext } from '@classytic/arc/scope';
239
+ *
240
+ * handler: async (request, reply) => {
241
+ * const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
242
+ * }
243
+ * ```
244
+ */
245
+ declare function getOrgContext(request: {
246
+ scope?: RequestScope;
247
+ user?: Record<string, unknown> | null;
248
+ headers?: Record<string, string | string[] | undefined>;
249
+ }): {
250
+ userId: string | undefined;
251
+ organizationId: string | undefined;
252
+ roles: string[];
253
+ orgRoles: string[];
254
+ };
255
+ /**
256
+ * Read `request.scope` safely from any object that *might* have one.
257
+ * Falls back to `PUBLIC_SCOPE` when the field is absent or undefined.
258
+ *
259
+ * This is the canonical way for permission checks, presets, and middleware
260
+ * to read scope — never access `request.scope` directly because it can be
261
+ * `undefined` on requests that haven't been touched by an auth adapter yet.
262
+ *
263
+ * Accepts a structural shape (`{ scope?: RequestScope }`) instead of the
264
+ * full Fastify request type so it can be called from any layer without
265
+ * dragging in the Fastify type. The actual runtime is identical.
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * import { getRequestScope } from '@classytic/arc/scope';
270
+ *
271
+ * function myCheck(ctx: PermissionContext) {
272
+ * const scope = getRequestScope(ctx.request);
273
+ * if (isElevated(scope)) return true;
274
+ * // ...
275
+ * }
276
+ * ```
277
+ */
278
+ declare function getRequestScope(request: {
279
+ scope?: RequestScope;
280
+ }): RequestScope;
281
+ /** Default public scope — used as initial decoration value */
282
+ declare const PUBLIC_SCOPE: Readonly<RequestScope>;
283
+ /** Default authenticated scope — used when user is logged in but no org */
284
+ declare const AUTHENTICATED_SCOPE: Readonly<RequestScope>;
285
+ //#endregion
286
+ export { isAuthenticated as _, getClientId as a, isOrgInScope as b, getOrgRoles as c, getScopeContextMap as d, getServiceScopes as f, hasOrgAccess as g, getUserRoles as h, getAncestorOrgIds as i, getRequestScope as l, getUserId as m, PUBLIC_SCOPE as n, getOrgContext as o, getTeamId as p, RequestScope as r, getOrgId as s, AUTHENTICATED_SCOPE as t, getScopeContext as u, isElevated as v, isService as x, isMember as y };
@@ -1,4 +1,4 @@
1
- import { Vt as ResourceDefinition } from "./interface-DYH8AXGe.mjs";
1
+ import { Vt as ResourceDefinition } from "./interface-Dwzqt4mn.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/integrations/mcp/types.d.ts
@@ -1,12 +1,12 @@
1
- import { n as ElevationOptions } from "./elevation-C_taLQrM.mjs";
2
- import { _ as Authenticator } from "./interface-DYH8AXGe.mjs";
3
- import { t as ExternalOpenApiPaths } from "./externalPaths-DpO-s7r8.mjs";
4
- import { i as CacheStore } from "./interface-D_BWALyZ.mjs";
5
- import { r as QueryCachePluginOptions } from "./queryCachePlugin-DcmETvcB.mjs";
6
- import { i as EventTransport } from "./EventTransport-wc5hSLik.mjs";
7
- import { t as EventPluginOptions } from "./eventPlugin-DW45v4V5.mjs";
8
- import { c as MetricsOptions, d as SSEOptions, m as CachingOptions, r as VersioningOptions, t as ErrorHandlerOptions } from "./errorHandler-Do4vVQ1f.mjs";
9
- import { r as IdempotencyStore } from "./interface-gr-7qo9j.mjs";
1
+ import { _ as Authenticator } from "./interface-Dwzqt4mn.mjs";
2
+ import { n as ElevationOptions } from "./elevation-Dm-HTBCt.mjs";
3
+ import { t as ExternalOpenApiPaths } from "./externalPaths-Dg7OLsKo.mjs";
4
+ import { i as CacheStore } from "./interface-CnluRL4_.mjs";
5
+ import { r as QueryCachePluginOptions } from "./queryCachePlugin-Bw8XyJpX.mjs";
6
+ import { i as EventTransport } from "./EventTransport-CUpRK_Lg.mjs";
7
+ import { t as EventPluginOptions } from "./eventPlugin-BgLxJkIB.mjs";
8
+ import { c as MetricsOptions, d as SSEOptions, m as CachingOptions, r as VersioningOptions, t as ErrorHandlerOptions } from "./errorHandler-COa51ho_.mjs";
9
+ import { r as IdempotencyStore } from "./interface-B9rHWPxD.mjs";
10
10
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
11
11
 
12
12
  //#region src/factory/loadResources.d.ts