tenant-engine/src/tenant_engine/guardrail/resolution.py
tegwick 33ceb882ee Finish TEN-WP-0006-T02: implement guardrail domain model
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 02:10:38 +02:00

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)