Platform

Tenant isolation

Schema-per-organisation instead of a discriminator column: the schema is set on the connection for the transaction, so a query that forgets everything still cannot cross an organisation boundary

Schema per organisation

Each organisation's tables live in their own database schema. Access is routed by setting the schema for the duration of the transaction, and per-organisation data is only ever reached through that path.

The process service follows the same model with its own schemas per organisation.

Why not a discriminator column

The common alternative puts a tenant_id on shared tables and filters on it.

Compare the failure modes:

Discriminator column Schema per organisation
Isolation holds because Every query in the codebase remembers the filter The objects are not in the search path
A forgotten filter Returns another organisation's rows Returns nothing - the table is not visible
A new developer's first query Is a risk Is not
Review burden Every query, forever The routing helper, once

The property worth stating plainly: isolation does not depend on discipline. It is not that people here are more careful; it is that carelessness has a different outcome.

The trade-off: schema-per-organisation means migrations run per organisation, which is real operational work. That is the side we chose.

Beyond the database

Isolation is not only a storage property. Per organisation:

  • Object storage - analytical files are scoped, and bucket scope is enforced, not assumed
  • Model providers and keys, including an organisation's own
  • Knowledge, embeddings, custom skills and installed marketplace skills
  • Credits, budgets and suspension state
  • The audit trail
  • Application repositories and their installations
  • AI employees, each a subject inside one organisation

And underneath the rest of access control

Isolation answers "which organisation". It does not answer "which rows" or "which values" - those are row policies and column masks, evaluated per query inside an organisation.

Both questions have to be answered. Neither substitutes for the other.