# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-present-an-attestation-chain-it-cannot-fully-re-verify.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-present-an-attestation-chain-it-cannot-fully-re-verify.warrant.json
# Markdown SHA-256:   5266aae44e6cb9fc2e92f4ba8f7c79147d6700e4e5433f0d87a2a51fe73102d5
# Code AST SHA-256:   c750197f8fc135fafd2e3733e0888e1c747ad2b93ec68478ba22b8227d6f887c
# Verified Classes:   AttestationRefusal, RelyingPartyPolicy, ChainLink
# Defensive Guards:   7 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass
from typing import FrozenSet, Optional


class AttestationRefusal(RuntimeError):
    """Raised when a chain cannot be re-verified under the relying party's policy.

    Distinct from ChainInvalidError: the chain may be cryptographically sound,
    but the presenter cannot establish the preconditions for asserting it.
    """


@dataclass(frozen=True)
class RelyingPartyPolicy:
    anchors: FrozenSet[str]
    max_revocation_staleness_s: int
    requires_delegation_scope: bool


@dataclass(frozen=True)
class ChainLink:
    issuer: str
    signature_ok: bool
    delegation_scope: Optional[str]  # None means scope not asserted
    revocation_age_s: Optional[int]  # None means status unresolvable


def present_or_refuse(
    chain: list[ChainLink],
    policy: RelyingPartyPolicy,
    rp_anchor_set_hash: str,
    cached_anchor_set_hash: str,
) -> None:
    if rp_anchor_set_hash != cached_anchor_set_hash:
        raise AttestationRefusal(
            "anchor set drift: cached bundle validated against a different "
            f"anchor set ({cached_anchor_set_hash}) than RP declares "
            f"({rp_anchor_set_hash}); cannot re-verify"
        )

    for i, link in enumerate(chain):
        if not link.signature_ok:
            raise AttestationRefusal(f"link {i} ({link.issuer}): signature invalid")

        if link.issuer not in policy.anchors and i == len(chain) - 1:
            raise AttestationRefusal(
                f"link {i} ({link.issuer}): root not in RP anchor set "
                f"{sorted(policy.anchors)}"
            )

        if link.revocation_age_s is None:
            raise AttestationRefusal(
                f"link {i} ({link.issuer}): revocation status unresolvable; "
                "refusing to present"
            )
        if link.revocation_age_s > policy.max_revocation_staleness_s:
            raise AttestationRefusal(
                f"link {i} ({link.issuer}): revocation status stale "
                f"({link.revocation_age_s}s > {policy.max_revocation_staleness_s}s)"
            )

        if policy.requires_delegation_scope and i < len(chain) - 1:
            if link.delegation_scope is None:
                raise AttestationRefusal(
                    f"link {i} ({link.issuer}): intermediate link lacks "
                    "delegation_scope; chain is semantically void for delegation"
                )
            if "delegate" not in link.delegation_scope.split(","):
                raise AttestationRefusal(
                    f"link {i} ({link.issuer}): attested for "
                    f"{link.delegation_scope!r} but not for 'delegate'"
                )

    # All preconditions established: presentation is equivalent to verification.
    return None
