Tenant Isolation Contract
Isolation is enforced by model design + repository layer + router policy.
Tenant model rules
For tenant-scoped data:
- model inherits
TenantScopedModel. - table has
tenant_idand it is non-null. - repository for the model should inherit
TenantScopedRepository. - raw ORM access in tenant app router/service is discouraged and checked by conformance.
Tenant resolution flow
DatabaseTenantContextResolver resolves tenant with:
- token tenant (
tid) - header tenant (
X-Tenant-ID) - current user default tenant
- tenant lifecycle policy and state checks
When token tenant and header tenant mismatch:
- raise
TENANT_CONTEXT_CONFLICT.
Access modes
Tenant scoped route
Use default tenant_required=True in router and route permission scope tenant.
Platform scoped route
Set permission_scope="platform" in create_router when route manages system-wide resources.
No tenant route
Use tenant_required=False for safe public or platform identity endpoints.
Repository behavior
TenantScopedRepository:
- reads tenant from request context
- filters all queries by tenant
- rejects writes that modify different tenant id
- inserts implicit tenant id if missing
CrossTenantRepository:
- must receive permission gate and optional reason
- designed for explicit cross-tenant admin scenarios
Cross-tenant error handling
Use reason + platform decision when using cross-tenant permissions:
- missing target tenants -> permission denied
- platform decision missing -> permission denied
- decision tenant mismatch -> permission denied
This is strict by design to keep auditability high.