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 = current_setting('app.tenant_id', true)::uuid);
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.