Browse pages
Multitenancy
Multitenancy lets independent customer organizations share one deployment while each keeps isolated data and policy. This page states how the product resolves tenant identity from trusted context, isolates each tenant's data and policy across authorization, storage, caches, reactions, jobs, exports, diagnostics, and recovery, and records the operations that cross tenant lines. A project selects it when separate customers require isolated data or policy in one deployment, because a tenant column without these obligations leaks one customer's data to another.
Stack: any
Layer: application
Activation#
Activation scope: project.
Applicable specification kinds: None.
The project selects tenancy only when independent customer organizations share one deployment and need isolated data or policy. One organization with many users does not select it: tenants are separate customers, not separate logins.
Baseline Relationship#
This extension replaces no baseline provision. It adds tenant obligations the baseline does not state: identity, isolation, tenant scope, and operations across every boundary a tenant touches. For delegation, this extension adds only the cross-tenant form; a delegated or temporary grant inside one tenant follows litespec/core/access#record-a-delegated-grant-inside-one-tenant-as-a-role-assignment.
It switches on no record type: tenant scope applies to records every project requires, such as decisions, audit records, and exports.
Standards#
Record the Tenant Adoption Decision#
ruleruleguidancelitespec/ext/tenancy#record-the-tenant-adoption-decisionA consumer selecting tenancy MUST record a decision naming tenant source, storage model, isolation guarantee, administrative access, evolution path, and enumeration bound.
Applies toartifact: record:decision
Why
Tenant source, storage model, isolation guarantee, administrative access, and evolution path interact across every boundary, so one decision records them together before code assumes any of them. The enumeration bound caps how many tenant identifiers an all-tenant record lists.
Checka reviewer answers
Reviewed forThe tenant adoption decision under .litespec/intent/decisions/ names source, storage model, isolation guarantee, administrative access, evolution path, and enumeration bound.
Marked guidance: a reviewer answers this question.
Define Tenant Isolation#
ruleruleguidancelitespec/ext/tenancy#define-tenant-isolationA multi-tenant decision MUST define shared-table, schema, or database isolation and state its guarantee and operating trade-off.
Applies toartifact: record:decision
Why
Shared tables, separate schemas, and separate databases isolate differently and operate differently: each choice moves work between queries, migrations, backups, and scaling. Naming the model with its guarantee and its operating trade-off tells every later choice which isolation it can rely on.
Checka reviewer answers
Reviewed forThe adoption decision names shared-table, schema, or database isolation with its guarantee and operating trade-off.
Marked guidance: a reviewer answers this question.
Avoid Unplanned Isolation Models#
ruleruleguidancelitespec/ext/tenancy#avoid-unplanned-isolation-modelsA multi-tenant system MUST NOT mix isolation models without a decision and data evolution plan.
Applies toartifact: any
Why
One part of the product on shared tables and another on separate databases answers which rows each tenant sees two ways. Each mixed model needs its own decision with a data evolution plan, or backup, restore, and incident response each guess which guarantee covers which data.
Checka reviewer answers
Reviewed forEach mixed isolation model traces to a decision with a data evolution plan.
Marked guidance: a reviewer answers this question.
Resolve Tenant Identity from Trusted Context#
ruleruleguidancelitespec/ext/tenancy#resolve-tenant-identity-from-trusted-contextA trusted host boundary MUST resolve tenant identity from verified claims, host mapping, or another authenticated source.
Applies toartifact: any
Why
The tenant decides which data the request may touch, so only a trusted host boundary resolves it: verified claims, host mapping, or another authenticated source. An identity the caller supplies itself is a choice, not a fact.
Checka reviewer answers
Reviewed forEach trusted host boundary resolves the tenant from verified claims, host mapping, or another authenticated source.
Marked guidance: a reviewer answers this question.
Reject Unrestricted Tenant Input#
ruleruleguidancelitespec/ext/tenancy#reject-unrestricted-tenant-inputA tenant resolver MUST NOT accept an unrestricted tenant ID from request data.
Applies toartifact: any
Why
A tenant ID taken straight from request data lets any caller name any tenant. Rejecting unrestricted input keeps the request from choosing a data boundary outside its authenticated context.
Checka reviewer answers
Reviewed forThe tenant resolver code accepts a tenant identifier only from an authenticated source, never from request data.
Marked guidance: a reviewer answers this question.
Verify Actor and Target Tenancy#
ruleruleguidancelitespec/ext/tenancy#verify-actor-and-target-tenancyA protected multi-tenant operation MUST confirm actor and target tenancy, with the needed permission held at the resolved scope and the target belonging to it.
Applies toartifact: command-handler, query-handler
Why
Checking the caller alone leaves the target unchecked: an authorized caller can still name a resource from another tenant. The operation confirms both sides through one chain: the actor holds a role at the resolved scope, the role allows the permission, the use case declares that permission in its grant, and the target belongs to the same scope.
Checka reviewer answers
Reviewed forThe authorization tests fail a protected operation whose actor and target tenants mismatch.
Marked guidance: a reviewer answers this question.
Scope Every Use Case to Its Tenant Reach#
ruleruleguidancelitespec/ext/tenancy#scope-every-use-case-to-its-tenant-reachEvery use case in a multi-tenant product MUST declare its tenant scope as own, cross, public, or none in access.
Applies toartifact: record:use-case
Why
The tenant scope bounds which tenant data the call may touch: own stays inside the caller tenant, cross reaches across tenants, public works beyond tenant boundaries, and none touches no tenant data. Without the scope, a read inside one tenant and a read across all tenants look the same. litespec/core/access#scope-every-grant-to-its-tenant-reach states the values.
Example
An expense report declares own, a finance search across companies declares cross, a public catalog declares public, and a health check declares none.
Checka reviewer answers
Reviewed forEach use case declares own, cross, public, or none in its access tenant scope.
Marked guidance: a reviewer answers this question.
Isolate Administrative Cross-Tenant Access#
ruleruleguidancelitespec/ext/tenancy#isolate-administrative-cross-tenant-accessAn administrative cross-tenant operation MUST require a permission the actor holds through a role at platform scope, writing an audit record.
Applies toartifact: command-handler, query-handler
Why
Support and operations sometimes reach across tenants, and that exceptional reach needs exceptional control: a permission held through a role at platform scope, with an audit record for every use. Without both, one support login becomes an unscoped key to every customer's data. Grants that let one tenant act for another follow the delegation extension instead.
Checka reviewer answers
Reviewed forEach administrative cross-tenant use case names a platform-scope permission, and the audit trail holds a record for every cross-tenant run.
Marked guidance: a reviewer answers this question.
Use Selected Tenant Storage Support#
ruleruleguidancelitespec/ext/tenancy#use-selected-tenant-storage-supportA multi-tenant persistence implementation MUST resolve its tenant at session creation through the selected provider's own tenancy support.
Applies toartifact: any
Why
Session creation is the point that matters: a session opened without a tenant serves an unscoped next query. Resolving the tenant then, through the selected provider's own tenancy support, scopes every query the session runs, so no query carries the concern alone. Adding the filter per query instead leaves every new query one omission away from reading across tenants. The baseline provider resolves the tenant from the registered tenancy at session creation; an Entity Framework Core implementation resolves it when the context is created and applies it through a query filter convention.
Checka reviewer answers
Reviewed forThe session-creation code of each provider resolves the tenant through that provider's tenancy support.
Marked guidance: a reviewer answers this question.
Scope Tenant-Owned Records#
ruleruleguidancelitespec/ext/tenancy#scope-tenant-owned-recordsEvery tenant-owned document, query, unique constraint, cache key, outbox record, reaction, job, export, and object-storage path MUST include tenant scope.
Applies toartifact: any
Why
Documents, queries, unique constraints, cache keys, outbox records, reactions, jobs, exports, and object-storage paths each form a boundary where tenants meet. Leaving tenant scope out of any one of them discloses or mixes customer data through exactly that gap.
Checka reviewer answers
Reviewed forThe documents, queries, constraints, cache keys, outbox records, reactions, jobs, exports, and storage paths in code each carry tenant scope.
Marked guidance: a reviewer answers this question.
Apply Cross-Tenant Disclosure Policy#
ruleruleguidancelitespec/ext/tenancy#apply-cross-tenant-disclosure-policyA cross-tenant resource response MUST use the documented not-found or forbidden policy.
Applies toartifact: command-handler, query-handler
Why
A caller that names another tenant's resource forces an answer: pretend it does not exist, or admit it exists and refuse. The product documents one policy and applies it to every cross-tenant response, so callers cannot probe for resources through inconsistent answers.
Checka reviewer answers
Reviewed forEach cross-tenant response follows the documented not-found or forbidden policy, shown in the API tests.
Marked guidance: a reviewer answers this question.
Protect Tenant Diagnostics#
ruleruleguidancelitespec/ext/tenancy#protect-tenant-diagnosticsErrors, logs, metrics, and traces MUST NOT reveal another tenant's business identifiers or data.
Applies toartifact: any
Why
Errors, logs, metrics, and traces travel to readers the API never authorized: support screens, dashboards, and log stores shared across tenants. Scrubbing other tenants' identifiers and data from diagnostics keeps the disclosure policy whole past the API boundary.
Checka reviewer answers
Reviewed forThe error, log, metric, and trace outputs hold no other tenant's identifiers or data.
Marked guidance: a reviewer answers this question.
Scope Tenant Operations#
ruleruleguidancelitespec/ext/tenancy#scope-tenant-operationsBackup, restore, replay, deletion, export, support access, and incident investigation MUST document one-tenant or all-tenant scope.
Applies toartifact: record:runbook
Why
Backup, restore, replay, deletion, export, support access, and incident investigation each touch more data than one normal request. Documenting each as one-tenant or all-tenant tells the operator how much data the run touches before it starts.
Checka reviewer answers
Reviewed forThe backup, restore, replay, deletion, export, support, and investigation procedures each state one-tenant or all-tenant scope.
Marked guidance: a reviewer answers this question.
Record an All-Tenant Operation#
ruleruleguidancelitespec/ext/tenancy#record-an-all-tenant-operationAn all-tenant operation MUST record actor, operation, reason, tenant count, and reached tenant identifiers up to the adoption decision bound.
Applies toartifact: command-handler, job
Why
A documented scope states what an operation may touch, not what one run touched, and the run is what a tenant asks about after an incident. An all-tenant run is also the one case where no single tenant's audit trail holds the whole event. The OWASP logging guidance treats an administrative action over many subjects as one event to record: actor, operation, reason, tenant count, and reached tenant identifiers up to the adoption decision bound. When the audit extension is selected, the record is an audit record; otherwise it stays a standalone record.
Example
A support export covering every tenant records the operator, the export, the tenant count, and the incident it was run for.
Checka reviewer answers
Reviewed forThe record of each all-tenant run names actor, operation, reason, tenant count, and reached tenant identifiers up to the adoption bound.
Marked guidance: a reviewer answers this question.
Declare Delegation Grants on Both Sides#
ruleruleguidancelitespec/ext/tenancy#declare-delegation-grants-on-both-sidesA delegation MUST declare the delegator grant naming who may be acted for and the acting grant naming who may act.
Applies toartifact: record:role
Why
Delegation lets one actor act for another, so a grant on one side alone leaves the other side unreviewed. The delegator grant names the role at its scope that may be acted for and the permissions open to delegation; the acting grant names the role permitted to act and the pairing it acts under. litespec/core/access#grant-across-tenants-only-through-declared-delegation requires both declarations.
Example
An assistant acts for a manager only where the manager delegation grant and the assistant acting grant both name the pairing.
Checka reviewer answers
Reviewed forEach delegation declares a delegator grant naming who may be acted for and an acting grant naming who may act.
Marked guidance: a reviewer answers this question.
Scope Delegated Grants to Named Permissions#
ruleruleguidancelitespec/ext/tenancy#scope-delegated-grants-to-named-permissionsA delegation MUST name the permissions it delegates, never all permissions.
Applies toartifact: record:role
Why
A delegation open to every permission hands the whole tenant to the acting party: one pairing meant for reading reports also moves money. Naming each delegated permission bounds the pairing to what the delegator intended, and each new permission needs its own review in the pull request before the acting party holds it.
Checka reviewer answers
Reviewed forEach delegation names its delegated permissions, with none open to all permissions.
Marked guidance: a reviewer answers this question.
Bound Every Delegation in Time#
ruleruleguidancelitespec/ext/tenancy#bound-every-delegation-in-timeA delegation MUST carry an expiry or a revocation path.
Applies toartifact: record:role
Why
A grant with no end outlives its reason: the assistant keeps acting after the engagement ends, and nobody reopens the pairing because no date asks for it. An expiry ends the pairing on its date, and a revocation path ends it on demand. Either way, the end is stated beside the grant instead of left for an incident to discover.
Checka reviewer answers
Reviewed forEach delegation carries an expiry or a revocation path.
Marked guidance: a reviewer answers this question.
Audit Actions Taken Under Delegation#
ruleruleguidancelitespec/ext/tenancy#audit-actions-taken-under-delegationAn action that an actor takes under delegation MUST record the delegation grant used.
Applies toartifact: command-handler, query-handler
Why
An acted-for action without a marker reads exactly like the delegator's own action, so a reviewer cannot tell who actually acted. Recording the delegation grant used puts the acting party on the record beside the delegator. litespec/ext/audit#record-delegated-administrative-access states the same marker for administrative grants.
Checka reviewer answers
Reviewed forEach action under delegation records the delegation grant used.
Marked guidance: a reviewer answers this question.
Prove Delegated Calls with Scenarios#
ruleruleguidancelitespec/ext/tenancy#prove-delegated-calls-with-scenariosA delegation MUST prove an allowed call and a refused call with scenarios.
Applies toartifact: record:scenario
Why
A pairing described only in prose hides where its grant stops: the allowed assistant and the uninvited one read alike. A scenario walking an allowed call and a scenario ending a call without delegation in the derived forbidden result pin the boundary as checked facts. litespec/core/access#prove-hierarchies-derived-trust-and-delegation-with-scenarios requires both scenarios.
Checka reviewer answers
Reviewed forEach delegation proves an allowed call and a refused call with scenarios.
Marked guidance: a reviewer answers this question.
Conventions#
Use Typed Tenant Identities#
conventiondefaultguidancelitespec/ext/tenancy#use-typed-tenant-identitiesRepresent tenant identity with a strongly typed TenantId.
ReplacementA consumer records a tenant-identity convention naming the type its tenant references carry.
Applies toartifact: any
Why
A bare string tenant ID passes anywhere a string passes: into an actor field, an aggregate key, or a log line. A strongly typed TenantId fails compilation at each of those mistakes instead of failing in production.
Checka reviewer answers
Reviewed forTenant references carry the TenantId type, or the tenant-identity convention names another type.
Marked guidance: a reviewer answers this question.
Use One Tenant Accessor#
conventiondefaultguidancelitespec/ext/tenancy#use-one-tenant-accessorUse one current-tenant accessor at the trusted host boundary.
ReplacementA consumer records a tenant-access convention naming where its readers find the current tenant.
Applies toartifact: any
Why
Every boundary reads the current tenant, and two accessors can disagree about which tenant is current. One accessor at the trusted host boundary gives every reader the same answer from the same trusted source.
Checka reviewer answers
Reviewed forOne current-tenant accessor serves the trusted host boundary, or the tenant-access convention names where readers find the tenant.
Marked guidance: a reviewer answers this question.
Pass Tenant Scope Explicitly#
conventiondefaultguidancelitespec/ext/tenancy#pass-tenant-scope-explicitlyPass tenant scope through Application messages or trusted ports rather than ambient Domain request state.
ReplacementA consumer records a tenant-scope convention naming how its Domain receives tenant scope.
Applies toartifact: use-case, port
Why
Domain logic that reads the tenant from ambient request state depends on the host to run at all, which complicates tests and reuse. Passing the scope through Application messages or trusted ports keeps the tenant visible in the call and the Domain free of host state.
Checka reviewer answers
Reviewed forDomain code reads no ambient tenant state, or the tenant-scope convention names how the Domain receives scope.
Marked guidance: a reviewer answers this question.
Record Delegation Grants with Roles#
conventiondefaultguidancelitespec/ext/tenancy#record-delegation-grants-with-rolesRecord each delegation grant on the role page that holds it.
ReplacementA consumer records a delegation-records convention naming where its delegation grants live.
Applies toartifact: record:role
Why
The grant qualifies the role: the delegator role names who may be acted for, and the acting role names who may act. Recording each grant on its role page keeps the pairing beside the permission it extends, so a review reads both at once.
Checka reviewer answers
Reviewed forEach delegation grant sits on its role page, or the delegation-records convention names where grants live.
Marked guidance: a reviewer answers this question.
Dependencies#
Packages: Tenancy support for Marten ships in the baseline persistence package. A project that isolates tenants with another provider records a decision and pins that provider in the manifest.
Related extensions: The audit extension owns tamper-resistant evidence when an all-tenant run needs an audit record. The plans extension owns entitlements when tenants buy different tiers.
Guides: None.