Tenant isolation
Guaranteeing one customer's data can never appear in another customer's account, no matter which query, cache, or background job touches it.
See it
What it is
One app, many customers, and a hard promise: nothing belonging to one tenant may ever surface inside another. Three common shapes, in increasing cost and increasing separation: a shared table with a 'tenant_id' column on every row, a schema per tenant, or a database per tenant. Most SaaS starts with the shared table because it is cheap to operate, and then enforces the boundary at the database with Postgres row-level security so a forgotten WHERE clause fails closed instead of returning everything.
Make the safe path the default one. Set the current tenant per request or per transaction, deny by default in policy, and put 'tenant_id' into every uniqueness constraint on tenant-scoped data, because a globally unique slug or invoice number is usually a bug in a multi-tenant product. Index order is a separate question with a separate answer: design it from the predicates, selectivity, and sort orders your real queries use, not from a rule that 'tenant_id' goes first everywhere. Then add a test that logs in as tenant B and tries to fetch every one of tenant A's object IDs, expecting 404 on all of them.
Gotcha: the leak almost never comes from the main query, which everyone reviews. It comes from the places nobody classifies as data access: a cached HTML fragment keyed without the tenant, a shared search index, a background job that inherited a stale tenant context, a CSV export, an internal admin endpoint, a webhook replay, or an autoincrement ID someone can guess in the URL.
Ask AI for it
Enforce tenant isolation in this application. Start by identifying the storage engine and the tenancy model actually in use (shared table with a tenant column, schema per tenant, database per tenant, or something document or key-value shaped) and say which one you found before changing anything. If it is shared-schema Postgres: add a non-null 'tenant_id' with a foreign key to every tenant-scoped table, include it in every uniqueness constraint on that data, and enable row-level security on those tables with a deny-by-default policy comparing 'tenant_id' to a transaction-local setting like 'app.current_tenant'. For any other architecture, implement the equivalent enforced boundary for that system instead of pretending it is Postgres. Wire middleware that sets the tenant from the authenticated session at the start of every request and clears it after, and make background jobs carry the tenant explicitly rather than inherit it. Design index order from the queries the app really runs. Key every cache entry by tenant. Treat swapping sequential IDs for UUIDs as optional enumeration resistance, never as the isolation control, and only propose it once the authorization checks are in place. Finish with an integration test that authenticates as tenant B and asserts 404 for every tenant A resource.