Tenancy & Row-Level Security
See
tenant-contextandroute-wrappingfor the canonical, citable rule statements. This page covers the mechanics behind them.
Every tenant-scoped table enforces isolation at the Postgres layer:
ALTER TABLE identity.organisations ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON identity.organisations
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::uuid);
current_setting('app.tenant_id', true)::uuid (without the NULLIF) is a landmine on pooled connections: once any transaction on a session has run set_config('app.tenant_id', …, true), the GUC's reset value is '' — defined-but-empty, not NULL — so the bare cast raises 22P02 and aborts every statement touching the table, because all policies on a table are evaluated even when another one would match. This took down the MCP token exchange in production (DIR-93). Use NULLIF(current_setting(…), '')::uuid, or a module helper like identity.row_in_current_tenant(tenant_id) which compares as text.
Tenant scoping happens in two distinct steps:
- Membership validation at the route layer. Every catalog and directory route uses the per-app
authedRoute/authedRouteWithParamshelpers (which composewithAuth+withTenantAuth).withTenantAuthresolves the active tenant (x-tenant-idheader →jwt.tenant_idfallback) and verifies the user has an active membership for it. NoSET LOCALhappens here. - DB session scoping at the tools layer. When a tool opens a transaction via
withTenantContext()from@constellation-platform/db, the helper executesSELECT set_config('app.tenant_id', $1, true)(transaction-local) so RLS picks up the value for every subsequent query in the transaction. The tenant-scoped Prisma client wraps this for you when you usecreateTenantClient(...).
Routes that read but never call into a tool can still be RLS-safe by going through createTenantClient(...), which does the transaction + SET LOCAL for each scoped query.
Route wrapping
The wrap is mandatory: see route-wrapping for the rule, the canonical wrap style, the escape-hatch syntax, and the CI enforcement script.