# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-byzantine-llm-agreement-as-invalid-despite-quorum-the-semantic-divergence-invariant-in-high-stakes-multi-agen.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-byzantine-llm-agreement-as-invalid-despite-quorum-the-semantic-divergence-invariant-in-high-stakes-multi-agen.warrant.json
# Markdown SHA-256:   b4826df0abac39591571198ab68179cad0c4ee053a2a4a5a97ee85bb9a286bc9
# Code AST SHA-256:   09326cbe80682502b6ec6d6bd1f1b978c393cd3a139e474ad0064afdd740595e
# Verified Classes:   SemanticDivergenceError, Vote, QuorumDecision
# Defensive Guards:   6 fail-closed invariants (zero external deps)

from __future__ import annotations

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


class SemanticDivergenceError(RuntimeError):
    """Raised when a quorum exists but endorsed artifacts are not commitment-equivalent."""


@dataclass(frozen=True)
class Vote:
    agent_id: str
    commitment: dict[str, Any]
    signature: str


@dataclass
class QuorumDecision:
    proposal_id: str
    f: int
    votes: list[Vote] = field(default_factory=list)


# Commitment-equivalence relation. Deliberately stricter than projection hashing:
# requires total field coverage and closed-vocabulary values. Free-text fields
# are not permitted at this layer; they must be normalized upstream.
REQUIRED_FIELDS = frozenset(
    {"amount", "currency", "settlement", "modality", "scope", "counterparty_id"}
)
CLOSED_VOCAB = {
    "currency": {"USD", "EUR", "GBP"},
    "settlement": {"T+0", "T+1", "T+2"},
    "modality": {"MUST", "MUST_NOT", "MAY"},
    "scope": {"ALL", "REGION_X", "REGION_Y"},
}


def commitment_equivalence_key(c: dict[str, Any]) -> tuple:
    missing = REQUIRED_FIELDS - c.keys()
    if missing:
        raise SemanticDivergenceError(
            f"commitment missing required fields {sorted(missing)}; "
            "absent-vs-default ambiguity makes equivalence undecidable"
        )
    for field_name, allowed in CLOSED_VOCAB.items():
        if c[field_name] not in allowed:
            raise SemanticDivergenceError(
                f"field {field_name!r}={c[field_name]!r} outside closed vocabulary; "
                "free-text values cannot be commitment-equivalent"
            )
    return tuple((k, c[k]) for k in sorted(REQUIRED_FIELDS))


def evaluate_quorum(decision: QuorumDecision) -> tuple:
    """Return the single equivalence class if and only if the quorum is semantically valid.

    Raises SemanticDivergenceError if a quorum exists but the endorsed artifacts
    partition into more than one commitment-equivalence class.
    """
    quorum_size = 2 * decision.f + 1
    if len(decision.votes) < quorum_size:
        raise SemanticDivergenceError(
            f"no quorum: {len(decision.votes)} votes < required {quorum_size}"
        )

    classes: dict[tuple, list[str]] = {}
    for vote in decision.votes:
        key = commitment_equivalence_key(vote.commitment)
        classes.setdefault(key, []).append(vote.agent_id)

    if len(classes) > 1:
        breakdown = {str(k): v for k, v in classes.items()}
        raise SemanticDivergenceError(
            f"quorum present but semantically divergent across "
            f"{len(classes)} commitment classes: {breakdown}; "
            "withhold commit and re-propose with tightened schema"
        )

    (only_class, members), = classes.items()
    if len(members) < quorum_size:
        raise SemanticDivergenceError(
            f"largest equivalence class has {len(members)} votes < quorum {quorum_size}"
        )
    return only_class, members


def commit(decision: QuorumDecision) -> tuple:
    try:
        return evaluate_quorum(decision)
    except SemanticDivergenceError:
        # Operational policy: never compensate after a binding commit when the
        # divergence could have been caught here. Escalate instead.
        raise
