# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-s-vote-weight-exceeds-its-epistemic-independence-consensus-weighted-agent-voting-and-the-correlated-hallucination-cliff.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-s-vote-weight-exceeds-its-epistemic-independence-consensus-weighted-agent-voting-and-the-correlated-hallucination-cliff.warrant.json
# Markdown SHA-256:   115cd42da5136364ce01e5a4bc4a7a665f9516f21ae362490b74a7e1093f1298
# Code AST SHA-256:   d9699c796825af91e98444c30cdaca6444b5a257d20bdea23ebb331624128d2d
# Verified Classes:   CorrelatedConsensusError, AgentVote, VoteTally
# Defensive Guards:   5 fail-closed invariants (zero external deps)

"""Consensus-weighted voting guard: refuse to weight when evidence independence is unverifiable."""

from dataclasses import dataclass, field


class CorrelatedConsensusError(RuntimeError):
    """Raised when consensus weighting is requested on non-independent voters."""
    pass


@dataclass
class AgentVote:
    agent_id: str
    answer: str
    evidence_source: str          # provenance token: index id, corpus id, "parametric"
    evidence_present: bool        # did this agent actually retrieve supporting evidence?


@dataclass
class VoteTally:
    votes: list[AgentVote]
    beta: float = 1.5             # consensus sharpening exponent
    max_agreement_fraction: float = 0.85
    answer_absent_accuracy_delta: float | None = None  # measured, not assumed


def _independent_sources(votes: list[AgentVote]) -> int:
    return len({v.evidence_source for v in votes if v.evidence_present})


def _fallback_agreement_fraction(votes: list[AgentVote]) -> float:
    absent = [v for v in votes if not v.evidence_present]
    if not absent:
        return 0.0
    counts: dict[str, int] = {}
    for v in absent:
        counts[v.answer] = counts.get(v.answer, 0) + 1
    return max(counts.values()) / len(absent)


def weighted_decision(tally: VoteTally) -> dict:
    votes = tally.votes
    if not votes:
        raise CorrelatedConsensusError("empty vote set: no decision possible")

    # Invariant 1: evidence independence must be auditable.
    if _independent_sources(votes) < 2:
        raise CorrelatedConsensusError(
            f"consensus weighting unsafe: only {_independent_sources(votes)} "
            "independent evidence source(s) across voters; agreement is not evidence"
        )

    # Invariant 2: agreement must be decomposed from availability.
    fallback = _fallback_agreement_fraction(votes)
    if fallback >= tally.max_agreement_fraction:
        raise CorrelatedConsensusError(
            f"fallback-agreement fraction {fallback:.2f} exceeds "
            f"{tally.max_agreement_fraction:.2f}: voters agree without evidence, "
            "likely correlated confabulation; refusing to weight"
        )

    # Invariant 3: weighting must be bounded by a measured answer-absent delta.
    if tally.answer_absent_accuracy_delta is None:
        raise CorrelatedConsensusError(
            "answer-absent accuracy delta not measured; cannot certify "
            "weighting is on the safe side of the correlation cliff"
        )
    if tally.answer_absent_accuracy_delta < 0:
        raise CorrelatedConsensusError(
            f"consensus weighting loses {abs(tally.answer_absent_accuracy_delta):.2f} "
            "accuracy on the evidence-absent stratum; fall back to unweighted majority"
        )

    # Safe path: compute consensus weights.
    counts: dict[str, int] = {}
    for v in votes:
        counts[v.answer] = counts.get(v.answer, 0) + 1
    n = len(votes)
    weights = {a: (c / n) ** tally.beta for a, c in counts.items()}
    total = sum(weights.values())
    probs = {a: w / total for a, w in weights.items()}
    winner = max(probs, key=probs.get)
    return {"answer": winner, "confidence": probs[winner], "weighted": True}
