# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-should-an-instance-distinguish-speculative-tool-call-prefetch-from-committed-execution-when-invalidation-races-with-result-delivery.md
# Remnant Warrant:    https://corpusfetch.com/c/how-should-an-instance-distinguish-speculative-tool-call-prefetch-from-committed-execution-when-invalidation-races-with-result-delivery.warrant.json
# Markdown SHA-256:   763cdf05dc877d4184ef58798f617f30f8506ef934fb6d25141dbd4d34fd497d
# Code AST SHA-256:   791fc52e107ea8901d11b97b3a88eaa53679388a1ccf14bee4772e427a706410
# Verified Classes:   SpeculativeAdmissionError, CallSlot, SpeculativeStore
# Defensive Guards:   5 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass, field
from typing import Optional


class SpeculativeAdmissionError(RuntimeError):
    """Raised when a speculative result is folded under an invalid admission state."""
    pass


@dataclass
class CallSlot:
    call_id: str
    arg_hash: str
    tombstoned: bool = False
    committed_branch: bool = False
    result: Optional[bytes] = None


class SpeculativeStore:
    def __init__(self) -> None:
        self._slots: dict[str, CallSlot] = {}

    @staticmethod
    def _hash_args(args: dict) -> str:
        return hashlib.sha256(repr(sorted(args.items())).encode()).hexdigest()

    def issue(self, call_id: str, args: dict) -> CallSlot:
        # call-id must be unique per (logical call, arg hash); reuse is the bug.
        slot = CallSlot(call_id=call_id, arg_hash=self._hash_args(args))
        self._slots[call_id] = slot
        return slot

    def deliver(self, call_id: str, payload: bytes) -> None:
        slot = self._slots.get(call_id)
        if slot is None:
            return
        # Delivery never decides admissibility; it only records bytes.
        slot.result = payload

    def invalidate(self, call_id: str) -> None:
        slot = self._slots.get(call_id)
        if slot is None:
            return
        # Tombstone is written BEFORE returning; this is the durable ordering.
        slot.tombstoned = True

    def fold(self, call_id: str, committed_args: dict) -> bytes:
        slot = self._slots.get(call_id)
        if slot is None:
            raise SpeculativeAdmissionError(f"no slot for {call_id}")
        # Re-validate at fold time, against durable state, not cached verdicts.
        if slot.tombstoned:
            raise SpeculativeAdmissionError(
                f"slot {call_id} tombstoned at fold time; discard and re-issue"
            )
        if slot.arg_hash != self._hash_args(committed_args):
            raise SpeculativeAdmissionError(
                f"slot {call_id} argument hash mismatch; speculative args superseded"
            )
        if not slot.committed_branch:
            raise SpeculativeAdmissionError(
                f"slot {call_id} branch not committed; result inadmissible"
            )
        if slot.result is None:
            raise SpeculativeAdmissionError(
                f"slot {call_id} has no delivered result"
            )
        return slot.result
