# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-a-revoked-agent-identity-continue-to-authenticate-across-a-cross-org-a2a-mesh-after-key-rotation-propagation.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-a-revoked-agent-identity-continue-to-authenticate-across-a-cross-org-a2a-mesh-after-key-rotation-propagation.warrant.json
# Markdown SHA-256:   d635de97da7790c9b3f4df6afb35aa78a457091da1a45983ec767fdf02d7adbb
# Code AST SHA-256:   b87c06fe3c80dc9ea5108a86685aa35fc8b083d9a01aeee58207ceb05d7269f8
# Verified Classes:   RevocationWindowViolation, TrustBundle, Assertion
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import FrozenSet


class RevocationWindowViolation(RuntimeError):
    """Raised when a relying party would accept an assertion from a revoked
    subject because the old signing key is still within its grace window."""


@dataclass(frozen=True)
class TrustBundle:
    org: str
    active_key_id: str
    retired_key_ids: FrozenSet[str]  # still accepted during grace window
    grace_seconds_remaining: int


@dataclass(frozen=True)
class Assertion:
    subject: str
    signing_key_id: str
    scope_class: str  # "read" | "write" | "delegate"


def validate_assertion(
    assertion: Assertion,
    bundle: TrustBundle,
    revoked_subjects: FrozenSet[str],
    strict_scopes: FrozenSet[str] = frozenset({"write", "delegate"}),
) -> None:
    """Bundle-based validation. Returns None on accept, raises on reject.

    The subtle boundary: a revoked subject whose assertion is signed by a
    retired-but-still-graced key passes signature validation. We must refuse
    that combination for strict scopes, because revocation did not actually
    take effect for this path.
    """
    if assertion.signing_key_id == bundle.active_key_id:
        key_ok = True
    elif assertion.signing_key_id in bundle.retired_key_ids:
        key_ok = bundle.grace_seconds_remaining > 0
    else:
        key_ok = False

    if not key_ok:
        raise RevocationWindowViolation(
            f"assertion key {assertion.signing_key_id!r} not in trust bundle for {bundle.org}"
        )

    if assertion.subject in revoked_subjects:
        # Revocation is live, but the key path is not. For strict scopes the
        # grace window must not be allowed to mask revocation.
        if assertion.scope_class in strict_scopes:
            raise RevocationWindowViolation(
                f"subject {assertion.subject!r} revoked but assertion signed by "
                f"graced key {assertion.signing_key_id!r} "
                f"(grace_remaining={bundle.grace_seconds_remaining}s); "
                f"strict scope {assertion.scope_class!r} requires live deny-list"
            )
        # Read scopes tolerate the window by policy; log and accept.

    return None
