# Tenant Model and Row-Level Security ## Global vs Tenant-Private - **Global objects:** Public or shared data that can be read across tenants (e.g. BIC, LEI, BIN range metadata). Stored with `tenant_id` null or a dedicated "global" tenant. Used for cross-tenant lookup when the rail has a public identifier scheme. - **Tenant-private objects:** Participant-specific data, merchant IDs, terminal IDs, contractual endpoints. Always scoped by `tenant_id`. Queries must supply tenant (from auth or request) so that only that tenant's rows are visible. ## Enforcement - **Postgres Row Level Security (RLS):** Enable RLS on `participants`, `identifiers`, `endpoints`, `capabilities`, `credentials`, `policies`, and optionally `routing_artifacts`. Policies: `tenant_id = current_setting('app.current_tenant_id')` or equivalent. Global rows: allow when `tenant_id IS NULL` or when reading public identifiers. - **Application layer:** Resolver and Admin API must set tenant context (e.g. from JWT or request parameter) before querying. Never return rows from another tenant. - **Per-tenant encryption:** For Tier 2+ data (see [data-classification](../security/data-classification.md)), use per-tenant encryption keys so that key compromise affects only one tenant. ## Caching - Cache key **includes tenant**: same request for different tenants must not share a cache entry. - Optional per-tenant TTL or invalidation rules (e.g. shorter TTL for high-churn tenants). - Negative cache: key includes tenant; invalidate on any change for that tenant. ## RLS Policy Summary | Table | Policy (conceptual) | | ---------------- | -------------------------------------------------------- | | participants | WHERE tenant_id = current_tenant OR tenant_id IS NULL | | identifiers | JOIN participants; same tenant or global | | endpoints | JOIN participants; same tenant | | capabilities | JOIN participants; same tenant | | credentials | JOIN participants; same tenant | | policies | WHERE tenant_id = current_tenant | | routing_artifacts| WHERE tenant_id = current_tenant OR tenant_id IS NULL | Apply `current_tenant` from connection/session (e.g. set by API after auth).