Realtime ConveyancerNational platform foundations
  • Demonstration
  • Storage
  • Design system
Proof of concept · live

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 WHERE clause 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.
1

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.

Tenant

Firm A

Not loaded yet. Refresh to read this firm’s matters from the service.

0 matter(s) visible to Firm A.

Tenant

Firm B

Not loaded yet. Refresh to read this firm’s matters from the service.

0 matter(s) visible to Firm B.

2

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.

3

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.

Scope

SET LOCAL, inside the transaction — so a pooled connection cannot carry one firm’s context into the next firm’s request.

Forced

The policy applies to the table’s owner too. Without FORCE, the role that owns the schema bypasses it silently.

Unset fails closed

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.

4

The refusals, attempted for real

Each of these is issued against the running service now. Nothing here is a recorded result.

Attempt

A tenant supplied in the request body

The oldest mistake in multi-tenancy: trusting a field the caller sent.

Attempt

A participant naming another firm's contact

Row-level security does not filter a foreign-key check — the policy alone does not stop this.

Attempt

No token at all

The unauthenticated surface must disclose nothing, including the request schema.

Attempt

A token whose signature does not verify

A well-formed token is not a verified one.

What this is not. No conveyancer-facing product screens ship in this deliverable — D4 §5 declined them deliberately, and that decision stands. There is no settlement track, no client portal and no document handling here, because there is no matter in flight to narrate. This console exists to make the architecture observable.

Both firms are addressable from one page, which no real deployment would ever permit. Their tokens are minted and held by this console’s own server; the browser addresses a firm by name and never receives a credential.