Orleans.Lattice.Api.TenantAdmin
This page documents Orleans.Lattice.Api.TenantAdmin 9.9.0, in the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at README.md, and llms.txt lists every page.Transport-agnostic tenant administration and region-residency control facades for Orleans.Lattice multi-tenancy: one coherent, discoverable, authorized surface for the tenant lifecycle and each tenant's region residency, plus a tenant-scoped tree-administration facade. Every transport binding (the gRPC service, the MCP tool group) is a thin adapter over these surfaces, so the control semantics are written and tested once and no transport concern leaks into the control logic.
What is it?
This package is the control plane for the Orleans.Lattice.Tenancy
companion. It mirrors the TreeAdmin packaging
convention exactly: the contracts live in Orleans.Lattice.Api.Abstractions (under
TenantAdmin/) - except ILatticeTenantScopedTreeAdmin and its
TenantScopeRequiredException, which this package declares - the implementations
here, the gRPC binding in a sibling package, and
an MCP TenantAdmin tool group. It composes the existing tree-administration
facade (ILatticeTreeAdmin) and the schema engine's in-process admin
(ILatticeSchemaAdmin) rather than reimplementing them.
The facades exposed are:
ILatticeTenantAdmin- the tenant lifecycle: create, suspend, resume, delete, and author per-tenant resource quotas.ILatticeTenantRegionAdmin- per-tenant region residency: authorize the allowed region set, set the residency set within it, read per-region status.ILatticeTenantSelfService- the read-only self-awareness counterpart: which tenant the caller is operating as, which tenants it may see, and one tenant's lifecycle and per-region residency. It holds no lifecycle authority at all, so it is safe to expose wherever tenancy is enabled without granting an administrative capability.ILatticeTenantScopedTreeAdmin- tree administration executed inside a single tenant's namespace (create/check/delete/recover/purge trees and manage per-tree schema policy), so a delegated tenant admin drives tree lifecycle without reaching outside its tenant.ILatticeTenantAccessAdmin- tenant access administration: list, add, and remove a tenant's tenant-admin subjects, so membership can change after creation rather than being frozen at the create-time seed.ILatticeTenantGrantAdmin- cross-tenant grant administration: the two-step agreement by which one tenant exposes a scope of its data to another (the granting tenant offers, the grantee approves or rejects, either party may revoke), plus a listing of a tenant's issued and received grants.ILatticeTenantQuotaUsage- the read-only usage-against-quota report: per dimension, a tenant's consumption next to its steady-state and burst-adjusted ceilings.
The gRPC binding serves every facade
above except ILatticeTenantScopedTreeAdmin, which no transport binding exposes. The
MCP binding exposes the self-service reads and, behind its control opt-in, only the
lifecycle, quota, and region-residency verbs (see the
tenancy guide).
Core properties
- Fail-closed authorization. Every lifecycle, region-residency, access, grant,
and quota-usage operation authorizes the caller before it changes anything. An
unauthenticated caller, or one the gate denies, is refused with a
LatticeAuthorizationDeniedException(unified intoTenantNotFoundExceptionon the quota-usage read) and no change is made. The read-only self-service surface scopes its answers to the caller instead of refusing, and the tenant-scoped tree facade relies on the facades it wraps (seeILatticeTenantScopedTreeAdmin). The bindings additionally gate every lifecycle, region-residency, access, grant, and quota-usage call they serve behind an explicit opt-in (the gRPC binding's default-deny authorizer, the MCP binding's control-tool switch), so a cluster that does not enable it exposes none of them; the read-only self-service calls are deliberately left outside that opt-in. - Two-tier governance. Tenant lifecycle and allowed-region authorization are
platform-operator actions (cluster-wide
Adminon the reserved auth policy tree, which the gate's control-plane isolation grants only to a platform operator). Setting residency, reading status, tenant access administration, cross-tenant grant administration, and the quota-usage read are tenant-admin actions, authorized when the caller is that operator or a live admin subject on the tenant record (for a grant step, the record of the tenant whose side of the agreement the step belongs to). Both tiers are independent of the data-planeDefaultEffect, so an unmatched request always resolves to deny even underDefaultEffect = Allow. - Reserved default tenant. The well-known legacy-adoption
defaulttenant can never be suspended, deleted, or given quotas, have its admin-subject set changed, or be named on either side of a cross-tenant grant offer; each fails closed with aReservedTenantOperationException, because it names the cluster's own legacy state. Resuming it is allowed and is an active no-op, since it can never be suspended in the first place. - Idempotent lifecycle. Suspend/resume report whether they changed anything;
create is not an idempotent upsert (a duplicate id fails closed with
TenantAlreadyExistsException), so it can never reset or reuse another tenant's definition. - Create seeds admin subjects. Tenant visibility on the read-only
ILatticeTenantSelfServicesurface resolves from the tenant record's admin-subject set, so a tenant created with none is mutable but invisible - even to the operator who just created it.CreateTenantAsynctherefore takes an optionaladminSubjectsset and seeds it onto the new record. Omit it (or pass an empty set) and the calling subject is seeded, so the creator can always see what it created; supply a non-empty one and it is used verbatim (the caller is not added on top), which is how you hand a tenant to its delegated admins in a single call. Every entry must be a non-blank subject id, a blank ornullentry fails closed with anArgumentException, and duplicates collapse. A caller that cannot be resolved to a subject (an anonymous or system-origin create) seeds nothing rather than inventing an owner; grant access explicitly in that case. When a real identity directory provider is registered (anything but the defaultNullIdentityDirectory) andLatticeIdentityDirectoryOptions.ValidationRequiredis set, every id in an explicitly supplied set must resolve - checked after authorization and before the write, exactly asILatticeTenantAccessAdmin.AddAdminSubjectAsyncchecks an added subject - and an id the directory resolves to nothing fails the whole create with aLatticeDirectoryValidationException(anArgumentException), so create cannot be used to grant what add would refuse. The caller-seeded default is not checked, since it is the authenticated caller's own resolved subject. The seeded set is echoed back onTenantCreationResult.AdminSubjects. - Authorize, then validate, then write. Every
ILatticeTenantAdminlifecycle verb first parses the tenant id (a purely syntactic step over the caller's own argument, which on create also rejects an id shadowing thesys-or_lattice_reserved namespaces with anArgumentException), then authorizes through the fail-closed gate, and only then inspects its remaining arguments or touches the registry. The region, access, and grant verbs also check their other arguments' syntax (a region set, a subject id, a grant scope and operation set) before the gate; their gate reads the tenant record to evaluate admin-subject membership, but reports a missing tenant to a non-operator as a denial, and no reserved-tenant check runs until it admits the call. So a denied caller learns nothing from the admin-subject list it supplied, from whether the tenant already exists, or from whether it is the reserveddefaulttenant: every one of those checks sits behind the gate and cannot be used as an oracle. - Cascading delete. Deleting a tenant first suspends it, so no new
tenant-scoped admission can race the delete, then cascades the delete to every tree
the tenant owns (each
t/{tenantId}/*tree is soft-deleted) before the registry record is removed. An interrupted delete therefore leaves a suspended, retriable record rather than orphaned trees. - Quota authoring.
SetTenantQuotasAsyncreplaces a tenant's resource quotas and burst allowance in one platform-operator action. Each ceiling (MaxBytes,MaxKeys,MaxMemoryBytes,MaxTreeCount,MaxOpsPerSecond) isnullfor unbounded on that dimension; passingTenantQuotasDescriptor.Unboundedlifts every cap again. A bounded ceiling must be non-negative, and so mustBurstPercent, the transient headroom above the bounded ceilings: a negative value on any of them fails closed with anArgumentExceptionand writes nothing. The reserveddefaulttenant can never be given quotas. The quotas now in effect come back onTenantQuotasUpdateResult.Quotas, and stay readable onILatticeTenantSelfService.GetTenantAsync(TenantStatusReport.Quotas) for any caller that can see the tenant. - Write contention. Every mutating verb commits through the tenancy registry's
optimistic read-merge-write, so under sustained contention on one tenant's record
it can fail with the tenancy package's
TenantRegistryConcurrencyException(see Store write contention) instead of its domain outcome; the write that exhausted its retries is not applied, and the call can be retried.
Registration
Register the facade on the silo (it requires the Orleans.Lattice.Tenancy package):
AddLatticeTenantAdminApi(this ISiloBuilder builder, Action<LatticeApiTenantAdminOptions>? configure = null)- registersILatticeTenantAdmin,ILatticeTenantRegionAdmin,ILatticeTenantAccessAdmin,ILatticeTenantGrantAdmin, and the read-onlyILatticeTenantSelfServiceandILatticeTenantQuotaUsage, together with the fail-closed authorizers they consult, and a residency listener that completes the drain of the silo's own region: a region dropped bySetResidencyAsyncmovesDraining->Offline->Removedon its own. A region it adds stays atProvisioninguntil an operator advances it withTenantRecord.TryPromoteRegionStatus, because nothing backfills the tenant's existing data into it (see Lifecycle states).AddLatticeTenantScopedTreeAdminApi(this ISiloBuilder builder)- registersILatticeTenantScopedTreeAdmin.
Each call is order-guarded at registration time: a misordered call throws an
InvalidOperationException with an actionable message rather than failing
obscurely at silo start.
| Call | Must run after | Because |
|---|---|---|
AddLatticeTenantAdminApi |
AddLatticeTenancy() |
The facade operates on the tenancy engine's tenant registry, so it would otherwise have no lifecycle store to act on. |
AddLatticeTenantScopedTreeAdminApi |
AddLatticeTreeAdminApi() |
It delegates the whole-tree lifecycle verbs to that facade. |
AddLatticeTenantScopedTreeAdminApi |
AddLatticeSchemaEnforcement() |
It delegates the per-tree schema-policy verbs to the schema engine's in-process ILatticeSchemaAdmin, which that call registers. |
Each is idempotent: repeating the call layers any supplied configuration delegate but performs the structural wiring only once.
LatticeApiTenantAdminOptions currently exposes no settings - it is the reserved
per-facade options seam, mirroring the sibling control facades - so the configure
delegate can be omitted. The knobs that shape tenancy behaviour live on the
Orleans.Lattice.Tenancy
options instead.
Facade method signatures
ILatticeTenantAdmin
The tenant lifecycle surface (published in Orleans.Lattice.Api.Abstractions,
namespace Orleans.Lattice.Api.TenantAdmin). Each method corresponds to one gRPC RPC
in the binding.
| Method | Signature |
|---|---|
CreateTenantAsync |
Task<TenantCreationResult> CreateTenantAsync(string tenantId, IReadOnlyCollection<string>? adminSubjects = null, CancellationToken cancellationToken = default) |
SuspendTenantAsync |
Task<TenantStatusChangeResult> SuspendTenantAsync(string tenantId, CancellationToken cancellationToken = default) |
ResumeTenantAsync |
Task<TenantStatusChangeResult> ResumeTenantAsync(string tenantId, CancellationToken cancellationToken = default) |
DeleteTenantAsync |
Task<TenantDeletionResult> DeleteTenantAsync(string tenantId, CancellationToken cancellationToken = default) |
SetTenantQuotasAsync |
Task<TenantQuotasUpdateResult> SetTenantQuotasAsync(string tenantId, TenantQuotasDescriptor quotas, CancellationToken cancellationToken = default) |
ILatticeTenantRegionAdmin
The per-tenant region-residency surface. Of the region sets it authors the operator-owned allowed set and the tenant-owned resident set, leaving the physical topology to the deployment. Residency is always a subset of the allowed set; the last resident region can never be removed.
| Method | Signature |
|---|---|
AuthorizeAllowedRegionsAsync |
Task<TenantRegionAuthorizationResult> AuthorizeAllowedRegionsAsync(string tenantId, IReadOnlyCollection<string> allowedRegions, CancellationToken cancellationToken = default) |
SetResidencyAsync |
Task<TenantResidencyChangeResult> SetResidencyAsync(string tenantId, IReadOnlyCollection<string> residencyRegions, CancellationToken cancellationToken = default) |
GetTenantRegionStatusAsync |
Task<TenantRegionStatusReport> GetTenantRegionStatusAsync(string tenantId, CancellationToken cancellationToken = default) |
Each authored set is a replacement, not a delta: the supplied collection becomes the whole set, so a currently-allowed or currently-resident region absent from it is revoked or drained.
Concurrent residency changes
The tenant record is CRDT-merged, and its per-region status is a map keyed by region
id, so two removals of different regions do not conflict - the join keeps both. A
guard that only checked before writing would therefore let two concurrent callers each
drop a different region and leave the tenant resident nowhere. SetResidencyAsync
closes that by checking the invariant twice: once before the write, and again on
the record the registry commits. Because the pre-write check has already refused the
single-writer case, a merged record with no resident region can only mean a concurrent
removal, so the call repairs the regions it drained (restoring their prior status
at a strictly later stamp), leaves the other caller's removal standing, and refuses
with TenantLastRegionException. Only the caller whose commit observes the emptied
set is refused; the other caller's removal, committed while a region was still
resident, succeeds and stands. Either way the tenant keeps at least one resident
region, and retrying the refused call afterwards meets the ordinary pre-write guard.
For the same reason both write operations report the merged record rather than the caller's pre-write view, so a concurrent change from another writer is present in the returned region set instead of silently absent.
Authorization tiers
| Operation | Tier | Who may call it |
|---|---|---|
AuthorizeAllowedRegionsAsync |
Operator only | Cluster-wide Admin on the reserved auth policy tree. A tenant admin is denied - the allowed set is the operator's containment boundary and a tenant must not be able to widen it. |
SetResidencyAsync |
Operator or tenant admin | That operator, or a live admin subject on the tenant record. |
GetTenantRegionStatusAsync |
Operator or tenant admin | Same as above. Read-only. |
Both tiers are independent of the data-plane DefaultEffect, so an unmatched request
resolves to deny even under DefaultEffect = Allow. Every transport binding inherits
this gate rather than re-implementing it, so neither tier can be widened by reaching
the facade over the wire.
Domain exceptions
Each failure mode is a distinct exception type so a transport binding can map it to a specific status rather than an opaque fault:
| Exception | Raised when |
|---|---|
TenantNotFoundException |
The tenant is not registered - reported to a platform operator. A non-operator caller naming an unknown tenant on a tenant-admin-tier verb gets LatticeAuthorizationDeniedException instead, so it cannot probe for a tenant's existence. |
TenantRegionNotAllowedException |
Residency was set to a region outside the allowed set, or an allowed region a tenant is still resident in was revoked. |
TenantLastRegionException |
The change would remove the tenant's last resident region - either as submitted, or once merged with a concurrent removal (see Concurrent residency changes). |
LatticeAuthorizationDeniedException |
The caller does not hold the required tier. |
ILatticeTenantSelfService
The read-only tenant self-awareness surface (published in
Orleans.Lattice.Api.Abstractions, namespace Orleans.Lattice.Api.TenantAdmin). It
never creates, suspends, resumes, or deletes a tenant.
| Method | Signature |
|---|---|
GetCurrentTenantAsync |
Task<TenantDescriptor> GetCurrentTenantAsync(CancellationToken cancellationToken = default) |
ListAccessibleTenantsAsync |
Task<IReadOnlyList<TenantDescriptor>> ListAccessibleTenantsAsync(CancellationToken cancellationToken = default) |
GetTenantAsync |
Task<TenantStatusReport> GetTenantAsync(string tenantId, CancellationToken cancellationToken = default) |
GetCurrentTenantAsync needs no administrative tier because it reports only the
caller's own context, and a caller with no tenant in context resolves to the reserved
default tenant. That is not the same as being ungated: GetCurrentTenantAsync and
ListAccessibleTenantsAsync each re-run the fail-closed tenant resolution first, so a
caller whose asserted active tenant was refused gets a LatticeTenantAccessDeniedException
instead of a report for a tenant it does not hold.
ListAccessibleTenantsAsync returns, in ascending ordinal tenant-id order, the
tenants the caller is a registered administrator of plus its own current tenant when
that is non-default, so an anonymous or non-privileged caller under the default tenant
gets an empty list. GetTenantAsync deliberately unifies "no such tenant" and "you may
not see this tenant" into a single TenantNotFoundException, so no caller can probe
for the existence of a tenant outside its authority.
ILatticeTenantScopedTreeAdmin
Tree administration executed inside one tenant's namespace (namespace
Orleans.Lattice.Api.TenantAdmin). Names are the tenant's unqualified tree names; the
facade injects the tenant segment.
| Method | Signature |
|---|---|
CreateTreeAsync |
Task<TreeCreationResult> CreateTreeAsync(string name, int? shardCount = null, int? maxLeafKeys = null, int? maxInternalChildren = null, CancellationToken cancellationToken = default) |
CheckTreeExistsAsync |
Task<TreeExistenceResult> CheckTreeExistsAsync(string name, CancellationToken cancellationToken = default) |
DeleteTreeAsync |
Task<TreeDeletionStatus> DeleteTreeAsync(string name, CancellationToken cancellationToken = default) |
RecoverTreeAsync |
Task<TreeDeletionStatus> RecoverTreeAsync(string name, CancellationToken cancellationToken = default) |
PurgeTreeAsync |
Task<TreeDeletionStatus> PurgeTreeAsync(string name, bool confirm, CancellationToken cancellationToken = default) |
GetTreeDeletionStatusAsync |
Task<TreeDeletionStatus> GetTreeDeletionStatusAsync(string name, CancellationToken cancellationToken = default) |
SetSchemaPolicyAsync |
Task SetSchemaPolicyAsync(string name, LatticeSchemaPolicy policy, CancellationToken cancellationToken = default) |
ClearSchemaPolicyAsync |
Task<bool> ClearSchemaPolicyAsync(string name, CancellationToken cancellationToken = default) |
GetSchemaPolicyAsync |
Task<LatticeSchemaPolicy?> GetSchemaPolicyAsync(string name, CancellationToken cancellationToken = default) |
Every method on this facade requires an active tenant. With none in scope the
call fails closed with a TenantScopeRequiredException (declared in this package,
namespace Orleans.Lattice.Api.TenantAdmin) rather than silently operating on the
cluster-global namespace.
The facade composes the target id from the ambient active-tenant assertion as
supplied and does not use the two-tier gate above. Its tree verbs delegate to
ILatticeTreeAdmin, which resolves and authorizes the composed id through the
access gate as it would for any caller - whole-tree Admin to create (also checked
here before any quota accounting), TreeLifecycle to delete, recover, or purge, and
Read to check existence or deletion status - so the caller's membership
validation and tenancy's isolation apply there. Its schema-policy verbs delegate to
the in-process ILatticeSchemaAdmin, which performs no authorization of its own (see
Capability gate), and the facade adds
no check for them.
ILatticeTenantAccessAdmin
The tenant access-administration surface (published in
Orleans.Lattice.Api.Abstractions, namespace Orleans.Lattice.Api.TenantAdmin).
Every operation is a tenant-admin action - authorized for the platform operator
or a live admin subject of the target tenant - and a caller holding neither is told
denied, never not found, so the surface cannot be used to enumerate tenants.
| Method | Signature |
|---|---|
ListAdminSubjectsAsync |
Task<TenantAdminSubjectReport> ListAdminSubjectsAsync(string tenantId, CancellationToken cancellationToken = default) |
AddAdminSubjectAsync |
Task<TenantAdminSubjectChangeResult> AddAdminSubjectAsync(string tenantId, string subjectId, CancellationToken cancellationToken = default) |
RemoveAdminSubjectAsync |
Task<TenantAdminSubjectChangeResult> RemoveAdminSubjectAsync(string tenantId, string subjectId, CancellationToken cancellationToken = default) |
Add and remove are idempotent (Changed reports whether the set moved). When a real
identity directory provider is registered (anything but the default
NullIdentityDirectory) and LatticeIdentityDirectoryOptions.ValidationRequired is
set, an add of a subject that is not already a member - checked after authorization
and the reserved-tenant check - requires the id to resolve: an id the directory
resolves to nothing is refused with a LatticeDirectoryValidationException (an
ArgumentException) before the write. Resolution is the only directory check. Unlike
the UpsertGroupAsync and AddMemberAsync paths of the
authorization-admin facade, the principal's kind is
not checked, so an id that resolves to a group is accepted. Admin-subject membership
is matched against the caller's own subject id with no group expansion, so such an
entry never authorizes the group's members. Removing a tenant's last admin subject is refused with
TenantLastAdminSubjectException - including when two concurrent removals of
different subjects would together empty the set, which is detected on the merged
record and repaired before the refusal - and the reserved default tenant's
membership can never be changed.
ILatticeTenantGrantAdmin
The cross-tenant grant surface. A grant is a two-step agreement: an offer creates it
Pending and authorizes nothing; only the grantee's approval makes it Active.
| Method | Signature |
|---|---|
ListGrantsAsync |
Task<TenantGrantReport> ListGrantsAsync(string tenantId, CancellationToken cancellationToken = default) |
OfferGrantAsync |
Task<TenantGrantChangeResult> OfferGrantAsync(string granterTenantId, string granteeTenantId, string scope, TenantGrantAccess operations, CancellationToken cancellationToken = default) |
ApproveGrantAsync |
Task<TenantGrantChangeResult> ApproveGrantAsync(string granterTenantId, string granteeTenantId, string scope, CancellationToken cancellationToken = default) |
RejectGrantAsync |
Task<TenantGrantChangeResult> RejectGrantAsync(string granterTenantId, string granteeTenantId, string scope, CancellationToken cancellationToken = default) |
RevokeGrantAsync |
Task<TenantGrantChangeResult> RevokeGrantAsync(string granterTenantId, string granteeTenantId, string scope, CancellationToken cancellationToken = default) |
Each step is authorized for the platform operator or a live admin subject of one
specific tenant: the granting tenant offers, the grantee tenant approves or
rejects, either party may revoke, and a listing is the listed tenant's own.
scope names the granting tenant's data the grant covers. It is stored as supplied
and matched against the full id of the tree a request names, so write it in the
granting tenant's composed form - a tree id such as t/acme/orders, or a prefix of
one that ends on a / segment boundary, such as t/acme/ - because an unqualified
name such as orders covers no tree. operations must not be
TenantGrantAccess.None, and the two tenants must differ: a blank scope, an empty
operation set, or the same tenant on both sides fails with an ArgumentException.
An offer never
requires the grantee to exist and may not name the reserved default tenant on
either side. A grant that was never offered - or whose granting tenant is not
registered - is reported identically as TenantGrantNotFoundException; asking for
the state a grant is already in is an idempotent no-op, and a transition the
lifecycle forbids (for example approving a rejected or revoked grant) raises
TenantGrantTransitionException before any write. A step that races the other
party's concurrent transition and loses the merge is refused with the same exception,
carrying the state that won.
The access gate honours an active grant's Write operation, but the data plane
refuses a user-origin write that names another tenant's t/ tree before the gate is
consulted, so through the data surface only a grant's read operations take effect.
ILatticeTenantQuotaUsage
The read-only usage-against-quota surface.
| Method | Signature |
|---|---|
GetQuotaUsageAsync |
Task<TenantQuotaUsageReport> GetQuotaUsageAsync(string tenantId, CancellationToken cancellationToken = default) |
The report carries, per dimension (Bytes, Keys, MemoryBytes, TreeCount,
OpsPerSecond), the consumption, the steady-state ceiling, the burst-adjusted
ceiling, the live overage, and the accrued metered overage, together with the
EnforcementScope the figures were read under. OpsPerSecond carries only its two
ceilings: the engine never samples a sustained operation rate, so its usage is always
null (not measured). A platform operator may read any
tenant and a live tenant admin only its own; an unauthorized tenant and an absent one
are unified into a single TenantNotFoundException, so the call cannot probe for
tenant existence. Authoring quotas remains the operator-only SetTenantQuotasAsync.
Authorization seams
The two fail-closed authorizers the facades consult are public types of this package
(namespace Orleans.Lattice.Api.TenantAdmin). Both honour a system-origin bypass for
trusted co-hosted infrastructure and are independent of the data-plane
DefaultEffect:
TenantAdminAccessAuthorizer- the platform-operator gate for the lifecycle verbs.AuthorizeTenantAdminAsyncthrowsLatticeAuthorizationDeniedExceptionunless the caller holds whole-scopeAdminonPlatformOperatorScope(the reserved authorization policy tree), refusing a key-filtered allow;IsTenantAdminAuthorizedAsyncis its non-throwing probe.TenantRegionResidencyAuthorizer- the two-tier gate for the tenant-tier verbs.AuthorizeOperatorAsyncis the operator tier.AuthorizeTenantAdminAsyncadmits the platform operator or a live admin subject on the tenant record and returns that record, reporting an unknown tenant asTenantNotFoundExceptionto the operator and as a denial to anyone else.TryAuthorizeTenantAdminAsyncreturnsnullinstead of throwing - for a missing tenant as well as a denial - so a verb either of two tenants may perform can consider both sides.
Public model types
Results and exceptions live in Orleans.Lattice.Api.Abstractions under
TenantAdmin/Model/.
| Type | Kind | Purpose |
|---|---|---|
TenantCreationResult |
result | The newly created tenant, with the admin subjects seeded onto it. |
TenantDescriptor |
model | One tenant's identity and lifecycle status, as reported by the read-only self-service surface. |
TenantStatusReport |
result | One tenant's read-only lifecycle status, authored Quotas, and per-region residency rows. |
TenantStatusChangeResult |
result | Suspend/resume outcome; Changed reports whether state moved. |
TenantDeletionResult |
result | Deletion outcome, including the count of trees cascaded. |
TenantQuotasDescriptor |
model | A tenant's per-dimension resource ceilings (null = unbounded) and BurstPercent; Unbounded sentinel and IsUnbounded predicate. |
TenantQuotasUpdateResult |
result | The tenant id and the quotas now in effect after authoring. |
TenantLifecycleStatus |
enum | Active / Suspended. |
TenantRegionAuthorizationResult |
result | The resulting allowed region set. |
TenantResidencyChangeResult |
result | The regions this call began adding (now Provisioning) and removing (now Draining), and the resulting per-region status rows. |
TenantRegionStatusReport |
result | Per-region rows (TenantRegionStatusDescriptor), ordered by region id. |
TenantRegionStatusDescriptor |
model | One region's allowed flag and lifecycle status. |
TenantRegionLifecycleStatus |
enum | None / Provisioning / Backfilling / Online / Draining / Offline / Removed. |
TenantNotFoundException |
exception | No tenant with that id is registered. |
TenantAlreadyExistsException |
exception | A tenant with the same id is already registered. |
ReservedTenantOperationException |
exception | Attempted suspend, delete, set-quotas, an admin-subject add / remove, or a cross-tenant grant offer on the reserved default tenant. |
TenantRegionNotAllowedException |
exception | A residency region is not in the allowed set (or a revoked region is still resident). |
TenantLastRegionException |
exception | The change would remove the last resident region, as submitted or once merged with a concurrent removal. |
TenantAdminSubjectReport |
result | A tenant's live admin-subject set, in ordinal order. |
TenantAdminSubjectChangeResult |
result | An add / remove outcome: the subject, Changed, and the resulting admin-subject set. |
TenantLastAdminSubjectException |
exception | The removal would leave the tenant with no admin subjects. |
TenantGrantDescriptor |
model | One cross-tenant grant: granting and grantee tenant, Scope, Operations, lifecycle State, and GrantId. |
TenantGrantReport |
result | A tenant's Issued and Received grants, in every lifecycle state. |
TenantGrantChangeResult |
result | A grant step's outcome: the Grant as committed and Changed. |
TenantGrantAccess |
enum | None / Read / Write / ReadWrite - what a grant authorizes once active. |
TenantGrantLifecycleState |
enum | Active / Pending / Rejected / Revoked. |
TenantGrantNotFoundException |
exception | No such grant has been offered (reported identically when the granting tenant is not registered). |
TenantGrantTransitionException |
exception | The grant's lifecycle forbids the requested transition; carries the current and requested states. |
TenantQuotaUsageReport |
result | A tenant's usage against its quotas: one TenantQuotaDimensionUsage per dimension, BurstPercent, the authored Quotas, HasUsage, and the EnforcementScope. |
TenantQuotaDimensionUsage |
model | One dimension's Usage (null = not measured), Limit (null = unbounded), BurstLimit, live Overage, and accrued MeteredOverage. |
TenantQuotaEnforcementScope |
enum | GlobalConverged (the converged cross-cluster total) / PerCluster (this cluster's local share only). |
ApiTenantAdminTypeAliases |
static class | The stable oitn.-prefixed Orleans serialization aliases of the tenant-admin contract types. |
See also
Orleans.Lattice.Tenancy- the core multi-tenancy companion (isolation, quotas, metering, residency enforcement).Orleans.Lattice.Api.TenantAdmin.Grpc- the code-first gRPC binding and remote client for these facades.Orleans.Lattice.Api.TreeAdmin- the sibling tree-administration facade this one composes and mirrors.- MultiTenancy sample.