# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-of-a-byzantine-fault-tolerant-commitment-protocol-reject-a-result-attestation-that-is-signed-by-a-quorum-of-age.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-of-a-byzantine-fault-tolerant-commitment-protocol-reject-a-result-attestation-that-is-signed-by-a-quorum-of-age.warrant.json
# Markdown SHA-256:   157afbb63250192b74e55a396aa6d46eb9f552812c6e5e19244c8bd346dc1e6c
# Code AST SHA-256:   dcf0a77f63949bfe56262b068c7f138c470968fe2650e1b0e83f4ee892b3c6d0
# Verified Classes:   AttestationRejected, Attestation, QuorumCertificate
# Defensive Guards:   7 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass
from typing import Callable, Dict, List


class AttestationRejected(RuntimeError):
    """Raised when a result attestation quorum fails the validity predicate."""
    pass


@dataclass(frozen=True)
class Attestation:
    agent_id: str
    task_id: str
    result_hash: str
    epoch: int
    execution_binding: str  # commitment to trace or proof; empty if none


@dataclass(frozen=True)
class QuorumCertificate:
    task_id: str
    result_hash: str
    epoch: int
    signers: List[str]
    threshold: int


def verify_result_attestation(
    qc: QuorumCertificate,
    attestations: Dict[str, Attestation],
    validity_predicate: Callable[[str], bool],
    current_epoch: int,
    min_independent_executions: int,
) -> bool:
    # C4: epoch monotonicity
    if qc.epoch > current_epoch:
        raise AttestationRejected(
            f"QC epoch {qc.epoch} is ahead of current epoch {current_epoch}"
        )
    if qc.epoch < current_epoch - 1:
        raise AttestationRejected(
            f"QC epoch {qc.epoch} is stale relative to current epoch {current_epoch}"
        )

    # Signature threshold
    if len(qc.signers) < qc.threshold:
        raise AttestationRejected(
            f"QC has {len(qc.signers)} signers, needs {qc.threshold}"
        )

    # C1: execution binding — every signer must commit to an execution trace
    bound = [
        attestations[s]
        for s in qc.signers
        if s in attestations and attestations[s].execution_binding
    ]
    if len(bound) < min_independent_executions:
        raise AttestationRejected(
            f"only {len(bound)} of {len(qc.signers)} signers have execution "
            f"binding; need {min_independent_executions}"
        )

    # C2: independence — distinct execution bindings (no shared trace)
    distinct_bindings = {a.execution_binding for a in bound}
    if len(distinct_bindings) < min_independent_executions:
        raise AttestationRejected(
            f"execution bindings are not independent: "
            f"{len(distinct_bindings)} distinct across {len(bound)} signers"
        )

    # C3: validity predicate must pass on the attested result
    if not validity_predicate(qc.result_hash):
        raise AttestationRejected(
            f"validity predicate failed for result {qc.result_hash}"
        )

    return True


def make_qc(task_id: str, result_hash: str, epoch: int,
            signers: List[str], threshold: int) -> QuorumCertificate:
    if len(set(signers)) != len(signers):
        raise AttestationRejected("duplicate signer in QC")
    return QuorumCertificate(task_id, result_hash, epoch, signers, threshold)
