Day 2 — Tenant isolation, proven
Landed: 2026-07-02 · Verified: 10 isolation tests green in Postgres + Weaviate; Day-1 debts closed
Non-negotiable. The whole multi-customer promise rests on this day. Isolation is not assumed — it is attacked and proven.
The goal
Section titled “The goal”Seal every customer’s data from every other, in the database and the search index. Prove it by attacking it, not by trusting the design on paper.
The story
Section titled “The story”- PlanSeal every customer’s data from every other, in the database and the search index. Prove it by attacking it.
- BuiltForced database walls (Postgres RLS FORCED, an unprivileged role, one single access path — the tenant_txn seam) plus two separate Weaviate search compartments (multi-tenancy on, two tenants created explicitly, auto_tenant_creation off).
- TestedA sweep that finds every table on its own, then tries cross-customer reads and writes. 10 isolation tests, against real Postgres and Weaviate services, not mocks.
- What brokeThe reviews caught 2 real bugs: a wrong row count in one assertion, and a test that opened its database connection at the wrong moment.
- The fixSwitched to a true count instead of the wrong one, and moved the connection to the right point in the test. Written down, not papered over.
- Green10 isolation tests pass. Quick checks green (80 unit tests). 23 integration tests pass.
What actually shipped
Section titled “What actually shipped”- Postgres tenant isolation: Row-Level Security forced on tenant-scoped tables, an unprivileged application role that cannot bypass the walls, and exactly one access path into tenant data (the
tenant_txnseam) that fails closed if the tenant is not set. - Weaviate tenant isolation: multi-tenancy enabled, two tenants created explicitly at bootstrap.
auto_tenant_creationdeliberately left off, so an unknown tenant is a hard failure, never a silently-created new shard. - A dynamic isolation sweep: 10 tests that find every tenant-scoped table on their own and attempt cross-tenant reads and writes against real Postgres and Weaviate, not fakes. A throwaway unprotected table was used to prove the sweep actually catches a table that forgets its wall — the guard itself was tested, not just trusted.
- The rebuildable-from-Postgres design: the
chunkstable stores each piece’s searchable form (text, metadata, and the embedding vector) per tenant with RLS forced, so Weaviate is a derived index, not a second source of truth. If Weaviate is ever lost, a reindex job rebuilds it from Postgres with no customer re-upload and no re-embedding cost. - Day-1 debts closed as part of this day’s work (the
widget_keysRLS gap flagged by Day 1’s security review).
Honest wrinkles worth telling
Section titled “Honest wrinkles worth telling”Two real bugs were caught by review, not shipped silently:
- A wrong row count in one of the isolation assertions — fixed by switching to a true count.
- A test that touched the database at the wrong moment — a connection opened before the tenant context was set, which could have made a false-negative pass possible. Fixed by moving the connection to the correct point in the test.
Both are recorded here, not smoothed over, because a review that never finds anything on a day this security-sensitive would be a weaker signal than a review that found two real things and got them fixed.
Architecture snapshot
Section titled “Architecture snapshot”What Day 2 added: tenant isolation
Postgres — RLS FORCED
documents + chunks tables walled per tenant; unprivileged role; one access path (tenant_txn), fails closed if unset
Weaviate — 2 tenants
multi-tenancy on, tenants created explicitly, auto_tenant_creation off — unknown tenant hard-fails
chunks table (rebuild source)
text + metadata + embedding vector per tenant — Weaviate can be rebuilt from here with no re-upload
Door A / B / C (Day 1)
tenant resolved once at the door, travels as data into this layer
Retrieval + agent (Days 5–6)
not yet built — will read through this same isolated layer
10 isolation tests: green (Postgres + Weaviate, real services)Quick-gate: green (80 unit tests)Integration: 23 tests passCross-tenant leaks: 0