99 lines
3.7 KiB
Python
99 lines
3.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Mapping
|
|
|
|
from tenant_engine.domain import Tenant, TenantLifecycle
|
|
from tenant_engine.guardrail.model import EffectiveLimit, LimitValue, Provenance
|
|
from tenant_engine.guardrail.registry import DEFAULT_REGISTRY, LimitRegistry
|
|
|
|
# Per-key precedence, highest first: override -> plan -> grouping (or the
|
|
# reserved profile) -> fail-closed floor. Evaluated *per key*, never per set,
|
|
# so a plan supplying `spend.monthly` does not wipe out a grouping-derived
|
|
# `entity.*`.
|
|
|
|
|
|
def resolve_limit(
|
|
key: str,
|
|
*,
|
|
tenant: Tenant,
|
|
plan_limits: Mapping[str, LimitValue] | None = None,
|
|
overrides: Mapping[str, LimitValue] | None = None,
|
|
registry: LimitRegistry = DEFAULT_REGISTRY,
|
|
) -> EffectiveLimit:
|
|
"""Resolve one limit key for one tenant. Total and side-effect-free.
|
|
|
|
Raises `UnknownLimitKeyError` for an unregistered key -- neither zero nor
|
|
unlimited, both of which would be lies.
|
|
"""
|
|
definition = registry.get(key)
|
|
|
|
override = (overrides or {}).get(key)
|
|
if override is not None:
|
|
return _clamp(EffectiveLimit(key, override, Provenance.OVERRIDE), tenant, definition.floor)
|
|
|
|
plan_limit = (plan_limits or {}).get(key)
|
|
if plan_limit is not None:
|
|
return _clamp(EffectiveLimit(key, plan_limit, Provenance.PLAN), tenant, definition.floor)
|
|
|
|
if tenant.is_reserved:
|
|
# Reserved identifiers are ungrouped, so layer 3 cannot apply. They do
|
|
# not fall through to the floor either: that would clamp the platform's
|
|
# own identity to zero and take the platform down with it.
|
|
return _clamp(
|
|
EffectiveLimit(key, definition.reserved_default, Provenance.RESERVED),
|
|
tenant,
|
|
definition.floor,
|
|
)
|
|
|
|
grouping_default = definition.grouping_defaults.get(tenant.grouping or "")
|
|
if grouping_default is not None:
|
|
return _clamp(
|
|
EffectiveLimit(key, grouping_default, Provenance.GROUPING), tenant, definition.floor
|
|
)
|
|
|
|
# Only reachable if the registry is internally inconsistent -- a grouping
|
|
# added to GROUPINGS with no default declared. The registry validates this
|
|
# at construction, so this is the second line of defence, not the first.
|
|
# Provenance stays `fail_closed` so the condition is visible in a read
|
|
# rather than mistaken for deliberate policy.
|
|
return _clamp(
|
|
EffectiveLimit(key, definition.floor, Provenance.FAIL_CLOSED), tenant, definition.floor
|
|
)
|
|
|
|
|
|
def resolve_limits(
|
|
*,
|
|
tenant: Tenant,
|
|
plan_limits: Mapping[str, LimitValue] | None = None,
|
|
overrides: Mapping[str, LimitValue] | None = None,
|
|
registry: LimitRegistry = DEFAULT_REGISTRY,
|
|
) -> dict[str, EffectiveLimit]:
|
|
"""Resolve every registered key. Every tenant resolves to exactly one
|
|
effective value per key -- there is no "unset means unlimited".
|
|
"""
|
|
return {
|
|
key: resolve_limit(
|
|
key,
|
|
tenant=tenant,
|
|
plan_limits=plan_limits,
|
|
overrides=overrides,
|
|
registry=registry,
|
|
)
|
|
for key in registry.keys
|
|
}
|
|
|
|
|
|
def _clamp(effective: EffectiveLimit, tenant: Tenant, floor: LimitValue) -> EffectiveLimit:
|
|
"""Apply the lifecycle clamp. Applied after precedence, and may only reduce.
|
|
|
|
Follows the TEN-WP-0005 precedent: operations that only reduce privilege
|
|
stay available while retired, loosening ones do not. A retired tenant's
|
|
guardrails stay readable -- it is the values that clamp, not the endpoint.
|
|
"""
|
|
if tenant.lifecycle is TenantLifecycle.ACTIVE:
|
|
return effective
|
|
|
|
clamped = effective.value.reduced_to_floor(floor)
|
|
if clamped == effective.value:
|
|
return effective
|
|
return EffectiveLimit(effective.key, clamped, Provenance.LIFECYCLE)
|