# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-transitive-trust-decay-from-adversarial-reputation-laundering-in-cross-org-a2a-scoring.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-transitive-trust-decay-from-adversarial-reputation-laundering-in-cross-org-a2a-scoring.warrant.json
# Markdown SHA-256:   0d11f16c776a13f2ca11a2c21c3df930b581f1b23531dee1b240192d7cb2c7f8
# Code AST SHA-256:   e70ffc2a6afc8ab913e0a17a86427dd05852e55408659e26f185e8b63d95cec0
# Verified Classes:   ReputationLaunderingError, Endorsement
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Iterable


class ReputationLaunderingError(RuntimeError):
    """Raised when aggregate trust exceeds the root-budget ceiling
    while root entropy is too low to explain it as honest popularity."""


@dataclass(frozen=True)
class Endorsement:
    endorser_root: str      # externally anchored org principal
    target: str
    weight: float           # assigned by endorser, 0..1
    hops: int               # path length from root to target


def evaluate_trust(
    endorsements: Iterable[Endorsement],
    damping: float = 0.8,
    root_budget: float = 1.0,
    entropy_floor: int = 5,
) -> float:
    endorsements = list(endorsements)
    if not endorsements:
        return 0.0

    roots = {e.endorser_root for e in endorsements}
    total = sum(e.weight * (damping ** e.hops) for e in endorsements)
    mean_hops = sum(e.hops for e in endorsements) / len(endorsements)
    ceiling = root_budget / max(mean_hops, 1.0)

    if total > ceiling and len(roots) < entropy_floor:
        raise ReputationLaunderingError(
            f"aggregate trust {total:.3f} exceeds ceiling {ceiling:.3f} "
            f"with only {len(roots)} independent roots (floor={entropy_floor}); "
            f"target={endorsements[0].target} — quarantine pending root attestation"
        )
    return total
