Classes
Section titled “Classes”CachedTenantConfigProvider
Section titled “CachedTenantConfigProvider”Decorator that wraps any TenantConfigProviderProtocol with in-process TTL caching.
The entire config dict for a tenant is cached as a unit. A write
(set_config) invalidates the cached dict for that tenant so the
next read fetches fresh data.
Usage
base_provider = InMemoryTenantProvider()cached = CachedTenantConfigProvider(base_provider, ttl=60)value = await cached.get_config("tenant-abc", "feature_x")base_provider = InMemoryTenantProvider()cached = CachedTenantConfigProvider(base_provider, ttl=60)value = await cached.get_config("tenant-abc", "feature_x")Initialise the caching decorator.
| Parameter | Type | Description |
|---|---|---|
| `inner` | TenantConfigProviderProtocol | The underlying config provider to wrap. |
| `ttl` | int | Cache TTL in seconds. |
Get a single config value, using the tenant-level cache.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is queried. |
| `key` | str | Configuration key. |
| Type | Description |
|---|---|
| Any | None | The value if set, or ``None``. |
Get all config for a tenant, from cache if valid.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is retrieved. |
| Type | Description |
|---|---|
| dict[str, Any] | Dictionary of all key-value pairs for the tenant. |
Set a config value and invalidate the cache.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is updated. |
| `key` | str | Configuration key. |
| `value` | Any | New value (must be JSON-serialisable). |
CompositeResolver
Section titled “CompositeResolver”Iterates the ResolverRegistry in priority order and returns the first non-``None`` result.
This is the primary resolution entry point used by TenantContextMiddleware.
Usage
resolver = CompositeResolver(registry)tenant_id = await resolver.resolve(context)resolver = CompositeResolver(registry)tenant_id = await resolver.resolve(context)Initialise with a pre-populated resolver registry.
| Parameter | Type | Description |
|---|---|---|
| `registry` | ResolverRegistry | ResolverRegistry containing all configured resolvers. |
Try each resolver in priority order and return the first result.
| Parameter | Type | Description |
|---|---|---|
| `context` | TenantResolutionContext | Immutable snapshot of the current request. |
| Type | Description |
|---|---|
| str | None | The resolved ``tenant_id``, or ``None`` if no resolver could determine the tenant. |
Resolve the tenant and report which resolver won.
Reuses the existing registry priority order. The resolver name
lets trust-tiered enforcement distinguish a server-verified source
(jwt_claim) from client-influenced ones.
| Parameter | Type | Description |
|---|---|---|
| `context` | TenantResolutionContext | Immutable snapshot of the current request. |
| Type | Description |
|---|---|
| tuple[str, str] | None | ``(resolver_name, tenant_id)`` of the winning resolver, or ``None`` when no resolver could determine the tenant. |
ConfigOverridesConfig
Section titled “ConfigOverridesConfig”Configuration for the per-tenant config override layer.
Attributes: cache_ttl: Seconds a tenant’s config dict is cached in memory.
DatabaseIsolationStrategy
Section titled “DatabaseIsolationStrategy”Full database isolation per tenant (reference implementation).
This is a reference implementation that applications must subclass
and extend with their infrastructure-specific provisioning logic
(e.g. AWS RDS, GCP Cloud SQL). It is NOT in
IsolationStrategyRegistry.with_defaults().
Attributes:
name: "database"
Record the tenant’s database backend in the execution context.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The active tenant. |
| `context` | dict[str, Any] | Mutable execution context dict. |
No-op (connection routing resets with the request scope).
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose isolation context to remove. |
Provision a database for the tenant.
Must be overridden by application subclasses with infrastructure-specific database creation logic.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The newly created tenant. |
| Exception | Description |
|---|---|
| NotImplementedError | Always — subclasses must override this. |
Deprovision the tenant’s database.
Must be overridden by application subclasses.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant being deactivated. |
| Exception | Description |
|---|---|
| NotImplementedError | Always — subclasses must override this. |
InMemoryTenantProvider
Section titled “InMemoryTenantProvider”Dict-backed tenant store implementing both TenantProviderProtocol and TenantConfigProviderProtocol.
Suitable for unit testing, integration testing, and single-process development environments. Not suitable for production multi-process deployments (state is not shared across processes).
A single instance acts as both the tenant store and the config store, so only one registration is needed in the DI container.
Initialise empty tenant and config stores.
Return the tenant record for tenant_id, or None.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Unique tenant identifier. |
| Type | Description |
|---|---|
| TenantInfo | None | TenantInfo or ``None``. |
Return the tenant with the given slug, or None.
| Parameter | Type | Description |
|---|---|---|
| `slug` | str | URL-safe tenant identifier. |
| Type | Description |
|---|---|
| TenantInfo | None | TenantInfo or ``None``. |
List tenants, optionally filtering to active ones.
| Parameter | Type | Description |
|---|---|---|
| `active_only` | bool | Return only active tenants when ``True``. |
| Type | Description |
|---|---|
| list[TenantInfo] | List of TenantInfo. |
Create and persist a new tenant record.
| Parameter | Type | Description |
|---|---|---|
| `command` | CreateTenantCommand | Creation parameters. |
| Type | Description |
|---|---|
| Result[TenantInfo, TenantError] | ``Ok(TenantInfo)`` always (in-memory store never fails). |
Update mutable fields on an existing tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to update. |
| `command` | UpdateTenantCommand | Fields to apply (``None`` fields are skipped). |
| Type | Description |
|---|---|
| Result[TenantInfo, TenantError] | ``Ok(TenantInfo)`` on success, ``Err(TenantNotFoundError)`` if not found. |
Mark a tenant as inactive.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to deactivate. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantNotFoundError)`` if not found. |
Mark a tenant as active.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to activate. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantNotFoundError)`` if not found. |
Mark a tenant as suspended.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to suspend. |
| `reason` | str | None | Optional reason string (stored in metadata for reference). |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantNotFoundError)`` if not found. |
Retrieve a single config value for a tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is queried. |
| `key` | str | Configuration key. |
| Type | Description |
|---|---|
| Any | None | The value if set, or ``None``. |
Retrieve all config for a tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is retrieved. |
| Type | Description |
|---|---|
| dict[str, Any] | A copy of the tenant's config dict (empty dict if none set). |
Set a config value for a tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is updated. |
| `key` | str | Configuration key. |
| `value` | Any | New value. |
IntegrationConfig
Section titled “IntegrationConfig”Configuration for cross-package integration features.
Attributes:
cache_key_prefix: When True, tenant-prefix cache keys via
TenantCacheKeyDecorator.
sql_context_bridge: When True, sync the core TENANT_ID context
key into oridecon-sql’s DB context via
TenantSQLContextBridge.
IsolationStrategyRegistry
Section titled “IsolationStrategyRegistry”Registry of TenantIsolationStrategyProtocol implementations keyed by ``strategy.name``.
Supports per-tenant strategy assignment via set_tenant_strategy and get_tenant_strategy for tier migration scenarios.
Usage
registry = IsolationStrategyRegistry.with_defaults()strategy = registry.get("row_level")await strategy.provision_isolation("tenant-abc")registry = IsolationStrategyRegistry.with_defaults()strategy = registry.get("row_level")await strategy.provision_isolation("tenant-abc")Initialise an empty registry.
Register an isolation strategy.
| Parameter | Type | Description |
|---|---|---|
| `strategy` | TenantIsolationStrategyProtocol | Strategy object with a ``name`` attribute. |
Retrieve a strategy by name.
| Parameter | Type | Description |
|---|---|---|
| `name` | str | The strategy name (e.g. ``"row_level"``). |
| Type | Description |
|---|---|
| TenantIsolationStrategyProtocol | The registered strategy. |
| Exception | Description |
|---|---|
| TenantError | If no strategy with the given name is registered. |
Return the names of all registered strategies.
| Type | Description |
|---|---|
| list[str] | List of strategy name strings. |
Assign an isolation strategy to a specific tenant.
Used during tier migration to cut over a tenant to its new isolation level. The strategy must already be registered.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant identifier. |
| `strategy_name` | str | The strategy name to assign. |
| Exception | Description |
|---|---|
| TenantError | If the strategy is not registered. |
Return the strategy name assigned to a tenant, or default.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant identifier. |
| `default` | str | None | Fallback value when no assignment exists. |
| Type | Description |
|---|---|
| str | None | The assigned strategy name, or *default*. |
Create a registry pre-populated with the row-level strategy.
LifecycleConfig
Section titled “LifecycleConfig”Configuration for tenant lifecycle and provisioning.
Attributes:
isolation_strategy: Name of the isolation strategy to use.
Defaults to "row_level".
auto_provision_isolation: When True, the provisioner runs the
isolation strategy automatically on tenant creation.
MigrationResult
Section titled “MigrationResult”Outcome of a completed migration.
MigrationServiceConfig
Section titled “MigrationServiceConfig”Configuration for TenantMigrationService.
ResolutionConfig
Section titled “ResolutionConfig”Configuration for the tenant resolution chain.
Attributes:
resolvers: Ordered list of resolver names to activate.
Available names: "jwt_claim", "header", "subdomain",
"path".
header_name: HTTP header name read by HeaderTenantResolver.
subdomain_pattern: Optional base domain for subdomain extraction
(e.g. "app.com" → acme from acme.app.com).
None disables subdomain resolver even if listed.
path_pattern: Path pattern for PathTenantResolver.
Use {tenant_id} as the placeholder.
jwt_claim_key: JWT claim key read by JWTClaimTenantResolver.
validator_cache_ttl: Seconds a validated TenantInfo is cached
by TenantValidator.
trusted_resolvers: Resolver names exempt from the membership
cross-check because their source is server-verified.
Defaults to ["jwt_claim"].
strict_membership: Default-deny gate. When True (default), a
tenant resolved by a non-trusted resolver is bound only after
the membership cross-check passes. Setting False reproduces
the pre-fix behavior for migration only and is unsafe.
ResolverRegistry
Section titled “ResolverRegistry”Ordered registry of TenantResolverProtocol instances.
Resolvers are stored and returned in ascending priority order
(lower number = tried first = higher trust level).
Usage
registry = ResolverRegistry()registry.register(HeaderTenantResolver("x-tenant-id"))for resolver in registry.ordered(): ...registry = ResolverRegistry()registry.register(HeaderTenantResolver("x-tenant-id"))for resolver in registry.ordered(): ...Initialise an empty resolver registry.
Register a resolver.
| Parameter | Type | Description |
|---|---|---|
| `resolver` | TenantResolverProtocol | A resolver implementing TenantResolverProtocol. |
Return resolvers sorted by ascending priority.
| Type | Description |
|---|---|
| list[TenantResolverProtocol] | List of resolvers in priority order (lowest number first). |
Build a registry from a list of resolver names.
Only resolvers whose names appear in resolver_names are instantiated. Unknown names are silently ignored.
| Parameter | Type | Description |
|---|---|---|
| `resolver_names` | list[str] | Ordered list of resolver names to activate. |
| `header_name` | str | Header name for HeaderTenantResolver. |
| `subdomain_pattern` | str | None | Base domain for SubdomainTenantResolver. |
| `path_pattern` | str | None | Path pattern for PathTenantResolver. |
| `jwt_claim_key` | str | Claim key for JWTClaimTenantResolver. |
| Type | Description |
|---|---|
| ResolverRegistry | A populated ResolverRegistry. |
RowLevelIsolationStrategy
Section titled “RowLevelIsolationStrategy”Row-level isolation strategy (default).
Provisioning is a no-op because isolation is enforced at query time by
oridecon-sql rather than by separate schema/database resources:
multi_tenant=Truerepositories (the preferred path) auto-scope every query to the ambient tenant and fail closed withTenantScopingErrorwhen no tenant is active.TenantScope(declared per-repository via__scopes__) is an opt-in fallback for repositories that don’t usemulti_tenant.
Attributes:
name: "row_level"
No-op — isolation is handled at the ORM/query level.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The active tenant. |
| `context` | dict[str, Any] | Execution context dict (not modified). |
No-op.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose isolation context to remove. |
No-op provisioning — nothing to create for row-level isolation.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The newly created tenant. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` always. |
No-op deprovisioning.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant being deactivated. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` always. |
SchemaIsolationStrategy
Section titled “SchemaIsolationStrategy”Creates a PostgreSQL schema per tenant.
This is a reference implementation. Applications must register it
explicitly — it is NOT in IsolationStrategyRegistry.with_defaults().
Requires oridecon-tenancy[sql]. Applications that need it must also
configure the deprovision policy appropriate for their infrastructure.
Attributes:
name: "schema"
Initialise the strategy.
| Parameter | Type | Description |
|---|---|---|
| `db_provider` | DatabaseProviderProtocol | Database provider implementing DatabaseProviderProtocol. |
| `deprovision_policy` | str | ``"rename"`` (archive schema, default) or ``"drop"`` (permanently destroy). |
Create a PostgreSQL schema for the tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The newly created tenant. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantProvisioningError)`` if the ``tenant_id`` contains characters that are unsafe for schema names. |
Set the PostgreSQL search_path in the execution context.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The active tenant. |
| `context` | dict[str, Any] | Mutable execution context dict. |
No-op (search_path resets with the DB connection).
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose isolation context to remove. |
Drop or rename the tenant schema.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant being deactivated. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success. |
TenancyConfig
Section titled “TenancyConfig”Top-level tenancy configuration.
Loaded from the tenancy: key in application.yaml, with
environment variable overrides via ORI_TENANCY__* prefix.
Composed of four focused sub-configs.
Attributes: resolution: Resolver chain configuration. lifecycle: Lifecycle and isolation strategy configuration. overrides: Per-tenant config override layer configuration. integration: Cross-package integration feature toggles.
TenancyModule
Section titled “TenancyModule”Multi-tenant resolution, lifecycle, isolation, and configuration.
Use configure to register a fully configured tenancy stack. Use stub for test environments (in-memory store, header resolver only, no isolation provisioning).
Usage
app.add_module(TenancyModule.configure( config=TenancyConfig( resolution=ResolutionConfig(resolvers=["header"]), )))app.add_module(TenancyModule.configure( config=TenancyConfig( resolution=ResolutionConfig(resolvers=["header"]), )))Create a configured tenancy module.
| Parameter | Type | Description |
|---|---|---|
| `config` | TenancyConfig | None | TenancyConfig or ``None`` for framework defaults. |
| Type | Description |
|---|---|
| DynamicModule | A DynamicModule descriptor. |
Create a test-friendly tenancy module.
Uses an in-memory store, header resolver only, no isolation provisioning, and no cache key prefix or SQL bridge.
| Type | Description |
|---|---|
| DynamicModule | A DynamicModule for testing. |
TenancyProvider
Section titled “TenancyProvider”Bundle provider that orchestrates all tenancy sub-providers.
Mirrors the oridecon-auth AuthBundleProvider pattern. Delegates
registration, boot, and shutdown to four focused sub-providers in order:
- TenantResolutionProvider
- TenantLifecycleProvider
- TenantConfigProvider
- TenantIntegrationProvider
Usage
from oridecon.tenancy.di.provider import TenancyProviderfrom oridecon.tenancy.config import TenancyConfig
provider = TenancyProvider(TenancyConfig(...))from oridecon.tenancy.di.provider import TenancyProviderfrom oridecon.tenancy.config import TenancyConfig
provider = TenancyProvider(TenancyConfig(...))Initialise the bundle provider.
| Parameter | Type | Description |
|---|---|---|
| `config` | TenancyConfig | None | Optional TenancyConfig. When ``None``, the orchestrator injects the typed ``tenancy`` yaml section after construction (``config_key``) and before register; framework defaults apply if no section exists. |
Delegate registration to all sub-providers.
Late config binding: when configure() ran with no explicit config,
the orchestrator injects the typed tenancy yaml section after
construction and before this call; sub-providers are composed now so
the automatic path behaves identically to the explicit one. An
explicit constructor config always wins over any later assignment to
config.
| Parameter | Type | Description |
|---|---|---|
| `container` | ContainerRegistrarProtocol | The DI container registrar. |
Delegate boot to all sub-providers.
| Parameter | Type | Description |
|---|---|---|
| `container` | BootContainerProtocol | The DI container for boot phase. |
Delegate shutdown to sub-providers in reverse order.
Aggregate health across all sub-providers.
| Type | Description |
|---|---|
| HealthCheckResult | A HealthCheckResult reflecting the worst sub-provider status. |
TenantConfigService
Section titled “TenantConfigService”High-level per-tenant configuration service.
Combines a raw TenantConfigProviderProtocol backend with a defaults dict and event emission.
Priority order for get:
- Tenant-specific override from the provider.
- Application default from
defaults. None.
Usage
service = TenantConfigService(provider, defaults={"max_users": 50}, event_bus=bus)value = await service.get("tenant-abc", "max_users")service = TenantConfigService(provider, defaults={"max_users": 50}, event_bus=bus)value = await service.get("tenant-abc", "max_users")Initialise the service.
| Parameter | Type | Description |
|---|---|---|
| `config_provider` | TenantConfigProviderProtocol | Underlying key-value config backend. |
| `defaults` | dict[str, Any] | Application-level default values used when no tenant override is set. |
| `event_bus` | DomainEventPublisherProtocol | Event bus for publishing TenantConfigChanged events. |
Get a config value with fallback to defaults.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is queried. |
| `key` | str | Configuration key. |
| Type | Description |
|---|---|
| Any | The tenant override if set, otherwise the application default, otherwise ``None``. |
Set a config value and publish the change event.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose config is updated. |
| `key` | str | Configuration key. |
| `value` | Any | New value. |
Merge defaults with tenant overrides (tenant wins on conflict).
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose effective config is computed. |
| Type | Description |
|---|---|
| dict[str, Any] | A merged dict where tenant overrides take precedence over defaults. |
TenantContextMiddleware
Section titled “TenantContextMiddleware”ASGI middleware that resolves the tenant for every HTTP/WebSocket request.
This middleware never rejects a request; it refuses to bind a
tenant it cannot verify. It resolves the tenant, authorizes the
binding against the caller’s identity (server-verified resolvers bind
directly; client-influenced ones require a membership cross-check),
then sets TENANT_ID in the shared
Context and stores the
TenantInfo in
scope["state"]["tenant"] for downstream use by
TenantGuard. The context
token is always reset on exit.
Registration order: after RequestContextMiddleware and
DIScopeMiddleware, before application middleware.
Initialise the middleware.
| Parameter | Type | Description |
|---|---|---|
| `app` | ASGIApp | The next ASGI application in the chain. |
| `resolver` | Any | CompositeResolver instance. |
| `validator` | Any | TenantValidator instance. |
| `ctx` | Context | The shared Context. |
TenantGuard
Section titled “TenantGuard”Route-level guard that enforces tenant context presence.
Applied via @use_guards(TenantGuard) on controllers or individual
routes. Returns False (triggering a 403 response) if no active
tenant is present in the current request scope.
The guard reads TENANT_ID from the shared context AND verifies the
tenant stored in scope["state"]["tenant"] has ACTIVE status.
Usage
@use_guards(TenantGuard)class TenantAwareController: ...@use_guards(TenantGuard)class TenantAwareController: ...Initialise the guard with the shared context.
| Parameter | Type | Description |
|---|---|---|
| `ctx` | Context | The shared Context. |
Check whether the current request may proceed.
| Parameter | Type | Description |
|---|---|---|
| `execution_context` | Any | The framework execution context, which must expose ``execution_context.request.scope`` for tenant state lookup. |
| Type | Description |
|---|---|
| bool | ``True`` if a valid, active tenant is in context; ``False`` otherwise (results in 403 Forbidden). |
TenantLifecycleService
Section titled “TenantLifecycleService”Orchestrates tenant CRUD operations with event emission and cache invalidation.
All mutations publish the corresponding domain event via the event bus and invalidate the TenantValidator cache so subsequent requests see fresh state.
Usage
service = TenantLifecycleService(provider, provisioner, event_bus, validator)result = await service.create_tenant(CreateTenantCommand(slug="acme", name="ACME Corp"))if result.is_ok(): tenant = result.unwrap()service = TenantLifecycleService(provider, provisioner, event_bus, validator)result = await service.create_tenant(CreateTenantCommand(slug="acme", name="ACME Corp"))if result.is_ok(): tenant = result.unwrap()Initialise the service.
| Parameter | Type | Description |
|---|---|---|
| `provider` | TenantProviderProtocol | Tenant storage backend. |
| `provisioner` | TenantProvisioner | Isolation provisioner for new/removed tenants. |
| `event_bus` | DomainEventPublisherProtocol | Event bus for publishing domain events. |
| `validator` | TenantValidator | Validator cache to invalidate on mutations. |
Create a new tenant, provision isolation, and publish the event.
| Parameter | Type | Description |
|---|---|---|
| `command` | CreateTenantCommand | Tenant creation parameters. |
| Type | Description |
|---|---|
| Result[TenantInfo, TenantError] | ``Ok(TenantInfo)`` on success, ``Err(TenantError)`` on failure. |
Update a tenant’s mutable fields.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to update. |
| `command` | UpdateTenantCommand | Fields to apply. |
| Type | Description |
|---|---|
| Result[TenantInfo, TenantError] | ``Ok(TenantInfo)`` with the updated record, ``Err(TenantError)`` on failure. |
Deactivate a tenant and publish the event.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to deactivate. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantError)`` on failure. |
Activate a tenant and publish the event.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to activate. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantError)`` on failure. |
Suspend a tenant and publish the event.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to suspend. |
| `reason` | str | None | Optional reason for the suspension. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success, ``Err(TenantError)`` on failure. |
TenantMigrationService
Section titled “TenantMigrationService”Public API for tenant tier migrations.
Builds and executes TenantTierMigrationSaga instances, publishes lifecycle events, and returns structured results.
Usage
result = await migration_service.migrate_tier( tenant_id="tenant-abc", target_tier="m5",)if result.is_ok(): print(result.unwrap())result = await migration_service.migrate_tier( tenant_id="tenant-abc", target_tier="m5",)if result.is_ok(): print(result.unwrap())Initialise the service.
| Parameter | Type | Description |
|---|---|---|
| `tenant_provider` | TenantProviderProtocol | Tenant storage backend. |
| `isolation_registry` | IsolationStrategyRegistry | Registry of isolation strategies. |
| `config_service` | TenantConfigService | Per-tenant config overrides. |
| `write_pause_registry` | WritePauseRegistry | Write-pause coordination. |
| `checkpoint_store` | ContentCheckpointStoreProtocol | Content-addressed checkpoint backend. |
| `copy_strategy` | MigrationCopyStrategy | Strategy for copying tenant data. |
| `event_bus` | DomainEventPublisherProtocol | None | Optional event bus for lifecycle events. |
| `config` | MigrationServiceConfig | None | Optional service configuration. |
Migrate a tenant to a new isolation tier.
This is the main entry point. It validates the request, builds a TenantTierMigrationSaga, executes it, and publishes lifecycle events.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant to migrate. |
| `target_tier` | str | Target tier name (e.g. ``"m5"``, ``"m6"``). |
| `actor_id` | str | None | Optional identifier of the user / system that triggered the migration. |
| Type | Description |
|---|---|
| Result[MigrationResult, TenantError] | ``Ok(MigrationResult)`` on success, ``Err(MigrationError)`` on failure. |
TenantProvisioner
Section titled “TenantProvisioner”Orchestrates data isolation setup and teardown when tenants are created or deactivated.
When auto_provision is False (e.g. in tests), both
provision and deprovision are no-ops that immediately
return Ok(None).
Initialise the provisioner.
| Parameter | Type | Description |
|---|---|---|
| `strategy` | TenantIsolationStrategyProtocol | The isolation strategy to use for provisioning. |
| `auto_provision` | bool | When ``False``, skip provisioning entirely. Useful for testing. |
Provision isolation resources for a new tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The newly created tenant. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success (or when ``auto_provision=False``), ``Err(TenantError)`` on failure. |
Deprovision isolation resources for a deactivated tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant being deactivated. |
| Type | Description |
|---|---|
| Result[None, TenantError] | ``Ok(None)`` on success (or when ``auto_provision=False``), ``Err(TenantError)`` on failure. |
TenantValidator
Section titled “TenantValidator”Validates a ``tenant_id`` against the tenant store with TTL caching.
Caches the lookup result in an in-process dict. In multi-process deployments the cache is process-local; the TTL bounds staleness.
Usage
validator = TenantValidator(provider, cache_ttl=300)info = await validator.validate("tenant-abc")if info: # tenant is active ...validator = TenantValidator(provider, cache_ttl=300)info = await validator.validate("tenant-abc")if info: # tenant is active ...Initialise the validator.
| Parameter | Type | Description |
|---|---|---|
| `provider` | TenantProviderProtocol | Tenant storage implementing TenantProviderProtocol. |
| `cache_ttl` | int | Seconds to cache a validated TenantInfo record. Defaults to 300 seconds. |
| `membership` | TenantMembershipProtocol | None | Optional membership check. When ``None`` and ``strict_membership`` is ``True``, non-trusted resolvers never bind a tenant (default-deny). |
| `trusted_resolvers` | list[str] | None | Resolver names exempt from the membership check because their source is server-verified. Defaults to ``["jwt_claim"]``. |
| `strict_membership` | bool | When ``True`` (default), deny tenant binding when a non-trusted resolver's tenant cannot be verified against the caller's identity. Setting ``False`` reproduces the pre-fix behavior and is **unsafe** — it is a migration-only lever. |
Validate that a tenant exists and is active.
Results are cached for cache_ttl seconds. Returns None for
inactive, suspended, or provisioning tenants.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | Identifier of the tenant to validate. |
| Type | Description |
|---|---|
| TenantInfo | None | The TenantInfo if the tenant is active, or ``None`` otherwise. |
Remove a specific tenant from the cache.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant whose cache entry should be evicted. |
Clear the entire validator cache.
Decide whether tenant_id may be bound to the caller.
Trusted resolvers are server-verified and bind without further
checks. Any other resolver must pass the membership cross-check
against user_id; when strict and verification is impossible
(no membership protocol bound, or no user_id), the tenant is
refused — never bound unverified (default-deny).
| Parameter | Type | Description |
|---|---|---|
| `resolver_name` | str | Name of the resolver that won the chain. |
| `user_id` | str | None | Authenticated identity from ``scope["state"]["user_id"]`` (``None`` for anonymous). |
| `tenant_id` | str | The tenant the resolver produced. |
| Type | Description |
|---|---|
| bool | ``True`` if the tenant may be bound, ``False`` otherwise. |
WritePauseRegistry
Section titled “WritePauseRegistry”Coordinates write-pausing during tenant tier migration.
Uses an optional CacheBackendProtocol for distributed deployments; falls back to an in-memory dict.
Services check is_paused before accepting write operations for a tenant undergoing migration.
Initialise the registry.
| Parameter | Type | Description |
|---|---|---|
| `backend` | CacheBackendProtocol | None | Optional distributed cache backend. |
Pause writes for a tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant to pause. |
| `reason` | str | Human-readable reason for the pause. |
Resume writes for a tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant to resume. |
Check whether writes are paused for a tenant.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant to check. |
| Type | Description |
|---|---|
| bool | ``True`` if writes are paused. |
Return the reason writes are paused, or None.
| Parameter | Type | Description |
|---|---|---|
| `tenant_id` | str | The tenant to check. |