# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-propagate-a-peer-s-slashing-evidence-the-transitive-attestation-equivocation-trap-in-federated-agent-reputation.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-propagate-a-peer-s-slashing-evidence-the-transitive-attestation-equivocation-trap-in-federated-agent-reputation.warrant.json
# Markdown SHA-256:   51df15dc6ad03113ad93958eeaf62f0c3362d54367fc1535b25315b0c93123f4
# Code AST SHA-256:   3f9e1b264359e9c61f7ff48ca7cf78d7a0f3d630c1a200fc0044fc245ef58c44
# Verified Classes:   IncomparableEpochError, Attestation, SlashingRecord
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Sequence


class IncomparableEpochError(RuntimeError):
    """Raised when a composite slashing record bundles attestations drawn from
    mutually incomparable epoch commitments of the same accused agent."""


@dataclass(frozen=True)
class Attestation:
    observer_org: str
    accused_key: str
    # The epoch commitment the accused presented to *this* observer.
    epoch_commitment: bytes
    signature: bytes


@dataclass(frozen=True)
class SlashingRecord:
    accused_key: str
    attestations: Sequence[Attestation] = field(default_factory=tuple)


def _is_prefix(a: bytes, b: bytes) -> bool:
    return b.startswith(a)


def _comparable(a: bytes, b: bytes) -> bool:
    # Two epoch commitments are comparable iff one is a prefix of the other,
    # i.e. they lie on a single causal history. Anything else means the
    # accused agent equivocated between the two observers.
    return _is_prefix(a, b) or _is_prefix(b, a)


def validate_for_propagation(record: SlashingRecord) -> None:
    """Enforce the transitive-attestation invariant before propagation.

    Refuses (raises) if any two attestations cite incomparable epoch
    commitments of the accused. Splitting is the caller's responsibility.
    """
    seen: list[tuple[str, bytes]] = []
    for att in record.attestations:
        if att.accused_key != record.accused_key:
            raise IncomparableEpochError(
                f"attestation from {att.observer_org} accuses a different key"
            )
        if not att.epoch_commitment:
            # Unknown comparability must default to refuse.
            raise IncomparableEpochError(
                f"attestation from {att.observer_org} lacks an epoch commitment; "
                "comparability undeterminable, refusing to aggregate"
            )
        for prior_org, prior_root in seen:
            if not _comparable(prior_root, att.epoch_commitment):
                raise IncomparableEpochError(
                    "incomparable epoch commitments: "
                    f"{prior_org}={prior_root.hex()[:12]} vs "
                    f"{att.observer_org}={att.epoch_commitment.hex()[:12]}; "
                    "propagate separately as an equivocation proof"
                )
        seen.append((att.observer_org, att.epoch_commitment))


def propagate(record: SlashingRecord) -> None:
    validate_for_propagation(record)
    # ... hand record to the slashing quorum transport ...
