@venizia/ignis-docs 0.2.1-0 → 0.2.1-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 (145) hide show
  1. package/content/best-practices/architectural-patterns.md +3 -3
  2. package/content/best-practices/code-style-standards/naming-conventions.md +1 -1
  3. package/content/best-practices/contribution-workflow.md +2 -2
  4. package/content/best-practices/error-handling.md +94 -89
  5. package/content/best-practices/security-guidelines.md +5 -5
  6. package/content/extensions/components/api-reference.md +22 -21
  7. package/content/extensions/components/authentication/api.md +64 -28
  8. package/content/extensions/components/authentication/errors.md +19 -5
  9. package/content/extensions/components/authentication/index.md +19 -18
  10. package/content/extensions/components/authentication/usage.md +55 -30
  11. package/content/extensions/components/authorization/api.md +351 -84
  12. package/content/extensions/components/authorization/errors.md +51 -17
  13. package/content/extensions/components/authorization/getting-started.md +227 -0
  14. package/content/extensions/components/authorization/index.md +24 -16
  15. package/content/extensions/components/authorization/usage.md +45 -21
  16. package/content/extensions/components/health-check.md +15 -9
  17. package/content/extensions/components/index.md +24 -90
  18. package/content/extensions/components/mail/api.md +105 -54
  19. package/content/extensions/components/mail/errors.md +12 -10
  20. package/content/extensions/components/mail/index.md +32 -13
  21. package/content/extensions/components/mail/usage.md +26 -18
  22. package/content/extensions/components/request-tracker.md +18 -14
  23. package/content/extensions/components/socket-io/api.md +377 -882
  24. package/content/extensions/components/socket-io/errors.md +49 -51
  25. package/content/extensions/components/socket-io/index.md +72 -88
  26. package/content/extensions/components/socket-io/usage.md +107 -117
  27. package/content/extensions/components/static-asset/api.md +83 -31
  28. package/content/extensions/components/static-asset/errors.md +18 -7
  29. package/content/extensions/components/static-asset/index.md +18 -11
  30. package/content/extensions/components/static-asset/usage.md +11 -6
  31. package/content/extensions/components/template/index.md +3 -3
  32. package/content/extensions/components/websocket/api.md +58 -27
  33. package/content/extensions/components/websocket/errors.md +3 -3
  34. package/content/extensions/components/websocket/index.md +8 -7
  35. package/content/extensions/components/websocket/usage.md +21 -8
  36. package/content/extensions/helpers/cron/index.md +8 -7
  37. package/content/extensions/helpers/crypto/index.md +16 -8
  38. package/content/extensions/helpers/crypto/reference.md +96 -24
  39. package/content/extensions/helpers/env/index.md +14 -10
  40. package/content/extensions/helpers/error/index.md +99 -23
  41. package/content/extensions/helpers/index.md +61 -47
  42. package/content/extensions/helpers/inversion/index.md +23 -6
  43. package/content/extensions/helpers/inversion/reference.md +30 -22
  44. package/content/extensions/helpers/kafka/admin.md +3 -2
  45. package/content/extensions/helpers/kafka/compile-binary.md +69 -44
  46. package/content/extensions/helpers/kafka/consumer.md +24 -21
  47. package/content/extensions/helpers/kafka/examples.md +22 -234
  48. package/content/extensions/helpers/kafka/index.md +30 -62
  49. package/content/extensions/helpers/kafka/producer.md +31 -26
  50. package/content/extensions/helpers/kafka/schema-registry.md +19 -14
  51. package/content/extensions/helpers/logger/hf-logger.md +49 -22
  52. package/content/extensions/helpers/logger/index.md +37 -12
  53. package/content/extensions/helpers/logger/pino.md +31 -11
  54. package/content/extensions/helpers/logger/reference.md +243 -52
  55. package/content/extensions/helpers/network/api.md +65 -30
  56. package/content/extensions/helpers/network/index.md +32 -11
  57. package/content/extensions/helpers/queue/index.md +17 -6
  58. package/content/extensions/helpers/queue/reference.md +52 -25
  59. package/content/extensions/helpers/redis/index.md +29 -11
  60. package/content/extensions/helpers/redis/reference.md +85 -28
  61. package/content/extensions/helpers/secrets/index.md +82 -12
  62. package/content/extensions/helpers/socket-io/api.md +40 -21
  63. package/content/extensions/helpers/socket-io/index.md +26 -10
  64. package/content/extensions/helpers/storage/api.md +42 -24
  65. package/content/extensions/helpers/storage/index.md +10 -7
  66. package/content/extensions/helpers/types/index.md +20 -7
  67. package/content/extensions/helpers/types/reference.md +53 -14
  68. package/content/extensions/helpers/uid/index.md +166 -10
  69. package/content/extensions/helpers/websocket/api.md +52 -13
  70. package/content/extensions/helpers/websocket/index.md +7 -4
  71. package/content/extensions/helpers/worker-thread/index.md +9 -5
  72. package/content/extensions/helpers/worker-thread/reference.md +20 -20
  73. package/content/extensions/index.md +38 -39
  74. package/content/extensions/src-details/mcp-server.md +96 -548
  75. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  76. package/content/guides/core-concepts/persistent/index.md +5 -1
  77. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  78. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  79. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  80. package/content/guides/core-concepts/persistent/search-typesense.md +26 -14
  81. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  82. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  83. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  84. package/content/guides/get-started/philosophy.md +135 -670
  85. package/content/guides/get-started/setup.md +53 -74
  86. package/content/guides/migrations/redis-helpers-migration.md +4 -3
  87. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  88. package/content/guides/migrations/unified-connectors-migration.md +7 -6
  89. package/content/guides/tutorials/realtime-chat.md +1 -1
  90. package/content/references/base/application.md +63 -21
  91. package/content/references/base/components.md +3 -3
  92. package/content/references/base/connectors.md +14 -14
  93. package/content/references/base/controllers.md +12 -12
  94. package/content/references/base/datasources-reference.md +32 -31
  95. package/content/references/base/datasources.md +6 -6
  96. package/content/references/base/dependency-injection.md +12 -11
  97. package/content/references/base/filter-system/application-usage.md +54 -30
  98. package/content/references/base/filter-system/array-operators.md +24 -46
  99. package/content/references/base/filter-system/comparison-operators.md +47 -67
  100. package/content/references/base/filter-system/default-filter.md +59 -53
  101. package/content/references/base/filter-system/fields-order-pagination.md +92 -146
  102. package/content/references/base/filter-system/index.md +25 -13
  103. package/content/references/base/filter-system/json-filtering.md +45 -184
  104. package/content/references/base/filter-system/list-operators.md +23 -53
  105. package/content/references/base/filter-system/logical-operators.md +63 -121
  106. package/content/references/base/filter-system/null-operators.md +34 -104
  107. package/content/references/base/filter-system/pattern-matching.md +40 -55
  108. package/content/references/base/filter-system/quick-reference.md +86 -198
  109. package/content/references/base/filter-system/range-operators.md +18 -46
  110. package/content/references/base/filter-system/tips.md +6 -6
  111. package/content/references/base/filter-system/use-cases.md +33 -15
  112. package/content/references/base/grpc-controllers.md +53 -17
  113. package/content/references/base/index.md +5 -3
  114. package/content/references/base/middlewares.md +11 -10
  115. package/content/references/base/models-reference.md +17 -17
  116. package/content/references/base/models.md +4 -3
  117. package/content/references/base/providers.md +8 -8
  118. package/content/references/base/repositories/advanced.md +224 -326
  119. package/content/references/base/repositories/index.md +19 -6
  120. package/content/references/base/repositories/mixins.md +5 -5
  121. package/content/references/base/repositories/relations.md +160 -293
  122. package/content/references/base/repositories/soft-deletable.md +16 -6
  123. package/content/references/base/secrets.md +17 -13
  124. package/content/references/base/services.md +6 -4
  125. package/content/references/configuration/environment-variables.md +31 -23
  126. package/content/references/configuration/index.md +6 -4
  127. package/content/references/index.md +1 -1
  128. package/content/references/utilities/duration.md +85 -0
  129. package/content/references/utilities/index.md +5 -1
  130. package/content/references/utilities/jsx-reference.md +11 -11
  131. package/content/references/utilities/jsx.md +2 -2
  132. package/content/references/utilities/module.md +78 -25
  133. package/content/references/utilities/request.md +2 -1
  134. package/content/references/utilities/retry.md +139 -0
  135. package/content/references/utilities/schema.md +2 -2
  136. package/content/references/utilities/statuses-reference.md +4 -4
  137. package/content/references/utilities/statuses.md +5 -5
  138. package/dist/mcp-server/index.js +0 -0
  139. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  140. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  141. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  142. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  143. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  144. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  145. package/package.json +17 -16
@@ -1,7 +1,19 @@
1
- # Authorization -- Error Reference
1
+ # Authorization - Error Reference
2
2
 
3
3
  > Complete error messages and troubleshooting for the authorization module. See [Setup & Configuration](./) for initial setup.
4
4
 
5
+ ## Find what you need
6
+
7
+ | You want to | Go to |
8
+ |---|---|
9
+ | See every error message, status code, and cause in one table | [Complete Error Reference](#complete-error-reference) |
10
+ | See which pipeline step throws which error | [Error Flow Diagram](#error-flow-diagram) |
11
+ | Fix a specific error message you're staring at | [Troubleshooting](#troubleshooting) |
12
+ | Debug "authorization does nothing" during rollout | [Authorization is not running](#authorization-is-not-running) |
13
+ | Debug rules rebuilding when you expected a cache hit | [Rules are rebuilt on every request](#rules-are-rebuilt-on-every-request) |
14
+ | Debug policies that don't seem to load | [Casbin policies not loading](#casbin-policies-not-loading) |
15
+ | Debug a Redis cache that never hits | [Redis cache not working](#redis-cache-not-working) |
16
+
5
17
  ## Error Flow Diagram
6
18
 
7
19
  ```mermaid
@@ -16,7 +28,8 @@ flowchart TD
16
28
  S4 -->|DENY| E403a[/"403: Authorization denied by voter"/]
17
29
  S4 -->|ALLOW/ABSTAIN| S5{"Step 5: Enforcers registered?"}
18
30
 
19
- S5 -->|No enforcers| OK3([No error - skip])
31
+ S5 -->|No enforcers, defaultDecision: allow| OK3([No error - allow, warning logged])
32
+ S5 -->|No enforcers, defaultDecision: deny or unset| E403c[/"403: no enforcer registered"/]
20
33
  S5 -->|Yes| S5b{"Resolve enforcer"}
21
34
  S5b -->|Name not found| E400b[/"400: Descriptor not found"/]
22
35
  S5b -->|DI fails| E400c[/"400: Failed to resolve"/]
@@ -35,6 +48,7 @@ flowchart TD
35
48
  style E401 fill:#f8d7da,stroke:#dc3545
36
49
  style E403a fill:#f8d7da,stroke:#dc3545
37
50
  style E403b fill:#f8d7da,stroke:#dc3545
51
+ style E403c fill:#f8d7da,stroke:#dc3545
38
52
  style E400b fill:#fff3cd,stroke:#ffc107
39
53
  style E400c fill:#fff3cd,stroke:#ffc107
40
54
  style E400d fill:#fff3cd,stroke:#ffc107
@@ -46,7 +60,7 @@ flowchart TD
46
60
 
47
61
  ## Complete Error Reference
48
62
 
49
- All error messages from the authorization module, organized by source. **Status** is what `error.statusCode` holds - most calls use `getError({ message })` with no explicit `statusCode`, which the shared `ApplicationError` defaults to **400**. Only the request-pipeline errors below (steps 2, 4, 7) set an explicit `401`/`403`.
63
+ All error messages from the authorization module, organized by source. **Status** is what `error.statusCode` holds. Most calls use `getError({ message })` with no explicit `statusCode`, so the shared `ApplicationError` defaults it to **400**. Only the request-pipeline errors below (steps 2, 4, 7) set an explicit `401`/`403`.
50
64
 
51
65
  ### Component Errors (`AuthorizeComponent`)
52
66
 
@@ -69,7 +83,7 @@ Thrown during `binding()`, at application startup.
69
83
  | <code v-pre>[AuthorizationEnforcerRegistry] Enforcer "{{name}}" does not support cache invalidation</code> | 400 | `invalidateUserCache` / `rebuildUserCache` | The resolved enforcer doesn't implement the optional cache-management methods |
70
84
 
71
85
  > [!NOTE]
72
- > `[AuthorizationEnforcerRegistry] No items registered` can only surface if `getDefaultEnforcerName()` is called directly. During normal middleware execution, the provider checks `registry.hasEnforcers()` first and skips authorization when no enforcers exist - it never reaches this throw.
86
+ > `[AuthorizationEnforcerRegistry] No items registered` can only surface if `getDefaultEnforcerName()` is called directly. During normal middleware execution, the provider checks `registry.hasEnforcers()` first and denies (or, with `defaultDecision: 'allow'`, proceeds) before ever calling `getDefaultEnforcerName()`, so it never reaches this throw.
73
87
 
74
88
  ### Authorization Provider Errors (`AuthorizationProvider` - the request pipeline)
75
89
 
@@ -79,6 +93,7 @@ The only errors in the module with an **explicit** `statusCode`.
79
93
  |---------------|--------|------|-------|
80
94
  | `Authorization failed: No authenticated user found` | 401 | 2 - User check | `Authentication.CURRENT_USER` is missing from the Hono context |
81
95
  | <code v-pre>Authorization denied by voter &#124; action: {{action}} &#124; resource: {{resource}}</code> | 403 | 4 - Voters | A voter function returned `AuthorizationDecisions.DENY` |
96
+ | <code v-pre>Authorization failed: authorize() was declared for this route but no enforcer is registered &#124; path: {{path}}</code> | 403 | 5 - Resolve enforcer | `hasEnforcers()` is `false` and `defaultDecision` is `'deny'` (the default) or unset |
82
97
  | `Authorization failed: user.principalType is required for enforcer-based authorization` | 400 | 6 - Build rules | The authenticated user object has no `principalType` field |
83
98
  | <code v-pre>Authorization denied &#124; action: {{action}} &#124; resource: {{resource}}</code> | 403 | 7 - Evaluate | The enforcer returned `DENY`, or `ABSTAIN` and `defaultDecision` resolved to `'deny'` |
84
99
 
@@ -95,7 +110,7 @@ The only errors in the module with an **explicit** `statusCode`.
95
110
  | <code v-pre>[resolveModel] Invalid model.driver &#124; Valids: [file, text]</code> | 400 | `configure` (via `resolveModel`) | `model.driver` isn't `'file'` or `'text'` |
96
111
 
97
112
  > [!NOTE]
98
- > There is no `Invalid cached.driver` runtime error - `cached` is a typed discriminated union (`{ use: false } | { use: true; driver: 'redis'; ... }`), so an unsupported cache driver is a compile-time error. Caching is **Redis-only**.
113
+ > `cached` is a typed discriminated union (`{ use: false } | { use: true; driver: 'redis'; ... }`), so an unsupported cache driver fails at compile time - there is no `Invalid cached.driver` runtime error. Caching is **Redis-only**.
99
114
 
100
115
  ### Casbin Enforcer Errors - Runtime (`buildRules` / `evaluate` / cache management)
101
116
 
@@ -108,7 +123,7 @@ The only errors in the module with an **explicit** `statusCode`.
108
123
  | `[extractUserLines] Adapter does not support loadFilteredPolicy.` | 400 | `buildRules` (via `extractUserLines`) | `options.adapter` doesn't implement casbin's `FilteredAdapter.loadFilteredPolicy` |
109
124
  | `[loadPolicyLinesIntoModel] Not configured. Call configure() first.` | 400 | `evaluate` (via `loadPolicyLinesIntoModel`) | Same root cause as the `evaluate` "Not configured" row, different call site |
110
125
 
111
- **Not an error** - `[CasbinAuthorizationEnforcer] Cached payload is not an array of policy lines.` is logged at `warn` level by `parseCachedPolicyLines` and never thrown up to the caller: a corrupted or legacy Redis entry is discarded and the lines are refetched from the adapter, so the request never 500s (or 400s) on cache corruption.
126
+ **Not an error** - `[CasbinAuthorizationEnforcer] Cached payload is not an array of policy lines.` `parseCachedPolicyLines` logs this at `warn` level; it never throws up to the caller. A corrupted or legacy Redis entry is discarded, and the lines are refetched from the adapter. The request never 500s (or 400s) on cache corruption.
112
127
 
113
128
  ## Troubleshooting
114
129
 
@@ -129,7 +144,11 @@ this.component(AuthorizeComponent);
129
144
 
130
145
  ### "Authorization failed: No authenticated user found"
131
146
 
132
- **Cause:** The authorization middleware runs after authentication, but no user was found on the context (`Authentication.CURRENT_USER` is `undefined`). Common triggers: the route has `authorize` but no `authenticate`, authentication was skipped but authorization wasn't, or the request genuinely has no valid credentials.
147
+ **Cause:** The authorization middleware runs after authentication, but no user was found on the context (`Authentication.CURRENT_USER` is `undefined`). Common triggers:
148
+
149
+ - The route has `authorize` but no `authenticate`.
150
+ - Authentication was skipped, but authorization wasn't.
151
+ - The request genuinely has no valid credentials.
133
152
 
134
153
  **Fix:** Every route with `authorize` needs a matching `authenticate` config.
135
154
 
@@ -148,7 +167,7 @@ this.defineRoute({
148
167
 
149
168
  ### "Authorization failed: user.principalType is required"
150
169
 
151
- **Cause:** The authenticated user object has no `principalType` field. Enforcer-based authorization uses it to build the casbin subject (e.g. `User_123`).
170
+ **Cause:** The authenticated user object has no `principalType` field. Enforcer-based authorization uses it to build the casbin subject - for example, `User_123`.
152
171
 
153
172
  **Fix:** Set `principalType` when you build the user in your authentication service or token payload.
154
173
 
@@ -167,16 +186,24 @@ return {
167
186
 
168
187
  **Cause:** A voter function explicitly returned `AuthorizationDecisions.DENY`.
169
188
 
170
- **Fix:** Inspect the voter named in your logs. Common causes: ownership check failed, a time-window check rejected the request, or a resource-state check (locked/archived) blocked it.
189
+ **Fix:** Inspect the voter named in your logs. Common causes:
190
+
191
+ - An ownership check failed.
192
+ - A time-window check rejected the request.
193
+ - A resource-state check (locked/archived) blocked it.
171
194
 
172
195
  ### "Authorization denied | action: ... | resource: ..."
173
196
 
174
- **Cause:** `enforcer.evaluate()` returned `DENY`, or `ABSTAIN` and it fell back to `defaultDecision`. Usually: no matching policy for the user, a matching `deny` policy, or the model doesn't cover the requested domain/resource/action combination.
197
+ **Cause:** `enforcer.evaluate()` returned `DENY`, or `ABSTAIN` and it fell back to `defaultDecision`. Usually one of:
198
+
199
+ - No matching policy for the user.
200
+ - A matching `deny` policy.
201
+ - The model doesn't cover the requested domain/resource/action combination.
175
202
 
176
203
  **Fix:** Work through this checklist:
177
204
 
178
205
  1. **Policies are loaded correctly** - verify the adapter returns the right rows for this user (`ScopedCasbinAdapter` reads `PolicyDefinition` filtered by `subject_type`/`subject_id`).
179
- 2. **Subject format matches.** `normalizePayloadFn` (or the default scoped payload) must produce a subject that matches what's stored, e.g. `User_123`.
206
+ 2. **Subject format matches.** `normalizePayloadFn` (or the default scoped payload) must produce a subject that matches what's stored - for example, `User_123`.
180
207
  3. **The model covers the request.** For a custom (non-scoped) `.conf`, confirm the matcher handles the action/resource/domain shape you're sending.
181
208
  4. **Set `defaultDecision` explicitly** - don't rely on an implicit fallback:
182
209
 
@@ -247,7 +274,7 @@ options: {
247
274
 
248
275
  ### "[registerMatchers] Role definition "g2" is not declared in the Casbin model..."
249
276
 
250
- **Cause:** `domainMatching.roleDefinition` references a relation the model doesn't declare under `[role_definition]`. Casbin would otherwise register the function as a silent no-op, leaving wildcard domains permanently unmatched (global roles silently denied) - the enforcer throws at boot instead.
277
+ **Cause:** `domainMatching.roleDefinition` references a relation the model doesn't declare under `[role_definition]`. Without this check, Casbin would register the function as a silent no-op, leaving wildcard domains permanently unmatched - global roles would be silently denied. The enforcer throws at boot instead.
251
278
 
252
279
  **Fix:** Point `roleDefinition` at a relation the model actually declares.
253
280
 
@@ -257,9 +284,16 @@ domainMatching: { roleDefinition: 'g', fn: CasbinDomainMatchingFunctions.KEY_MAT
257
284
 
258
285
  ### "[CasbinAuthorizationEnforcer] Matcher smoke test failed at warmup..."
259
286
 
260
- **Cause:** `assertMatcherCompilesSync()` runs a dummy `enforceSync()` at warmup to force casbin's lazy matcher compile. It failed - a matcher syntax error, a function referenced in the matcher that was never registered, or a request-arity mismatch (3 vs. 4 tokens).
287
+ **Cause:** `assertMatcherCompilesSync()` runs a dummy `enforceSync()` at warmup to force casbin's lazy matcher compile. It failed for one of these reasons:
288
+
289
+ - A matcher syntax error.
290
+ - A function referenced in the matcher that was never registered.
291
+ - A request-arity mismatch (3 vs. 4 tokens).
292
+
293
+ **Fix:** Check the `[matchers]` section of your `.conf`.
261
294
 
262
- **Fix:** Check the `[matchers]` section of your `.conf`. If scoped, confirm `isScoped: true` is set (registers `objectMatch` + `keyMatch` automatically); if using a custom flat model, confirm every function the matcher calls is registered via `domainMatching`.
295
+ - Scoped model: confirm `isScoped: true` is set. It registers `objectMatch` and `keyMatch` automatically.
296
+ - Custom flat model: confirm every function the matcher calls is registered via `domainMatching`.
263
297
 
264
298
  ### "[CasbinAuthorizationEnforcer] cached.options.expiresIn must be >= 10000"
265
299
 
@@ -281,7 +315,7 @@ cached: {
281
315
 
282
316
  ### "[CasbinAuthorizationEnforcer] Not configured. Call configure() first."
283
317
 
284
- **Cause:** `evaluate()` (or its internal `loadPolicyLinesIntoModel()`) ran before `configure()` built the enforcer pool. This shouldn't happen through `resolveEnforcer()` (which auto-configures), but can occur if the enforcer is resolved manually from the DI container.
318
+ **Cause:** `evaluate()` (or its internal `loadPolicyLinesIntoModel()`) ran before `configure()` built the enforcer pool. This shouldn't happen through `resolveEnforcer()`, which auto-configures. It can occur if the enforcer is resolved manually from the DI container.
285
319
 
286
320
  **Fix:** Always go through the registry, which handles configure-once automatically.
287
321
 
@@ -332,7 +366,7 @@ Check, in order:
332
366
  1. `authorize` is actually set on the route config (not just `authenticate`).
333
367
  2. `authenticate` isn't `{ skip: true }` - that skips authorization too, in both raw route configs and the CRUD factory.
334
368
  3. The component is registered AND at least one enforcer is registered via `AuthorizationEnforcerRegistry`.
335
- 4. If no enforcers are registered, the middleware skips authorization silently (`next()`) rather than throwing - this is often the real cause of "authorization does nothing" during rollout.
369
+ 4. If no enforcers are registered, the middleware denies with a 403 naming the missing enforcer - unless `defaultDecision: 'allow'` is set, in which case it proceeds and logs a warning. A warning in your logs on every request is often the real sign of "no enforcer registered" during rollout.
336
370
 
337
371
  ### Rules are rebuilt on every request
338
372
 
@@ -354,7 +388,7 @@ Rules cache on `Authorization.RULES`, but only **within one request**:
354
388
  1. **Connection** - verify the `IRedisHelper` (`RedisSingleHelper`/`RedisClusterHelper`/`RedisSentinelHelper`) is actually connected.
355
389
  2. **`keyFn`** - must return a unique, non-empty key per user.
356
390
  3. **`expiresIn`** - must be `>= 10_000` (`MIN_EXPIRES_IN`).
357
- 4. **Corruption is silent** - a malformed cached payload is discarded and refetched (see the Casbin runtime error table above), so a "cache never seems to hit" symptom is more likely a `keyFn` mismatch than corruption.
391
+ 4. **Corruption is silent.** A malformed cached payload is discarded and refetched (see the Casbin runtime error table above). If caching never seems to hit, suspect a `keyFn` mismatch before corruption.
358
392
 
359
393
  ## See Also
360
394
 
@@ -0,0 +1,227 @@
1
+ ---
2
+ title: Authorization Getting Started
3
+ description: The mental model behind IGNIS authorization, then one grant seeded and enforced end to end
4
+ difficulty: beginner
5
+ ---
6
+
7
+ # Authorization Getting Started
8
+
9
+ Every authorization question IGNIS answers - can this user do that, here - comes down to one thing: can you reach an `allow` edge by walking a graph from the user. This page builds that picture first, then seeds one real grant and watches one route go from 401 to 403 to 200.
10
+
11
+ ## The graph, not the rule table
12
+
13
+ Forget rules for a moment. IGNIS authorization state is one graph. The nodes are four kinds of thing: `User`, `Role`, `Permission`, `Domain`. Every row in the single `PolicyDefinition` table is one edge between two nodes, and its `variant` column names what kind of edge it is - a role assignment, a domain membership, a permission grant, or one of three "inherits" edges that let one node stand in for a family of others.
14
+
15
+ A request is a tuple: who is asking (`subject`), where (`domain`), on what (`resource`), to do what (`action`). Casbin decides a request by walking the graph along all four of those axes at once. If it can reach an `allow` edge on every axis, and no `deny` edge also matches, the request passes. Nothing is precomputed - the walk runs fresh on every request, over the edges that user actually holds.
16
+
17
+ So what do `g`, `g2`, `g3`, `g4`, `g5` mean? Each one names the walk along one axis of that tuple - not a separate language to memorize.
18
+
19
+ | Relation | Axis | What it walks |
20
+ |---|---|---|
21
+ | `g` | subject | User holds Role, Role inherits Role |
22
+ | `g2` | domain (membership) | User belongs to Domain |
23
+ | `g3` | domain (nesting) | Domain contains Domain |
24
+ | `g4` | resource | Resource covers Resource |
25
+ | `g5` | action | Action covers Action |
26
+
27
+ `p` is not a walk - it is the edge you are trying to reach. A `p` line is a grant: an action, an effect (`allow` or `deny`), and a domain, attached to a User or a Role.
28
+
29
+ That is the whole model. The rest of this page makes it concrete: real tables, one real grant, one real request.
30
+
31
+ ## Before you touch authorization
32
+
33
+ Authorization runs after authentication and only ever reads what authentication already put on the request. It needs two things from your authenticated user: `principalType` (which node type the user is - almost always `'user'`) and a `userId`. Neither is a dedicated field on `IAuthUser`; both are plain properties your authentication layer adds to the token payload.
34
+
35
+ Read the [Authentication component's entity column helpers](../authentication/usage#entity-column-helpers) before continuing. They define the `User`, `Role`, `Permission`, and `PolicyDefinition` columns this page builds on, and skipping them is the single most common reason authorization "does nothing": without `user.principalType`, the enforcer throws a 400 one step before it would even reach a 403.
36
+
37
+ ## The three tables
38
+
39
+ Authorization reads three tables directly: `Role`, `Permission`, and `PolicyDefinition`. It never reads `User` - a request only ever carries `principalType` and `userId`, not a database row.
40
+
41
+ **`Role`**, via `extraRoleColumns()`:
42
+
43
+ | Column | Type | Meaning |
44
+ |---|---|---|
45
+ | `identifier` | text, unique | The casbin role name, for example `900_admin` |
46
+ | `name` | text | Human-readable label |
47
+ | `priority` | integer | Higher outranks lower; backs `AuthorizationRole` comparisons |
48
+ | `status` | text | Role lifecycle, defaults to `RoleStatuses.ACTIVATED` |
49
+ | `description` | text, nullable | Optional |
50
+
51
+ **`Permission`**, via `extraPermissionColumns()`:
52
+
53
+ | Column | Type | Meaning |
54
+ |---|---|---|
55
+ | `code` | text, unique | The resource string a route's `authorize.resource` matches, for example `configuration` |
56
+ | `subject` | text | Groups permissions by resource family, for example `Order` |
57
+ | `method` | text | For example `GET`; used by subset grants |
58
+ | `action` | text | For example `read` |
59
+ | `scope` | text | For example `global` |
60
+ | `parentId` | text or integer | Optional resource nesting |
61
+
62
+ **`PolicyDefinition`**, via `extraPolicyDefinitionColumns({ idType: 'string' })` - the edge table:
63
+
64
+ | Column | Type | Meaning |
65
+ |---|---|---|
66
+ | `variant` | text | Which of the seven edge kinds this row is |
67
+ | `subjectType`, `subjectId` | text | The edge's source node, for example `user` + a user id |
68
+ | `targetType`, `targetId` | text | The edge's destination node |
69
+ | `action` | text, nullable | Set only on `grant` rows |
70
+ | `effect` | text, nullable | `allow` or `deny`, set only on `grant` rows |
71
+ | `domain` | text, nullable | The casbin domain token - see the note below |
72
+ | `metadata` | jsonb, nullable | Only subset ("custom") grants use it |
73
+
74
+ `variant` must be one of exactly seven values, owned by `AuthorizationPolicyVariants`: `grant`, `assign_role`, `role_inherits`, `join_domain`, `domain_inherits`, `resource_inherits`, `action_inherits`. Nothing validates this column on read. A typo or a wrong value does not error - the row just never matches any query, and the grant it was meant to carry silently does not exist.
75
+
76
+ `domain` has the same trap. It stores a full casbin token, `<Type>_<id>` - for example `Organization_3fa85f64-5717-4562-b3fc-2c963f66afa6` - never a bare id. Get `variant` right and `domain` wrong, and every domain-scoped check for that row still fails. The next section shows the one way to avoid both mistakes at once.
77
+
78
+ ## Register the component
79
+
80
+ This assumes a Postgres datasource is already registered - see [DataSources](/guides/core-concepts/persistent/datasources) if it is not yet. Adjust the import path and binding key to your own datasource class name.
81
+
82
+ ```typescript
83
+ import {
84
+ AuthorizeBindingKeys, AuthorizeComponent, AuthorizationDecisions, AuthorizationEnforcerRegistry,
85
+ AuthorizationEnforcerTypes, CasbinAuthorizationEnforcer, CasbinEnforcerModelDrivers,
86
+ ScopedCasbinAdapter, CASBIN_RBAC_DOMAIN_SCOPED_MODEL, BaseApplication, IAuthorizeOptions,
87
+ } from '@venizia/ignis';
88
+ import { PostgresDataSource } from './datasources/postgres.datasource';
89
+ import { Organization, Permission, PolicyDefinition, Role } from './models/entities';
90
+
91
+ export class Application extends BaseApplication {
92
+ preConfigure() {
93
+ const dataSource = this.get<PostgresDataSource>({ key: 'datasources.PostgresDataSource' });
94
+
95
+ this.bind<IAuthorizeOptions>({ key: AuthorizeBindingKeys.OPTIONS }).toValue({
96
+ defaultDecision: AuthorizationDecisions.DENY,
97
+ alwaysAllowRoles: ['999_super-admin'],
98
+ });
99
+
100
+ this.component(AuthorizeComponent);
101
+
102
+ const adapter = new ScopedCasbinAdapter({
103
+ dataSource,
104
+ entities: {
105
+ policyDefinition: { tableName: PolicyDefinition.name },
106
+ permission: { tableName: Permission.name },
107
+ principals: { user: 'user', role: Role.name },
108
+ domainTypes: [Organization.name],
109
+ },
110
+ });
111
+
112
+ AuthorizationEnforcerRegistry.getInstance().register({
113
+ container: this,
114
+ enforcers: [{
115
+ enforcer: CasbinAuthorizationEnforcer,
116
+ name: 'casbin',
117
+ type: AuthorizationEnforcerTypes.CASBIN,
118
+ options: {
119
+ model: { driver: CasbinEnforcerModelDrivers.TEXT, definition: CASBIN_RBAC_DOMAIN_SCOPED_MODEL },
120
+ isScoped: true,
121
+ adapter,
122
+ cached: { use: false },
123
+ },
124
+ }],
125
+ });
126
+ }
127
+ }
128
+ ```
129
+
130
+ `entities.principals.user` must equal the exact `principalType` string your authentication layer puts on the token - `'user'` here. `entities.principals.role` must equal your `Role` entity's `.name`; it builds the casbin prefix for role-scoped lines (`Role_<id>`). `cached.use: false` re-reads policy rows on every request - fine while you are getting this working. The [RBAC with domains guide](./usage#rbac-with-domains-multi-tenant) covers adding a Redis cache once it matters.
131
+
132
+ ## Seed exactly one grant
133
+
134
+ Build every `PolicyDefinition` row through `AuthorizationPolicyBuilder`. Never write `variant` or `domain` by hand - the builder cannot produce a wrong `variant`, and its `serializeDomain` step is what turns a typed `{ type, id }` domain into the `<Type>_<id>` token from the section above.
135
+
136
+ A grant needs three things to already exist: the user (from sign-up), one `Organization` row (the tenant), and one `Permission` row - say `code: 'configuration'`, `action: 'read'`. Seed those however you already seed reference data. Then seed the grant itself:
137
+
138
+ ```typescript
139
+ import { randomUUID } from 'crypto';
140
+ import { Pool } from 'pg';
141
+ import { AuthorizationActions, AuthorizationDecisions, AuthorizationPolicyBuilder } from '@venizia/ignis';
142
+
143
+ const pool = new Pool({ /* your connection */ });
144
+
145
+ const grant = AuthorizationPolicyBuilder.grant({
146
+ subject: { type: 'user', id: userId },
147
+ permission: { type: 'Permission', id: permissionId },
148
+ action: AuthorizationActions.READ,
149
+ effect: AuthorizationDecisions.ALLOW,
150
+ domain: { type: 'Organization', id: organizationId },
151
+ });
152
+
153
+ await pool.query(
154
+ `INSERT INTO "PolicyDefinition"
155
+ (id, variant, subject_type, subject_id, target_type, target_id, action, effect, domain)
156
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
157
+ [randomUUID(), grant.variant, grant.subjectType, grant.subjectId, grant.targetType, grant.targetId, grant.action, grant.effect, grant.domain],
158
+ );
159
+ ```
160
+
161
+ That one row is enough - no role, no role assignment. The scoped model's role, domain, resource, and action axes all fall back to a self-link when a request matches a stored value exactly, so a grant made directly to the user's own `subject` clears every axis on its own. `grant.domain` now reads `Organization_<organizationId>`, never the bare id.
162
+
163
+ `examples/vert/scripts/seed-user-policies.ts` runs this same builder call for seven personas at once, some with a role assignment added and some without - read it once you need more than one grant.
164
+
165
+ ## Protect one route
166
+
167
+ ```typescript
168
+ // Inside a controller's binding()
169
+ const READ_CONFIGURATIONS_CONFIG = {
170
+ path: '/authz-example/configurations',
171
+ method: 'get',
172
+ authenticate: { strategies: [Authentication.STRATEGY_JWT] },
173
+ authorize: {
174
+ action: AuthorizationActions.READ,
175
+ resource: 'configuration',
176
+ domain: { from: 'header', key: 'x-organization-id', type: 'Organization' },
177
+ },
178
+ responses: jsonResponse({ description: 'Configurations', schema: ConfigurationsSchema }),
179
+ } as const;
180
+
181
+ this.defineRoute({
182
+ configs: READ_CONFIGURATIONS_CONFIG,
183
+ handler: context => context.json({ data: ['app.name=My App'] }),
184
+ });
185
+ ```
186
+
187
+ `domain: { from: 'header', ... }` reads the tenant id straight off a request header and turns it into the same `Organization_<id>` token the grant stores - no `domainResolver` needed yet. Now sign in as usual (see [Authentication Usage](../authentication/usage)) and call the route with three different tokens:
188
+
189
+ ```bash
190
+ # No token at all
191
+ curl -i http://localhost:3000/api/authz-example/configurations
192
+ # -> 401: no authenticated user
193
+
194
+ # A signed-in user with zero PolicyDefinition rows
195
+ curl -i http://localhost:3000/api/authz-example/configurations \
196
+ -H "Authorization: Bearer $TOKEN_WITHOUT_GRANT"
197
+ # -> 403: authenticated, but nothing to reach an allow edge
198
+
199
+ # The user from the previous section, with their organization's id
200
+ curl -i http://localhost:3000/api/authz-example/configurations \
201
+ -H "Authorization: Bearer $TOKEN_WITH_GRANT" \
202
+ -H "x-organization-id: $ORGANIZATION_ID"
203
+ # -> 200
204
+ ```
205
+
206
+ `examples/vert` runs this exact scenario for real, with real ports and real tokens: `scripts/seed-authz-test-data.ts` and `scripts/seed-user-policies.ts` seed seven personas, and `scripts/test-authorization.sh` curls all of them - cases C1, C2, and C3 are the three requests above.
207
+
208
+ ## What just happened
209
+
210
+ The 200 request walked the [seven-step pipeline](./#how-it-works) like this:
211
+
212
+ 1. `Authorization.SKIP_AUTHORIZATION` was not set - continue.
213
+ 2. `Authentication.CURRENT_USER` was present, with `principalType: 'user'` - continue.
214
+ 3. No `alwaysAllowRoles` or `allowedRoles` matched - continue.
215
+ 4. No voters were registered - continue.
216
+ 5. The `casbin` enforcer resolved, the only one registered.
217
+ 6. The `x-organization-id` header resolved to `Organization_<id>`. `ScopedCasbinAdapter` read the user's own rows from `PolicyDefinition` and built one `p` line from the grant you seeded.
218
+ 7. `enforcer.evaluate()` matched that line on every axis - `g` and `g3` by self-link, since the grant names the user and the domain directly; `objectMatch` and `g5` by exact string equality on `configuration` and `read` - and returned `allow`.
219
+
220
+ The 403 request stopped at the same step 7, on the same enforcer, with no line to match: that user's `PolicyDefinition` rows were empty, so the rules built in step 6 had nothing in them. The 401 request never got past step 2 - there was no user to check anything against.
221
+
222
+ ## Where to go next
223
+
224
+ - [Usage & Examples](./usage) - securing gRPC routes, voters, role shortcuts, CRUD factory integration, and domain scoping with a global `domainResolver`
225
+ - [API Reference](./api) - every option, binding key, and enforcer internal
226
+ - [Error Reference](./errors) - what each error means and how to fix it
227
+ - `examples/vert/src/controllers/authorization-example/` - the full controller these routes are drawn from
@@ -6,7 +6,10 @@ difficulty: advanced
6
6
 
7
7
  # Authorization
8
8
 
9
- `AuthorizeComponent` wires up enforcer-based route authorization - RBAC through Casbin (with an optional multi-tenant domain-scoped model), voters, and role shortcuts - evaluated by the `authorize()` middleware after authentication.
9
+ `AuthorizeComponent` decides whether an authenticated request is allowed to proceed. It evaluates role shortcuts, custom voters, and a Casbin RBAC enforcer, in that order, through the `authorize()` middleware. The middleware runs after authentication. Casbin's optional domain-scoped model adds multi-tenant grants on top.
10
+
11
+ > [!TIP]
12
+ > New to this component? Start with [Getting Started](./getting-started) - it builds the mental model (a graph of edges, not a rule table), then seeds one grant and protects one route end to end before you read the reference material below.
10
13
 
11
14
  ## In one example
12
15
 
@@ -73,11 +76,11 @@ const DELETE_ARTICLE_CONFIG = {
73
76
 
74
77
  ## How it works
75
78
 
76
- - **Enforcer-based and pluggable.** `authorize({ spec })` returns Hono middleware built by `AuthorizationProvider`. It resolves an `IAuthorizationEnforcer` from `AuthorizationEnforcerRegistry` by name (default: the first registered) - swap `CasbinAuthorizationEnforcer` for a custom class without touching route configs.
77
- - **Runs after authentication.** The middleware reads `Authentication.CURRENT_USER` from the Hono context, so `AuthenticateComponent` must run first and the route needs an `authenticate` config alongside `authorize`.
78
- - **No enforcers registered = no-op.** If `AuthorizationEnforcerRegistry.hasEnforcers()` is `false`, the middleware calls `next()` and skips authorization entirely - useful during incremental rollout, dangerous if you forget to register an enforcer in production.
79
- - **Casbin's scoped RBAC model is the recommended engine.** `CASBIN_RBAC_DOMAIN_SCOPED_MODEL` + `isScoped: true` + `ScopedCasbinAdapter` reads one principal's role/permission/domain edges (plus the shared role/resource/action/domain hierarchy) from a single `PolicyDefinition` table, and supports multi-tenant grants scoped to `SYSTEM_WIDE`, `ANY_MEMBER`, or a specific `<Type>_<id>` domain.
80
- - **Per-request enforcers, cached lines.** Each Casbin evaluation borrows an isolated enforcer from an internal pool, loads that user's policy lines into it, and evaluates - the datasource query only runs on cache miss (or always, if `cached.use: false`).
79
+ - **Enforcer-based and pluggable.** `authorize({ spec })` returns Hono middleware built by `AuthorizationProvider`, which resolves an `IAuthorizationEnforcer` from `AuthorizationEnforcerRegistry` by name (default: the first registered). Swap `CasbinAuthorizationEnforcer` for a custom class without touching route configs.
80
+ - **Runs after authentication.** The middleware reads `Authentication.CURRENT_USER` from the Hono context. `AuthenticateComponent` must run first, and the route needs an `authenticate` config alongside `authorize`.
81
+ - **No enforcers registered = deny, unless you opt into allow.** If `AuthorizationEnforcerRegistry.hasEnforcers()` is `false`, the middleware throws a 403 naming the missing enforcer. Set `defaultDecision: 'allow'` on `IAuthorizeOptions` to proceed instead - useful during incremental rollout - and the middleware logs a warning each time it does.
82
+ - **Casbin's scoped RBAC model is the recommended engine.** Combine `CASBIN_RBAC_DOMAIN_SCOPED_MODEL`, `isScoped: true`, and `ScopedCasbinAdapter` to read one principal's policy edges from a single `PolicyDefinition` table. See [RBAC with domains](./usage#rbac-with-domains-multi-tenant) for multi-tenant grant scoping.
83
+ - **Per-request enforcers, cached lines.** Each Casbin evaluation borrows an isolated enforcer from an internal pool, loads that user's policy lines into it, then evaluates. The datasource query runs only on a cache miss, or every time if `cached.use: false`.
81
84
 
82
85
  **Pipeline (7 steps, short-circuits marked)**
83
86
 
@@ -87,7 +90,7 @@ const DELETE_ARTICLE_CONFIG = {
87
90
  | 2 | Read `Authentication.CURRENT_USER` | Missing -> 401 |
88
91
  | 3 | Role shortcuts (`alwaysAllowRoles` + `allowedRoles`) | Match -> `next()` |
89
92
  | 4 | Voters (per-route) | `ALLOW`/`DENY` -> `next()` / 403 |
90
- | 5 | Resolve enforcer | None registered -> `next()` |
93
+ | 5 | Resolve enforcer | None registered -> 403, or `next()` if `defaultDecision: 'allow'` |
91
94
  | 6 | Build/cache rules (+ resolve domain, if any) | - |
92
95
  | 7 | `enforcer.evaluate()` | `DENY`/`ABSTAIN`-as-deny -> 403 |
93
96
 
@@ -99,7 +102,12 @@ const DELETE_ARTICLE_CONFIG = {
99
102
  authorize: { action: AuthorizationActions.READ, resource: 'Article' }
100
103
  ```
101
104
 
102
- **Bypass the enforcer for trusted roles.** `alwaysAllowRoles` (global, in `IAuthorizeOptions`) or `allowedRoles` (per-route spec) skip straight to `next()` once a matching role is found.
105
+ **Bypass the enforcer for trusted roles.** Both skip straight to `next()` once a matching role is found.
106
+
107
+ | Option | Scope |
108
+ |---|---|
109
+ | `alwaysAllowRoles` | Global, set in `IAuthorizeOptions` |
110
+ | `allowedRoles` | Per-route, set on the spec |
103
111
 
104
112
  ```typescript
105
113
  authorize: { action: AuthorizationActions.DELETE, resource: 'Article', allowedRoles: [AuthorizationRoles.ADMIN.identifier] }
@@ -120,7 +128,7 @@ const ownerVoter: TAuthorizationVoter = async ({ user, context }) =>
120
128
  authorize: { action: 'read', resource: 'Order', domain: { from: 'param', key: 'merchantId', type: 'Merchant' } }
121
129
  ```
122
130
 
123
- **Reference a model instead of a hardcoded string.** `@model({ settings: { authorize: { principal } } } })` auto-populates `Model.AUTHORIZATION_SUBJECT`.
131
+ **Reference a model instead of a hardcoded string.** `@model({ settings: { authorize: { principal } } })` auto-populates `Model.AUTHORIZATION_SUBJECT`.
124
132
 
125
133
  ```typescript
126
134
  authorize: { action: AuthorizationActions.READ, resource: Article.AUTHORIZATION_SUBJECT }
@@ -138,10 +146,10 @@ authorize: { action: AuthorizationActions.READ, resource: Article.AUTHORIZATION_
138
146
 
139
147
  **Files:**
140
148
 
141
- - [`packages/core/src/components/auth/authorize/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authorize/component.ts) - `AuthorizeComponent`
142
- - [`packages/core/src/components/auth/authorize/common/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authorize/common) - constants, binding keys, types, policy/permission builders
143
- - [`packages/core/src/components/auth/authorize/providers/authorization.provider.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authorize/providers/authorization.provider.ts) - `AuthorizationProvider` (the 7-step pipeline)
144
- - [`packages/core/src/components/auth/authorize/enforcers/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authorize/enforcers) - `CasbinAuthorizationEnforcer`, `AuthorizationEnforcerRegistry`, `CASBIN_RBAC_DOMAIN_SCOPED_MODEL`
145
- - [`packages/core/src/components/auth/authorize/adapters/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authorize/adapters) - `BaseFilteredAdapter`, `ScopedCasbinAdapter`
146
- - [`packages/core/src/components/auth/authorize/models/authorization-role.model.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/components/auth/authorize/models/authorization-role.model.ts) - `AuthorizationRole`
147
- - [`packages/core/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core/src/base/metadata/persistents.ts) - `@model` auto-populating `AUTHORIZATION_SUBJECT` from `settings.authorize.principal`
149
+ - [`packages/core-server/src/components/auth/authorize/component.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/component.ts) - `AuthorizeComponent`
150
+ - [`packages/core-server/src/components/auth/authorize/common/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/common) - constants, binding keys, types, policy/permission builders
151
+ - [`packages/core-server/src/components/auth/authorize/providers/authorization.provider.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/providers/authorization.provider.ts) - `AuthorizationProvider` (the 7-step pipeline)
152
+ - [`packages/core-server/src/components/auth/authorize/enforcers/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/enforcers) - `CasbinAuthorizationEnforcer`, `AuthorizationEnforcerRegistry`, `CASBIN_RBAC_DOMAIN_SCOPED_MODEL`
153
+ - [`packages/core-server/src/components/auth/authorize/adapters/`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/adapters) - `BaseFilteredAdapter`, `ScopedCasbinAdapter`
154
+ - [`packages/core-server/src/components/auth/authorize/models/authorization-role.model.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/components/auth/authorize/models/authorization-role.model.ts) - `AuthorizationRole`
155
+ - [`packages/core-server/src/base/metadata/persistents.ts`](https://github.com/VENIZIA-AI/ignis/blob/main/packages/core-server/src/base/metadata/persistents.ts) - `@model` auto-populating `AUTHORIZATION_SUBJECT` from `settings.authorize.principal`