feat: add typed tier assurance guardrails
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

This commit is contained in:
codex 2026-08-19 21:32:07 +02:00
parent 3d0f614f49
commit c65a2f1ff9
13 changed files with 727 additions and 7 deletions

View file

@ -7,6 +7,7 @@ from pathlib import Path
from typing import Any, Literal
PricingModelStatus = Literal["active", "candidate", "retired"]
AssuranceClaimKind = Literal["isolation", "availability", "retention", "performance"]
ChargeComponentKind = Literal[
"access",
"setup",
@ -40,6 +41,9 @@ _ALLOWED_PARAMETER_CLASSES = {
"constrained",
"provider",
}
_ALLOWED_ASSURANCE_KINDS = {"isolation", "availability", "retention", "performance"}
_AXIS_MAXIMUMS = {"I": 3, "A": 4, "E": 4, "P": 4, "R": 4, "V": 4}
_ALLOWED_ERASURE_MECHANISMS = {"row-deletion", "key-destruction"}
def _money(value: str | int | float | Decimal | None) -> Decimal | None:
@ -88,6 +92,20 @@ class TunableParameter:
options: tuple[str, ...] = ()
@dataclass(frozen=True)
class AssuranceClaim:
id: str
kind: AssuranceClaimKind | str
customer_wording: str
minimum_levels: dict[str, int]
delivering_service: str
evidence_ref: str
maximum_erasure_horizon_days: int | None = None
provider_contract_ref: str | None = None
resource_governor_ref: str | None = None
erasure_mechanism: str | None = None
@dataclass(frozen=True)
class PricingModel:
id: str
@ -104,6 +122,7 @@ class PricingModel:
charge_components: tuple[ChargeComponent, ...] = ()
commitments: tuple[Commitment, ...] = ()
tunable_parameters: tuple[TunableParameter, ...] = ()
assurance_claims: tuple[AssuranceClaim, ...] = ()
eligibility: tuple[str, ...] = ()
provider_hints: dict[str, Any] = field(default_factory=dict)
metadata: dict[str, Any] = field(default_factory=dict)
@ -152,6 +171,36 @@ def _parse_tunable_parameter(raw: dict[str, Any]) -> TunableParameter:
)
def _parse_assurance_claim(raw: dict[str, Any]) -> AssuranceClaim:
def integer_or_raw(value: Any) -> Any:
if isinstance(value, str):
try:
return int(value)
except ValueError:
return value
return value
return AssuranceClaim(
id=str(raw.get("id", "")),
kind=str(raw.get("kind", "")),
customer_wording=str(raw.get("customer_wording", "")),
minimum_levels={
str(axis): integer_or_raw(level)
for axis, level in raw.get("minimum_levels", {}).items()
},
delivering_service=str(raw.get("delivering_service", "")),
evidence_ref=str(raw.get("evidence_ref", "")),
maximum_erasure_horizon_days=(
integer_or_raw(raw["maximum_erasure_horizon_days"])
if raw.get("maximum_erasure_horizon_days") is not None
else None
),
provider_contract_ref=raw.get("provider_contract_ref"),
resource_governor_ref=raw.get("resource_governor_ref"),
erasure_mechanism=raw.get("erasure_mechanism"),
)
def _legacy_charge_components(raw: dict[str, Any]) -> list[dict[str, Any]]:
components: list[dict[str, Any]] = [
{
@ -233,6 +282,9 @@ def _parse_pricing_model(raw: dict[str, Any]) -> PricingModel:
tunable_parameters=tuple(
_parse_tunable_parameter(item) for item in raw.get("tunable_parameters", [])
),
assurance_claims=tuple(
_parse_assurance_claim(item) for item in raw.get("assurance_claims", [])
),
eligibility=tuple(str(item) for item in raw.get("eligibility", [])),
provider_hints=_tuple_dict(raw.get("provider_hints")),
metadata=metadata,
@ -320,4 +372,46 @@ def validate_pricing_model(model: PricingModel) -> list[str]:
if len(commitment_ids) != len(set(commitment_ids)):
issues.append("commitment ids must be unique")
claim_ids = [claim.id for claim in model.assurance_claims]
if len(claim_ids) != len(set(claim_ids)):
issues.append("assurance claim ids must be unique")
for claim in model.assurance_claims:
prefix = f"assurance claim '{claim.id or '<missing>'}'"
if not claim.id.strip():
issues.append("assurance claim id is required")
if claim.kind not in _ALLOWED_ASSURANCE_KINDS:
issues.append(f"{prefix} has unsupported kind '{claim.kind}'")
for field_name, value in (
("customer_wording", claim.customer_wording),
("delivering_service", claim.delivering_service),
("evidence_ref", claim.evidence_ref),
):
if not value.strip():
issues.append(f"{prefix} requires {field_name}")
if not claim.minimum_levels:
issues.append(f"{prefix} requires minimum_levels")
for axis, level in claim.minimum_levels.items():
if axis not in _AXIS_MAXIMUMS:
issues.append(f"{prefix} has unknown axis '{axis}'")
elif (
isinstance(level, bool)
or not isinstance(level, int)
or not 0 <= level <= _AXIS_MAXIMUMS[axis]
):
issues.append(f"{prefix} has invalid {axis} level {level!r}")
if (
claim.maximum_erasure_horizon_days is not None
and (
isinstance(claim.maximum_erasure_horizon_days, bool)
or not isinstance(claim.maximum_erasure_horizon_days, int)
or claim.maximum_erasure_horizon_days < 1
)
):
issues.append(f"{prefix} has an invalid maximum erasure horizon")
if (
claim.erasure_mechanism is not None
and claim.erasure_mechanism not in _ALLOWED_ERASURE_MECHANISMS
):
issues.append(f"{prefix} has unsupported erasure_mechanism")
return issues