# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-equivocation-in-bft-task-commitment-when-quorum-certificates-outlive-the-epoch-that-produced-them.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-equivocation-in-bft-task-commitment-when-quorum-certificates-outlive-the-epoch-that-produced-them.warrant.json
# Markdown SHA-256:   472a4fbb0cfb1a0b1a2f7e66c102bbd87a93ded6ca1b2ed7a764d8f7cbb0d978
# Code AST SHA-256:   e73bb3bec72ecb9021955b8088a73da258fc2d9802c75edcb255035a7cc65752
# Verified Classes:   StaleQuorumCertificateError, QuorumCertificate, ActiveValidatorSet
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Optional


class StaleQuorumCertificateError(RuntimeError):
    """Raised when a QC's issuing validator set is not admissible at consumption."""
    pass


@dataclass(frozen=True)
class QuorumCertificate:
    epoch: int
    set_hash: str          # hash of the validator set that signed
    signatures: tuple      # 2f+1 signatures over (value, epoch, set_hash)
    value: str


@dataclass(frozen=True)
class ActiveValidatorSet:
    epoch: int
    set_hash: str


def admit_qc(
    qc: QuorumCertificate,
    active: ActiveValidatorSet,
    *,
    max_epoch_lag: int = 1,
    safety_critical: bool = True,
) -> bool:
    """
    Decide whether a QC may be consumed to commit a task commitment.

    Returns True if admissible. Raises StaleQuorumCertificateError when the
    QC is cryptographically valid but operationally inadmissible.
    """
    if qc.set_hash != active.set_hash:
        # Signature validity is irrelevant: the signers are not the current set.
        raise StaleQuorumCertificateError(
            f"QC signed by set {qc.set_hash!r} at epoch {qc.epoch}, "
            f"but active set is {active.set_hash!r} at epoch {active.epoch}. "
            f"Replayed QC cannot bind a task commitment."
        )

    lag = active.epoch - qc.epoch
    if lag < 0:
        raise StaleQuorumCertificateError(
            f"QC epoch {qc.epoch} is ahead of active epoch {active.epoch}; "
            f"possible set-rollback attack."
        )
    if lag > max_epoch_lag:
        if safety_critical:
            raise StaleQuorumCertificateError(
                f"QC epoch lag {lag} exceeds max {max_epoch_lag} for "
                f"safety-critical commitment; refusing to finalize."
            )
        # Idempotent/revocable path: admit but flag for re-confirmation.
        return False

    return True


def commit_task(
    qc: QuorumCertificate,
    active: ActiveValidatorSet,
    task_id: str,
    *,
    safety_critical: bool = True,
) -> str:
    admissible = admit_qc(qc, active, safety_critical=safety_critical)
    if not admissible:
        # Tag with issuing epoch; caller must re-confirm under fresh QC.
        return f"{task_id}@epoch{qc.epoch}:PENDING_RECONFIRM"
    return f"{task_id}@epoch{active.epoch}:FINAL"
