Authorization answers whether an authenticated principal may perform a specific action on a specific resource in context. Multi-tenant isolation must survive every storage and execution path: SQL queries, caches, object keys, queues, search filters, model context, tools, logs, exports, and background jobs. One missing tenant predicate can become a breach.
The request may select an organization, but server code verifies that selection against authoritative membership. It creates an AuthorizationContext containing principal, tenant, roles/permissions, assurance level, and trace. Application policy then evaluates an action such as ticket:read against the resource.
The repository query includes tenant scope before returning data. Policy adds fine-grained checks. Returning 404 for inaccessible identifiers can reduce enumeration, but audit logs should preserve the forbidden attempt safely.
Defense in depth is valuable. No single ORM filter or middleware magically covers all paths.
Create tenant A and B with colliding human-readable data. For every read, write, list, export, cache hit, job, retrieval, stream reconnect, and tool call, attempt access from the wrong tenant. Test ordinary users against admin actions. Run repository integration tests against real PostgreSQL, not only mocks.
Prompt injection often tries to reveal hidden context or invoke unauthorized tools. The model must never receive content the user cannot access. Retrieval filters and tool authorization are server-enforced; instructions like “ignore previous rules” cannot change them.
Build a tenant-isolation test matrix for ticket read/list/update, cached view, document retrieval, AI draft job, and download link.
Acceptance criteria: every cross-tenant attempt returns no protected content, causes no side effect, and emits a safe audit event; tests run with real persistence adapters where relevant.