@venizia/ignis-docs 0.2.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 (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -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