# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-commits-a-task-but-its-result-attestation-quorum-is-formed-from-a-different-view-than-the-commitment-quorum.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-commits-a-task-but-its-result-attestation-quorum-is-formed-from-a-different-view-than-the-commitment-quorum.warrant.json
# Markdown SHA-256:   b97bf38c3821f0cf4b9b5eda94f90274cd513adf6ec97c73c8405ae4d6959ebe
# Code AST SHA-256:   83da20f5feb704bc766b6c7db914fe855a932905b8cf8065d14df5b2f8c847d0
# Verified Classes:   UnchainedResultCertificateError, CommitmentCertificate, ResultCertificate
# Defensive Guards:   6 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class UnchainedResultCertificateError(RuntimeError):
    """Raised when a result certificate does not cryptographically bind to the
    commitment certificate for the same task, allowing cross-epoch duplication."""
    pass


@dataclass(frozen=True)
class CommitmentCertificate:
    task_id: str
    epoch: int
    view: int
    assignee: str
    fencing_token: str

    def digest(self) -> bytes:
        h = hashlib.sha256()
        h.update(self.task_id.encode())
        h.update(self.epoch.to_bytes(8, "big"))
        h.update(self.view.to_bytes(8, "big"))
        h.update(self.assignee.encode())
        h.update(self.fencing_token.encode())
        return h.digest()


@dataclass(frozen=True)
class ResultCertificate:
    task_id: str
    result_digest: bytes
    attestation_epoch: int
    attestation_quorum: tuple[str, ...]
    committed_cert_digest: bytes | None  # None == unchained (legacy)


def verify_result_binding(
    commitment: CommitmentCertificate,
    result: ResultCertificate,
    canonical_commitment: CommitmentCertificate,
    active_fencing_token: str,
) -> None:
    if result.task_id != commitment.task_id:
        raise UnchainedResultCertificateError(
            f"task id mismatch: result={result.task_id!r} commitment={commitment.task_id!r}"
        )

    # The core invariant: a result certificate must name the digest of the
    # commitment certificate it is the output of. Without this, a result
    # attested in a later epoch cannot be distinguished from a result for a
    # re-assigned task under a new commitment.
    if result.committed_cert_digest is None:
        raise UnchainedResultCertificateError(
            f"result for task {result.task_id!r} carries no commitment-certificate "
            f"digest; cross-epoch duplication is undetectable"
        )
    if result.committed_cert_digest != commitment.digest():
        raise UnchainedResultCertificateError(
            f"result for task {result.task_id!r} binds to commitment digest "
            f"{result.committed_cert_digest.hex()[:16]}... but presented commitment "
            f"digests to {commitment.digest().hex()[:16]}..."
        )

    # Epoch binding: the attestation must not predate the commitment epoch.
    if result.attestation_epoch < commitment.epoch:
        raise UnchainedResultCertificateError(
            f"result attestation epoch {result.attestation_epoch} precedes "
            f"commitment epoch {commitment.epoch} for task {result.task_id!r}"
        )

    # Fencing: a re-assigned task revokes the original assignee's capability.
    if commitment.fencing_token != active_fencing_token:
        raise UnchainedResultCertificateError(
            f"commitment for task {result.task_id!r} uses stale fencing token "
            f"{commitment.fencing_token!r}; active is {active_fencing_token!r} "
            f"(task was re-assigned)"
        )

    # Canonical-commitment check: only the finalized commitment for the task
    # may anchor a result.
    if commitment.digest() != canonical_commitment.digest():
        raise UnchainedResultCertificateError(
            f"commitment for task {result.task_id!r} is not the canonical "
            f"finalized commitment; refusing to accept result"
        )
