# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-a-delegated-aaif-credential-whose-authority-chain-is-structurally-valid-but-temporally-misordered.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-a-delegated-aaif-credential-whose-authority-chain-is-structurally-valid-but-temporally-misordered.warrant.json
# Markdown SHA-256:   95d6ab107a84a653f33f9e1e34b14c81bcff490ea825869b1f12ee03a6b6a629
# Code AST SHA-256:   7b5055dc4a62cb119344af4a2081673651b95ad2cea03d94c6718512fdeb2950
# Verified Classes:   DelegationContainmentError, Credential
# Defensive Guards:   8 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Optional


class DelegationContainmentError(RuntimeError):
    """Raised when a delegated AAIF credential chain is structurally valid
    but temporally or scope-wise non-contained across a delegation hop."""
    pass


@dataclass(frozen=True)
class Credential:
    subject: str
    issuer: str
    not_before: float
    not_after: float
    issuance_time: Optional[float]
    scopes: frozenset


def validate_chain(
    chain: list[Credential],
    now: float,
    requested_lease: float,
    skew_bound: float,
) -> float:
    """Return the effective lease end, or raise DelegationContainmentError.

    chain is ordered root-first; chain[-1] is the present instance.
    """
    if not chain:
        raise DelegationContainmentError("empty chain")

    for i in range(1, len(chain)):
        parent, child = chain[i - 1], chain[i]

        # 1. Temporal containment with bounded skew.
        if child.not_before < parent.not_before - skew_bound:
            raise DelegationContainmentError(
                f"hop {i}: child.not_before {child.not_before} precedes "
                f"parent.not_before {parent.not_before} beyond skew {skew_bound}"
            )
        if child.not_after > parent.not_after + skew_bound:
            raise DelegationContainmentError(
                f"hop {i}: child.not_after {child.not_after} exceeds "
                f"parent.not_after {parent.not_after} beyond skew {skew_bound}"
            )

        # 2. Continuity at issuance: child must have been minted while
        #    the parent held live authority.
        if child.issuance_time is None:
            raise DelegationContainmentError(
                f"hop {i}: missing issuance_time; cannot establish continuity"
            )
        if not (parent.not_before - skew_bound
                <= child.issuance_time
                <= parent.not_after + skew_bound):
            raise DelegationContainmentError(
                f"hop {i}: issuance_time {child.issuance_time} outside parent "
                f"window [{parent.not_before}, {parent.not_after}]"
            )

        # 3. Scope narrowing: child scopes must be a subset of parent scopes.
        if not child.scopes.issubset(parent.scopes):
            raise DelegationContainmentError(
                f"hop {i}: scope broadening {child.scopes - parent.scopes}"
            )

    # Effective window is the intersection of all hops, minus skew.
    eff_start = max(c.not_before for c in chain) + skew_bound
    eff_end = min(c.not_after for c in chain) - skew_bound

    if now < eff_start:
        raise DelegationContainmentError(
            f"chain not yet effective until {eff_start}, now={now}"
        )
    if now + requested_lease > eff_end:
        raise DelegationContainmentError(
            f"requested lease {requested_lease}s exceeds effective authority "
            f"end {eff_end} (now={now})"
        )

    return eff_end
