# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-speculate-on-a-tool-call-whose-side-effects-cannot-be-compensated.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-speculate-on-a-tool-call-whose-side-effects-cannot-be-compensated.warrant.json
# Markdown SHA-256:   34811fe86f16fae1b8f4fee2ff94564e9ca0d402a4e630bf6a985ea02af1932e
# Code AST SHA-256:   c1681988a4e4770fd02eeeb3a2ba8f4926201deaebb388ea2ba14b68b725dfad
# Verified Classes:   UncompensatableSpeculationError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class UncompensatableSpeculationError(RuntimeError):
    """Raised when a speculative tool call's side effect cannot be provably
    rolled back before the gate resolves. The call must be deferred, not speculated."""


EFFECT_BUFFERED = "BUFFERED"
EFFECT_REVERSIBLE = "REVERSIBLE"
EFFECT_IRREVERSIBLE = "IRREVERSIBLE"

SPECULATABLE = {EFFECT_BUFFERED, EFFECT_REVERSIBLE}


def gate_speculation(tool_name: str, effect_class: str, compensation_verified: bool) -> str:
    """Decide whether a tool call may be pre-executed ahead of the gate.

    Returns 'SPECULATE' or 'DEFER'. Raises if the effect class is unknown or if a
    claimed-reversible effect has no verified state-restoring compensation.
    """
    if effect_class not in (EFFECT_BUFFERED, EFFECT_REVERSIBLE, EFFECT_IRREVERSIBLE):
        raise UncompensatableSpeculationError(
            f"tool {tool_name!r} has unclassified effect {effect_class!r}; "
            f"refusing to speculate against an unknown effect class"
        )

    if effect_class == EFFECT_REVERSIBLE and not compensation_verified:
        raise UncompensatableSpeculationError(
            f"tool {tool_name!r} claims REVERSIBLE but compensation is not "
            f"provably state-restoring; a semantic undo is not a rollback"
        )

    if effect_class not in SPECULATABLE:
        return "DEFER"

    return "SPECULATE"
