# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-equivocation-at-the-speculative-branch-merge-point-under-byzantine-participants.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-equivocation-at-the-speculative-branch-merge-point-under-byzantine-participants.warrant.json
# Markdown SHA-256:   76c79a7cb7a83b5ab58bc7795d185f4d5b52a45253047766403b7a13e95f2bc3
# Code AST SHA-256:   66086218966ed07f82abea6d12edfc4edb9c3cff0c0939529fb49e60accd6f39
# Verified Classes:   BranchEquivocationError, BranchRecord
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass
from typing import Dict, Tuple


class BranchEquivocationError(RuntimeError):
    """Raised when one participant emits divergent digests for the same step."""


@dataclass(frozen=True)
class BranchRecord:
    participant_id: str
    step: int
    peer_id: str
    payload: bytes
    signature: bytes

    def digest(self) -> str:
        return hashlib.sha256(self.payload).hexdigest()


def reconcile_step(
    records: Dict[Tuple[str, int], Dict[str, BranchRecord]]
) -> Dict[Tuple[str, int], str]:
    """
    records maps (participant_id, step) -> {peer_id: BranchRecord}.
    Returns the agreed digest per (participant_id, step), or raises on equivocation.
    """
    agreed: Dict[Tuple[str, int], str] = {}
    for key, per_peer in records.items():
        digests = {peer: rec.digest() for peer, rec in per_peer.items()}
        distinct = set(digests.values())
        if len(distinct) > 1:
            participant_id, step = key
            raise BranchEquivocationError(
                f"participant {participant_id!r} equivocated at step {step}: "
                f"peers saw digests {digests}; merge must halt and exclude participant"
            )
        agreed[key] = distinct.pop()
    return agreed


def commit_after_reconcile(
    records: Dict[Tuple[str, int], Dict[str, BranchRecord]],
    quorum: int,
) -> Dict[Tuple[str, int], str]:
    if quorum < 2:
        raise BranchEquivocationError(
            "quorum < 2 cannot cross-check digests; commit-on-merge is unsafe "
            "behind an adversarial participant"
        )
    agreed = reconcile_step(records)
    for key, per_peer in records.items():
        if len(per_peer) < quorum:
            raise BranchEquivocationError(
                f"insufficient peer witnesses for {key}: {len(per_peer)} < {quorum}"
            )
    return agreed
