Skip to main content

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.