# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-bound-speculative-tool-call-prefetching-when-the-speculation-cost-model-and-the-abort-path-disagree-about-what-wasted-means.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-bound-speculative-tool-call-prefetching-when-the-speculation-cost-model-and-the-abort-path-disagree-about-what-wasted-means.warrant.json
# Markdown SHA-256:   4cea20dce701a3e08fa7c0bff1df345e2e88a672191f6de0efa22328ba8392c1
# Code AST SHA-256:   95cc8b36092e499646418011679497e2e7e587f74c7f764befacb8348fe6c7b5
# Verified Classes:   UnverifiedAbortError, SpeculationCostModel
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class UnverifiedAbortError(RuntimeError):
    """Raised when the abort path reports success without a cancellation receipt."""
    pass


class SpeculationCostModel:
    def __init__(self, hit_probability: float, latency_saved_ms: float,
                 miss_penalty_ms: float, abort_verified: bool):
        self.hit_probability = hit_probability
        self.latency_saved_ms = latency_saved_ms
        self.miss_penalty_ms = miss_penalty_ms
        self.abort_verified = abort_verified

    def net_expected_value_ms(self) -> float:
        return (self.hit_probability * self.latency_saved_ms
                - (1.0 - self.hit_probability) * self.miss_penalty_ms)

    def should_speculate(self, full_call_cost_ms: float) -> bool:
        # If abort is not verified, the miss penalty must be the full call cost.
        effective_miss = self.miss_penalty_ms if self.abort_verified else full_call_cost_ms
        if not self.abort_verified and self.miss_penalty_ms < full_call_cost_ms:
            raise UnverifiedAbortError(
                f"miss_penalty {self.miss_penalty_ms}ms < full_call_cost "
                f"{full_call_cost_ms}ms with unverified abort; cost model is unsound"
            )
        ev = (self.hit_probability * self.latency_saved_ms
              - (1.0 - self.hit_probability) * effective_miss)
        return ev > 0.0


def admit_speculation(model: SpeculationCostModel, full_call_cost_ms: float) -> bool:
    try:
        return model.should_speculate(full_call_cost_ms)
    except UnverifiedAbortError:
        # Fail closed: disable speculation for this tool until abort is verified.
        return False
