Multi-Tenancy Architecture
Multi-Tenancy Architecture
Status: ✅ Production Ready
Namespace: src/tenancy
Verification: src/tenancy/tenancy-verification.spec.ts
Overview
The Multi-Tenancy module provides strict data isolation between different customers (tenants) sharing the same application instance. It ensures that every request, query, and background job runs within a specific TenantContext.
Core Components
1. TenantService
The central service responsible for:
- Resolving tenant from headers (
x-tenant-id) or JWT. - Managing the lifecycle of
TenantContext. - Applying isolation policies to database queries.
2. AsyncLocalStorage (Context Propagation)
We use Node.js AsyncLocalStorage to store the tenant context for the duration of a request. This avoids "prop drilling" (passing tenantId manually to every function).
// Example usage
this.tenantService.runWithTenant(context, async () => {
// context is implicitly available here
const id = this.tenantService.getCurrentTenantId();
});
Isolation Strategies
The system supports two isolation modes, configured via JORVIS_TENANT_ISOLATION.
Strategy A: ROW (Row-Level Security)
Best for: High scale, many small tenants.
- All tenants share the same database tables.
- Every table has a
tenant_idcolumn. TenantService.applyTenantFilter(sql)injectsWHERE tenant_id = ?automatically.
Strategy B: SCHEMA (Schema-Level Isolation)
Best for: Enterprise, strict security compliance.
- Each tenant has their own Postgres SCHEMA (
tenant_123). search_pathis set to the tenant's schema at the start of the transaction.- Complete data isolation at the database level.
Implementation Details
Tenant Resolution
- Header:
x-tenant-id(Development/API) - Domain: Subdomain mapping (e.g.,
tenant1.jorvis.com) - Token: Extracted from JWT
org_idclaim.
Security
- Context Integrity: Once a context is set in
AsyncLocalStorage, it cannot be overridden by user input within that execution scope. - SQL Guard: The
SqlGuardServicecallsTenantServiceto enforce isolation on raw SQL queries generated by LLMs.
Verification
Run the verification suite to ensure isolation integrity:
npx jest src/tenancy/tenancy-verification.spec.ts