# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-prune-a-tool-schema-mid-turn-the-argument-residency-invariant-in-multi-turn-context-engineering.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-prune-a-tool-schema-mid-turn-the-argument-residency-invariant-in-multi-turn-context-engineering.warrant.json
# Markdown SHA-256:   e80cc39b6dd692e4c8646e2b6331ce24f9c9afcc897fbcccf74fe230ae771197
# Code AST SHA-256:   0dba86e4bb157017af7eb1607b7e7af63cb89c887a827d4c045af48a5a15c7c5
# Verified Classes:   GhostToolInvariantError, ToolContextState
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from typing import Optional


class GhostToolInvariantError(RuntimeError):
    """Raised when a tool's schema residency is false while its argument
    residency is true and no tombstone has been emitted."""
    pass


@dataclass
class ToolContextState:
    name: str
    schema_resident: bool
    argument_resident: bool
    tombstoned: bool
    is_mutation: bool


def enforce_evict_or_tombstone(state: ToolContextState) -> Optional[str]:
    """Return a tombstone string if one must be emitted, else None.

    Raises GhostToolInvariantError if the state is unsound and cannot be
    repaired by tombstoning (e.g. a mutation tool with no tombstone
    support requested).
    """
    if state.schema_resident:
        return None
    if not state.argument_resident:
        # Safe prune: no arguments to reconstruct from.
        return None
    if state.tombstoned:
        return None

    if not state.is_mutation:
        # Read-only ghost tools fail closed; tombstone is advisory only.
        return (
            f"[TOOL TOMBSTONE] '{state.name}' schema withheld; "
            f"request re-expansion before calling."
        )

    # Mutation tool: schema gone, arguments present, no tombstone.
    # This is the unsound state. Repair by tombstoning; if the caller
    # cannot accept a tombstone, the invariant is violated.
    raise GhostToolInvariantError(
        f"Unsound context: mutation tool '{state.name}' has argument "
        f"residency but no schema residency and no tombstone. "
        f"Evict arguments or emit a tombstone before proceeding."
    )


def plan_turn_pruning(states: list[ToolContextState]) -> list[str]:
    tombstones: list[str] = []
    for s in states:
        t = enforce_evict_or_tombstone(s)
        if t:
            tombstones.append(t)
    return tombstones
