Superseded by 0088: pool and RLS are free,
DB-per-tenant and the scope tree are Pro; decisions 1, 2, 4, 6 and 7 carry forward there.
Context
0014 leaves tenant isolation as “a consumer decision made at the first tenant-owned table” and presumes no tenancy until then. Stackra is a framework that products install (0023), so each product would otherwise re-decide isolation and re-implement it. The owner chose one mechanism for all of them: a shared database with row-level security by default, and a dedicated database for a tenant that needs one. This ADR amends 0014 decision 7 by moving that decision from the consumer into a framework package.Decision
@stackra/tenancyis the one tenancy package. Products and services use it; none implements isolation itself.- Pool is the default. Every tenant-owned table has a
tenant_idcolumn, and row-level security is bothENABLEd andFORCEd on it. - Silo is available on demand. A tenant may get its own database. A tenant registry records where each tenant lives, and a connection resolver picks the connection per request from the registry.
- No schema-per-tenant. It is not offered as a third mode.
- A scope tree exists from day one. Scopes form a tree stored with Postgres
ltree, so a grant or a query can address a subtree (for example an organisation and everything under it). The tree is part of the first migration, not added later. - Isolation is enforced at two layers: the MikroORM scoped entity and the database policy. The rule itself stays
/constitution/tenancy. - Every tenant-owned table has a cross-tenant isolation test that fails when a row from another tenant is readable or writable.
Consequences
- One isolation model to review, test and audit, which also serves the SOC 2 target.
- Moving a tenant from pool to silo is a data migration the registry and resolver must support; its procedure is TODO.
- Migrations must run against the pool database and every silo database.
Enforcement
Planned — a validator check that a migration creating a table withtenant_id also enables and forces RLS, and the per-table isolation test required by decision 7, run on real Postgres. Nothing is implemented yet.
Supersedes and sources
Supersedes: none. Amends: 0014 decision 7 (tenancy moves from a consumer decision to@stackra/tenancy); the rest of 0014 is unchanged. Sources: owner decisions in chat, 2026-10-19; /constitution/tenancy.