Tenant isolation
Every customer is a logical tenant. All data access funnels through the org_id discriminator at the row level. Cross-tenant queries are blocked at the ORM layer and the database constraint layer.
- Postgres row-level security policies enforce org_id matching on every SELECT, UPDATE, and DELETE — application code cannot accidentally cross tenant boundaries.
- Read replicas inherit the same RLS policies. Backups are tenant-segregated for restore.
- Background jobs carry org_id in the message envelope; workers refuse to process payloads without it.
- S3-style object storage uses per-tenant prefix isolation with bucket-level policies that deny cross-tenant reads.
Threat model
Adversary: another customer's compromised account. Defense: RLS at DB + bucket policy at object store + worker-level org_id assertions. A bug at any single layer is caught by the others.