# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-a-federated-agent-trust-graph-fail-open-when-a-manifest-s-issuer-field-is-co-signed-by-a-delegated-authority-that-itself-has-n.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-a-federated-agent-trust-graph-fail-open-when-a-manifest-s-issuer-field-is-co-signed-by-a-delegated-authority-that-itself-has-n.warrant.json
# Markdown SHA-256:   3c7d1895cc70a5b31583c4220cf34edb27a509876b71992664e92be6b492d5f9
# Code AST SHA-256:   888a27f6df939ff6ee0d05381ae301632b13c4ff1a169615023d48e0dfaa26c0
# Verified Classes:   DelegatedAuthorityRevocationGap, SigningChain, RevocationOracle
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass, field
from typing import Iterable


class DelegatedAuthorityRevocationGap(RuntimeError):
    """Raised when a verifier cannot prove the signing authority's liveness.

    This is not a signature failure. The signature is valid. The failure is
    that the authority that produced it may have been revoked after the
    delegation certificate was minted, and the verifier has no channel to
    observe that revocation keyed to the correct subject.
    """


@dataclass(frozen=True)
class SigningChain:
    leaf_agent_did: str
    delegated_authority_did: str
    root_did: str
    delegation_not_after: float
    authority_key_cached_at: float


@dataclass
class RevocationOracle:
    revoked: set[str] = field(default_factory=set)

    def is_revoked(self, did: str) -> bool:
        return did in self.revoked


CACHE_TTL_SECONDS = 3600.0
MAX_ACCEPTABLE_EXPOSURE_SECONDS = 120.0


def verify_manifest(
    chain: SigningChain,
    oracle: RevocationOracle,
    now: float | None = None,
) -> None:
    """Verify a federated agent manifest, failing closed on liveness gaps.

    The critical check is that revocation is queried for the *delegated
    authority*, not for the leaf agent. Querying the leaf is the default
    behavior of most VC libraries and is the source of the silent gap.
    """
    now = time.time() if now is None else now

    # Structural check: delegation must still be within its validity window.
    if now >= chain.delegation_not_after:
        raise DelegatedAuthorityRevocationGap(
            f"delegation certificate for {chain.delegated_authority_did} "
            f"expired at {chain.delegation_not_after}; chain walk fails closed"
        )

    # Liveness check: query revocation for EVERY DID in the chain, including
    # the delegated authority. This is the check that the incident verifiers
    # skipped by keying off manifest.issuer.
    for did in (chain.leaf_agent_did, chain.delegated_authority_did, chain.root_did):
        if oracle.is_revoked(did):
            raise DelegatedAuthorityRevocationGap(
                f"DID {did} is revoked; manifest rejected at chain position "
                f"of {chain.delegated_authority_did}"
            )

    # Cache-freshness check: if the authority's key was cached longer ago
    # than the maximum acceptable exposure window, we cannot prove liveness
    # even though the revocation oracle says "good" right now. Fail closed.
    key_age = now - chain.authority_key_cached_at
    if key_age > MAX_ACCEPTABLE_EXPOSURE_SECONDS:
        raise DelegatedAuthorityRevocationGap(
            f"delegated authority key for {chain.delegated_authority_did} "
            f"cached {key_age:.0f}s ago, exceeds exposure bound "
            f"{MAX_ACCEPTABLE_EXPOSURE_SECONDS}s; re-resolve DID document "
            f"before admitting manifest"
        )


def admit(chain: SigningChain, oracle: RevocationOracle) -> bool:
    try:
        verify_manifest(chain, oracle)
    except DelegatedAuthorityRevocationGap:
        return False
    return True
