ADR 000: Multi-Tenant Architecture Design
Date: 2026-04-22 Status: Accepted Source: Architecture decision record
Context
Human.work needs to serve multiple client organizations (tenants) from a single platform deployment while maintaining strict data isolation. The design must:
- Prevent any client org from seeing another org's data
- Allow internal roles (superadmin, AM, expert) to operate across orgs with appropriate scoping
- Scale to ~1,200 client orgs by Month 36 (PRD target)
- Maintain 72% gross margin — per-org infrastructure is expensive
The core question: dedicated database per org vs. shared database with logical isolation (RLS)?
Decision
Shared PostgreSQL database with logical multi-tenancy via org_id column scoping and Row-Level Security (RLS).
Isolation Mechanism (Two-Layer)
Layer 1 — Application: OrgRlsInterceptor (NestJS APP_INTERCEPTOR) sets app.org_id via SET LOCAL on every request. All TypeORM queries are scoped to this value.
Layer 2 — Database: PostgreSQL RLS policies enforce org_id isolation at the DB level. Internal roles bypass via explicit two-tier policy (not BYPASSRLS).
Org Identity
Every tenant entity carries org_id UUID NOT NULL. This is the primary partition key for all client data.
Internal roles do not have org_memberships rows — their cross-org access is governed by service-layer checks.
Org Subdomain
Each org gets a unique slug used as their subdomain (``{slug}.human.work). The slug is immutable after client confirmation to prevent breaking existing integrations and bookmarks.
Consequences
Benefits:
- Operationally simple — one DB to manage, one backup, one scaling unit
- Low marginal cost per new org — no new infrastructure
- pgvector embeddings share the same instance; no per-tenant vector DB needed
Trade-offs:
- Noisy neighbor risk — a large org's heavy queries can affect others. Mitigated by per-org rate limiting (planned) and query timeouts.
- RLS complexity — policies must be maintained carefully; any gap in coverage creates a data leak risk. Full DB-level RLS is not yet complete (issue #3).
- Enterprise isolation requirement — some enterprise clients may require dedicated DB instances. This is tracked as a future option (GitHub issue #27, escalated to Eusden).
Decision deferred: GitHub issue #27 (dedicated DB per enterprise client) was escalated and is not resolved. The current architecture can evolve to hybrid: shared DB for SMB clients, dedicated DB for enterprise if needed.