Multi-tenancy: the inbuxa-features crate, the permission ceiling and tenant disk quota (MT-12, MT-13, MT-14, MT-15, MT-16, MT-19, MT-20)

New crate crates/features (inbuxa-features), AGPL-3.0-only, holding the
tenancy rules. Hooks in common: a tenant's roles and permission lists cap
its people's permissions; a change to a tenant, or to a role a tenant holds,
drops its members' cached permissions; delivery and every other write check
the tenant's maxDiskQuota; usedDiskQuota reads the tenant usage counter.
The default Tenant Administrator role gains sysTenantGet and sysTenantQuery.
This commit is contained in:
2026-09-18 15:19:58 -07:00
parent b6b345a129
commit ad0db8b2b0
18 changed files with 1398 additions and 5 deletions
+25
View File
@@ -280,6 +280,15 @@ impl Server {
.registry()
.linked_objects(ObjectId::new(ObjectType::Role, role_id.into()))
.await?;
// inbuxa: MT-16: a role a tenant holds sets its ceiling
for tenant_id in inbuxa_features::tenancy::members::tenants_using_role(
self.registry(),
&linked_objects,
)
.await?
{
changes.insert(CacheInvalidation::Tenant(tenant_id));
}
for linked_object in linked_objects {
match linked_object.object() {
ObjectType::Account => {
@@ -297,6 +306,22 @@ impl Server {
}
}
// inbuxa: MT-16: a tenant's change reaches its people on their next request
let tenant_ids = changes
.iter()
.filter_map(|change| match change {
CacheInvalidation::Tenant(tenant_id) => Some(*tenant_id),
_ => None,
})
.collect::<Vec<_>>();
for tenant_id in tenant_ids {
for account_id in
inbuxa_features::tenancy::members::accounts(self.registry(), tenant_id).await?
{
changes.insert(CacheInvalidation::AccessToken(account_id));
}
}
let changes = changes.into_iter().collect::<Vec<_>>();
self.invalidate_local_caches(&changes).await;
self.cluster_broadcast(BroadcastEvent::CacheInvalidate(changes))