Skip to content

Tenancy

Resolution

A request is resolved to a TenantContext by a chain of resolvers (principal, header, or the application's own), then authenticated and authorized. Resolution is explicit: there is no "default tenant", and a request that resolves to none fails closed.

Placement

A tenant is SHARED (one schema, RLS-scoped rows), SCHEMA (its own schema) or DEDICATED (its own database). The strategy is a property of the tenant record, not of the request, so a relocation is a control-plane operation with its own bookkeeping (tenant_placements, tenant_migration_states, tenant_relocations).

Isolation

Row-level security is installed per tenant schema with FORCE, bound to the application role, and verified by inspection rather than assumed. The constants the tests carry (SELECT on tenants.id for the authenticated role) are the ones the invariants document names.

Lifecycle

provisioning → active → suspended → archived → deleted, with explicit transitions (TENANT_CREATED, TENANT_PROVISIONED, TENANT_SUSPENDED, TENANT_RESUMED, TENANT_ARCHIVED, TENANT_DEPROVISIONED, TENANT_PURGED, TENANT_RELOCATED). Suspension is honoured per request: a suspended tenant is not servable, and a job enqueued before the suspension is refused at execution (jobs.md).

Background work

A queued job carries a signed tenant token and a job identity, and the whole chain is revalidated when the job runs - principal active, tenant present, entitlement held, tenant servable. Authority is not frozen at enqueue.