PostgreSQL row-level security filters and checks rows according to policies for the active role and command. It can provide defense in depth for shared-table tenancy, but table owners, superusers, BYPASSRLS roles, unsafe definer functions, missing tenant context, connection-pool leakage, and incomplete policies can defeat the intended boundary.
RLS behaves like an implicit predicate plus write check. If enabled with no applicable policy, normal access is default-deny. Owners normally bypass unless forced; privileged roles can bypass.
An application authenticates a user, authorizes tenant membership, begins a transaction, and sets a transaction-local validated tenant ID:
Policy concept:
The application must validate the setting value before SQL, set it locally inside every transaction, and fail closed when absent. Design error behavior to avoid cast surprises. Production policies often join membership/permission data or use safer helper functions after careful privilege review.
The same RLS-protected relation should supply candidate chunks and tool context. Do not retrieve globally then filter in application/model code. Evaluation jobs and support tooling need explicit privileged paths and audit, not blanket bypass.
As the actual runtime role, test two tenants: allowed select/insert/update/delete, wrong-tenant direct ID, wrong-tenant foreign key, missing context, invalid context, pooled reuse, owner behavior, and definer functions. A test run as superuser proves nothing about runtime RLS.
Implement RLS on a disposable copy of tickets for two tenants and run it through a transaction-pooled simulation.
Acceptance criterion: missing/stale context returns no unauthorized data, writes cannot change tenant, and owner/bypass behavior is documented.