# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-re-derive-its-aaif-capability-chain-the-stale-signer-boundary-in-signed-capability-tokens.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-re-derive-its-aaif-capability-chain-the-stale-signer-boundary-in-signed-capability-tokens.warrant.json
# Markdown SHA-256:   2b75fbcec8525faeef35259c41de4d8d7a859c12be9dd668d7bdc1348dabd54f
# Code AST SHA-256:   9f8b46afc53fc5304c3621502c043a9bf47d681a9ca386fe66e366ccb975ef60
# Verified Classes:   StaleChainError, ChainResolution
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass
from typing import Mapping


class StaleChainError(RuntimeError):
    """Raised when a capability chain cannot be re-derived from the current root."""


@dataclass(frozen=True)
class ChainResolution:
    leaf_id: str
    root_version: int
    revoked_keys: frozenset[str]


def rederive_chain(
    leaf_id: str,
    chain_keys: list[str],
    embedded_root_version: int,
    current_root_version: int,
    revoked_keys: frozenset[str],
    cached: ChainResolution | None,
) -> ChainResolution:
    # A chain is only reusable if its derivation inputs are unchanged.
    if cached is not None:
        inputs_unchanged = (
            cached.root_version == current_root_version
            and cached.revoked_keys == revoked_keys
        )
        if inputs_unchanged:
            return cached

    # Root rotated or revocations changed: the embedded version is now suspect.
    if embedded_root_version != current_root_version:
        # Not fatal by itself, but the chain must be re-walked, not trusted.
        pass

    for key in chain_keys:
        if key in revoked_keys:
            raise StaleChainError(
                f"chain for leaf {leaf_id!r} passes through revoked key {key!r}; "
                f"root_version={current_root_version}, embedded={embedded_root_version}"
            )

    return ChainResolution(
        leaf_id=leaf_id,
        root_version=current_root_version,
        revoked_keys=revoked_keys,
    )


def verify_capability(
    leaf_id: str,
    chain_keys: list[str],
    embedded_root_version: int,
    current_root_version: int,
    revoked_keys: frozenset[str],
    cache: Mapping[str, ChainResolution],
) -> ChainResolution:
    cached = cache.get(leaf_id)
    try:
        return rederive_chain(
            leaf_id=leaf_id,
            chain_keys=chain_keys,
            embedded_root_version=embedded_root_version,
            current_root_version=current_root_version,
            revoked_keys=revoked_keys,
            cached=cached,
        )
    except StaleChainError:
        # Fail closed: caller must treat the capability as unusable.
        raise
