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

@ -4,6 +4,8 @@ from dataclasses import dataclass, field
from decimal import Decimal
from typing import Any, Literal
from .pricing_models import PricingModel
GovernanceDecision = Literal["proceed", "approval_required", "blocked"]
RecommendationType = Literal["research", "simulation", "model_change", "execution"]
RecommendationPriority = Literal["high", "medium", "low"]
@ -46,6 +48,7 @@ class ApprovalRequirement:
approver_role: str
reason: str
blocking: bool = True
evidence_refs: tuple[str, ...] = ()
@dataclass(frozen=True)
@ -138,6 +141,95 @@ class SafeTuningContract:
notes: tuple[str, ...] = ()
def assurance_claim_approval_requirements(
model: PricingModel,
previous_model: PricingModel | None,
*,
approver_role: str = "operator",
) -> tuple[ApprovalRequirement, ...]:
"""Return one-time approval gates for assurance changes at tier definition.
Callers must pass the previously approved definition. Publication and
campaign workflows must not call this for an unchanged model: Tenancy
Posture section 11.3 records the approval once when a tier is defined.
"""
current = {claim.id: claim for claim in model.assurance_claims}
previous = {
claim.id: claim
for claim in (previous_model.assurance_claims if previous_model is not None else ())
}
changed_ids = sorted(
claim_id
for claim_id in current.keys() | previous.keys()
if current.get(claim_id) != previous.get(claim_id)
)
requirements: list[ApprovalRequirement] = []
for claim_id in changed_ids:
claim = current.get(claim_id)
prior_claim = previous.get(claim_id)
evidence_ref = (claim or prior_claim).evidence_ref
change = "removed" if claim is None else "added" if prior_claim is None else "changed"
requirements.append(
ApprovalRequirement(
id=f"assurance-claim-{claim_id}-approval",
title=f"Assurance claim '{claim_id}' approval",
approver_role=approver_role,
reason=(
f"Tier '{model.id}' {change} customer assurance claim '{claim_id}'. "
f"Review its typed minimums and evidence before the tier definition is accepted."
),
evidence_refs=(evidence_ref,),
)
)
return tuple(requirements)
def assess_tier_definition_assurance(
model: PricingModel,
previous_model: PricingModel | None,
*,
approver_role: str = "operator",
) -> GovernanceAssessment:
approvals = assurance_claim_approval_requirements(
model,
previous_model,
approver_role=approver_role,
)
if approvals:
return GovernanceAssessment(
decision="approval_required",
summary="Approval required for a changed customer assurance claim.",
approvals=approvals,
risks=(
GovernanceRisk(
id="unapproved-assurance-change",
severity="high",
summary="The tier definition changes a customer-visible assurance obligation.",
mitigation="Validate the typed minima, inspect the attached evidence, and record human approval once for this tier revision.",
),
),
supporting_observations=tuple(
SupportingObservation(
id=f"{approval.id}-evidence",
title="Assurance evidence",
summary="Evidence attached to the tier-definition approval gate.",
source_ref=approval.evidence_refs[0],
)
for approval in approvals
),
notes=("Do not reuse this definition-time gate as a per-campaign approval.",),
)
return GovernanceAssessment(
decision="proceed",
summary="No assurance claim changed since the approved tier definition.",
approvals=(),
risks=(),
supporting_observations=(),
)
def governance_policy_from_dict(raw: dict[str, Any]) -> GovernancePolicy:
return GovernancePolicy(
policy_id=raw.get("policy_id", "default-governance-policy"),