# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-re-attest-its-agent-identity-after-a-hardware-key-rotation-versus-treat-the-rotation-as-a-transparent-continuit.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-re-attest-its-agent-identity-after-a-hardware-key-rotation-versus-treat-the-rotation-as-a-transparent-continuit.warrant.json
# Markdown SHA-256:   8e8dfbeea063409a086825636d19cf6ef9ef735bb6df2f6b0dfeaa58be5df7db
# Code AST SHA-256:   5a939e1d47dc6a7d222067183e6994835fb7712314e3868ec79d28352fa6f2e6
# Verified Classes:   AttestationRotationError, AttestationReport, ActiveToken
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Optional


class AttestationRotationError(RuntimeError):
    """Raised when a key rotation cannot be classified or safely applied."""


@dataclass(frozen=True)
class AttestationReport:
    principal_id: str
    key_id: str
    measurement: bytes
    root_of_trust: str


@dataclass(frozen=True)
class ActiveToken:
    principal_id: str
    key_id: str
    measurement: bytes
    root_of_trust: str


def classify_rotation(
    old: ActiveToken,
    new_report: AttestationReport,
) -> str:
    """
    Return 'continuity' if the rotation is transparent, or 'reattest'
    if the agent must re-run the full attestation handshake.
    """
    if new_report.principal_id != old.principal_id:
        raise AttestationRotationError(
            f"principal changed across rotation: {old.principal_id} -> "
            f"{new_report.principal_id}; this is not a rotation"
        )

    if new_report.root_of_trust != old.root_of_trust:
        # Different endorsement chain: cannot inherit prior evidence.
        return "reattest"

    if new_report.measurement != old.measurement:
        # The key changed because the measured state changed.
        return "reattest"

    return "continuity"


def apply_rotation(
    old: ActiveToken,
    new_report: AttestationReport,
    posture_assertions: list,
) -> ActiveToken:
    verdict = classify_rotation(old, new_report)

    if verdict == "reattest":
        # Invalidate every assertion bound to the old measurement.
        stale = [a for a in posture_assertions if a.measurement == old.measurement]
        if stale:
            raise AttestationRotationError(
                f"{len(stale)} posture assertions reference stale measurement "
                f"{old.measurement.hex()[:16]}; refusing to extend token"
            )
        raise AttestationRotationError(
            "rotation requires full re-attestation: measurement or root of "
            "trust changed; old token must not be extended"
        )

    # Continuity: same measurement, same root, same principal.
    return ActiveToken(
        principal_id=new_report.principal_id,
        key_id=new_report.key_id,
        measurement=new_report.measurement,
        root_of_trust=new_report.root_of_trust,
    )
