Skip to main content
Status: Proposed (RECONSTRUCTION — requires validation) · Date: 2026-08-17 (reconstructed) Refs: ../constitution/founding-constitution.md
⚠️ Reconstructed from the material we have, not recovered. Tags: [derived] is supported by evidence in the repository · [inferred] is deduced · [proposed] is a gap I filled.

Context

Three shapes were available: a database per tenant, a schema per tenant, or a shared schema with row-level isolation. The first two isolate perfectly and stop scaling at a few hundred tenants — migrations, connection pools and backups all multiply. The third scales and puts the entire isolation burden on getting one thing right, every time. [inferred]

Decision

Shared schema with tenant_id on every tenant-scoped table, isolated by row-level security. [derived] RLS is the boundary, not a second layer behind application checks. [derived] An application bug must not be able to cross a tenant, and that is only true if the database refuses. Database access goes only through getDb(tenantCtx). [derived] There is no path to a connection without a tenant context. Every tenant-scoped table also carries cell_id, present from the first migration and unused. [derived] It is the seam for regional partitioning: when a tenant must be pinned to a region, the column is already there and populated. Every uniqueness constraint on tenant-scoped data includes tenant_id. [derived — added 2026-08-17]

Consequences

  • One migration, one pool, one backup, for any number of tenants.
  • Regional partitioning becomes a routing problem rather than a migration. − Every table needs RLS and every RLS policy needs a denial test. A forgotten policy is a breach. − Noisy-neighbour effects are real and are handled by rate limiting and quotas, not by isolation.

What I could not determine

Whether cell_id was intended as a physical region identifier or a logical shard key. [inferred] — I have treated it as a region, which is how the data-residency material reads.