The isolation is in the database.
This is a demonstration of the foundation, not the product. It shows two firms sharing one database and one service, and what happens when one of them asks for the other’s data — against the running system, with every response shown exactly as the service returned it.
- One firm cannot read another
- Enforced by a forced row-level-security policy, not by a
WHEREclause each query remembers. - One way to reach the data
- Transaction manager, then repository, then a typed builder — so the tenant contract has a single place it can be guaranteed.
- Wrong code still cannot leak
- The claims below hold even when the application is mistaken, which is the only version of this claim worth making.
Two firms, one database, one write
Both panels read from the same service and the same tables. Create a matter in either firm and watch where it appears — and where it does not.
Firm A
Not loaded yet. Refresh to read this firm’s matters from the service.
0 matter(s) visible to Firm A.
Firm B
Not loaded yet. Refresh to read this firm’s matters from the service.
0 matter(s) visible to Firm B.
The same identifier, asked as each firm
Not a list filtered in the application — the exact identifier, requested twice. One firm receives the record; the other receives nothing, and is not told the difference.
Nothing asked yet. The result of interest is not that Firm B is refused — it is how it is refused.
What actually enforced it
The path the last request travelled, and the values in force at each hop. The predicate below is the one installed by the migration — the application does not add a WHERE clause, and could not remove one.
Run a request above and its path appears here.
SET LOCAL, inside the transaction — so a pooled connection cannot carry one firm’s context into the next firm’s request.
The policy applies to the table’s owner too. Without FORCE, the role that owns the schema bypasses it silently.
A query with no tenant raises 22023 rather than returning zero rows — an empty result reads as “this firm has none” and propagates as an answer.
The refusals, attempted for real
Each of these is issued against the running service now. Nothing here is a recorded result.
A tenant supplied in the request body
The oldest mistake in multi-tenancy: trusting a field the caller sent.
A participant naming another firm's contact
Row-level security does not filter a foreign-key check — the policy alone does not stop this.
No token at all
The unauthenticated surface must disclose nothing, including the request schema.
A token whose signature does not verify
A well-formed token is not a verified one.