# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-equivocation-before-commitment-in-byzantine-multi-agent-task-attestation.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-equivocation-before-commitment-in-byzantine-multi-agent-task-attestation.warrant.json
# Markdown SHA-256:   d2c9658e2515aa94e124bf519fde4e4b1bffa0c88542b4df0ed1c52bff3356ba
# Code AST SHA-256:   54917ab1f3fedcbecfb4e6a6c9cbece0985e47c8cac7e7dcc8d877a929784851
# Verified Classes:   EquivocationDetected, Commitment, CommitmentLedger
# Defensive Guards:   2 fail-closed invariants (zero external deps)

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


class EquivocationDetected(RuntimeError):
    """Raised when two distinct signed commitments exist for the same (task, epoch)."""


@dataclass(frozen=True)
class Commitment:
    task_id: str
    epoch: int
    payload: bytes
    signature: bytes  # in a real system: verified over (task_id, epoch, digest)

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


class CommitmentLedger:
    def __init__(self) -> None:
        self._seen: Dict[Tuple[str, int], Commitment] = {}

    def ingest(self, c: Commitment) -> None:
        # Invariant: signature covers the payload digest. Reject unsigned-digest
        # commitments here; the ledger only trusts digest-bound signatures.
        key = (c.task_id, c.epoch)
        prior = self._seen.get(key)
        if prior is not None and prior.digest != c.digest:
            raise EquivocationDetected(
                f"task={c.task_id} epoch={c.epoch} "
                f"digest_a={prior.digest} digest_b={c.digest}"
            )
        self._seen[key] = c

    def is_actionable(self, c: Commitment) -> bool:
        prior = self._seen.get((c.task_id, c.epoch))
        if prior is None:
            return False
        if prior.digest != c.digest:
            raise EquivocationDetected(
                f"task={c.task_id} epoch={c.epoch} conflicting digest at action time"
            )
        return True
