# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-a-speculative-decoding-router-reject-a-draft-token-acceptance-spike-that-arrives-after-backend-failover.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-a-speculative-decoding-router-reject-a-draft-token-acceptance-spike-that-arrives-after-backend-failover.warrant.json
# Markdown SHA-256:   0d17e37fed50edd70665745783f435411458812ea58f656508dad32c4bdee692
# Code AST SHA-256:   be82107afd28b369944cbdd3889f22390460f2339b3c72da87a7caacd8100e46
# Verified Classes:   SpeculativeRoutingError, BackendBinding
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class SpeculativeRoutingError(RuntimeError):
    """Raised when a routing decision would act on an unattributable acceptance signal."""


@dataclass(frozen=True)
class BackendBinding:
    backend_id: str
    tokenizer_hash: str
    params_hash: str  # hash of (temperature, top_p, seed, grammar_id)
    kv_block_size: int


def params_hash(temperature: float, top_p: float, seed: int, grammar_id: str) -> str:
    blob = f"{temperature:.4f}|{top_p:.4f}|{seed}|{grammar_id}".encode()
    return hashlib.sha256(blob).hexdigest()[:16]


def validate_post_failover_acceptance(
    draft: BackendBinding,
    target: BackendBinding,
    pre_acceptance: float,
    post_acceptance: float,
    forwarded_logits: bool,
) -> None:
    """
    Gate a post-failover acceptance spike before the router acts on it.
    Raises SpeculativeRoutingError if the spike cannot be attributed to a
    verified-consistent backend pair.
    """
    if draft.tokenizer_hash != target.tokenizer_hash:
        raise SpeculativeRoutingError(
            f"tokenizer mismatch draft={draft.backend_id} target={target.backend_id}"
        )
    if draft.kv_block_size != target.kv_block_size:
        raise SpeculativeRoutingError(
            "kv block size mismatch; verifier positional indices unreliable"
        )
    if forwarded_logits and draft.params_hash != target.params_hash:
        raise SpeculativeRoutingError(
            "draft/target sampling params differ while logits are forwarded; "
            "acceptance measures argmax agreement, not speculative correctness"
        )

    delta = post_acceptance - pre_acceptance
    if delta > 0.25:
        # Spike is only trustworthy if the pair is param-consistent.
        if draft.params_hash != target.params_hash:
            raise SpeculativeRoutingError(
                f"unattributable acceptance spike delta={delta:.3f}; "
                f"draft params={draft.params_hash} target params={target.params_hash}"
            )
        # Consistent pair + real gain: allow cooldown extension.
        return


def route_after_failover(
    draft: BackendBinding,
    target: BackendBinding,
    pre_acceptance: float,
    post_acceptance: float,
    forwarded_logits: bool,
) -> str:
    try:
        validate_post_failover_acceptance(
            draft, target, pre_acceptance, post_acceptance, forwarded_logits
        )
    except SpeculativeRoutingError:
        # Reject pairing; re-bind to a params-consistent backend.
        return "REBIND"
    return "LOCK"
