"""Claim provenance — A-16 applied to claims this surface consumes. `key-cape` emits `tenant` and `principal_type` as bare strings. A consumer cannot tell a value the *directory asserted about the person* from one a *registration supplied about the client they came through*. `GH-DEC-2026-013` §5 requires the claim to carry its provenance. Until it does, this surface records which route the value arrived by rather than storing an undifferentiated string (PR-09), and never discharges a human-in-the-loop control on a registration-supplied assertion of humanity (PR-11, `GH-DEC-2026-016` §5). A-16's rider applies to this module: where the route marker is written by the party whose conduct the route describes, it constrains a defect but not an adversary. These markers are written by us about claims we received, which is the semi-independent case — we gain nothing by mislabelling them. """ from __future__ import annotations from dataclasses import dataclass from enum import Enum class Route(str, Enum): """How a claim value reached us. Never collapsed, never defaulted.""" #: Asserted by the directory about the person. Strong. DIRECTORY = "directory-asserted" #: Supplied by the client registration. The GH-DEC-2026-013 bounded gap. REGISTRATION = "registration-supplied" #: Derived from the authentication event itself. AUTHENTICATION = "authentication-derived" #: Present, but its route is not determinable. Never treated as any of the above. INDETERMINATE = "indeterminate" @dataclass(frozen=True) class Claim: """A claim value with the route it arrived by. There is no constructor that takes a value without a route. A claim whose provenance is unknown is ``INDETERMINATE``, explicitly — not defaulted to the strongest reading. """ value: str route: Route def __post_init__(self) -> None: if not self.value: raise ValueError("claim value must be non-empty") @property def is_verified_about_the_person(self) -> bool: """True only where the claim was asserted about the *person*. A registration-supplied claim says something about the client the person came through, not about the person. `GH-DEC-2026-016` §5: refusing a service principal while accepting an unverified assertion of humanity moves the defect rather than closing it. """ return self.route in (Route.DIRECTORY, Route.AUTHENTICATION) class HumanControlNotDischargeable(Exception): """Raised when a human-in-the-loop control cannot be discharged. Not an authorization decision. This surface is not saying the actor may not act; it is saying *this claim cannot carry that weight*. """ def assert_human_control_dischargeable(principal_type: Claim) -> None: """Guard for `GH-DEC-2026-016` §5 / PR-11. Today `principal_type: human` is a property of the client registration, so this raises. That is correct and deliberate: the guard exists so the limitation is visible at the point of use rather than buried in a document. """ if principal_type.value != "human": raise HumanControlNotDischargeable( f"principal_type is {principal_type.value!r}, not 'human'" ) if not principal_type.is_verified_about_the_person: raise HumanControlNotDischargeable( "principal_type 'human' arrived by " f"{principal_type.route.value}; a human-in-the-loop control must not " "be discharged on a claim that describes the client rather than the " "person (GH-DEC-2026-016 §5, A-16)" )