# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-semantic-drift-introduced-by-automatic-context-compaction-in-long-horizon-agent-sessions.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-semantic-drift-introduced-by-automatic-context-compaction-in-long-horizon-agent-sessions.warrant.json
# Markdown SHA-256:   7efe87a1b23e4ac916228f7308080c958b9361b9bc7b4324c4c43931fa392ca3
# Code AST SHA-256:   375e7d73062fd3704a2fec9ba4c16c1a49f621bbabd25455617ca14e87aa8192
# Verified Classes:   BindingDriftError, BeliefClass, Belief, GroundTruth, Session
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from enum import Enum


class BindingDriftError(RuntimeError):
    """Raised when a consequential action relies on an unverified or
    drifted binding inherited from a compacted summary."""
    pass


class BeliefClass(Enum):
    NARRATIVE = "narrative"   # safe to inherit from compaction
    BINDING = "binding"       # must be re-derived from ground truth


@dataclass
class Belief:
    phrase: str
    cls: BeliefClass
    resolved: str | None = None       # ground-truth value, if verified
    compaction_depth: int = 0


@dataclass
class GroundTruth:
    """External, non-summarized artifacts the instance can query."""
    resources: dict[str, str] = field(default_factory=dict)   # phrase -> canonical id
    constraints: set[str] = field(default_factory=set)


class Session:
    def __init__(self, truth: GroundTruth):
        self.truth = truth
        self.beliefs: dict[str, Belief] = {}

    def inherit_from_compaction(self, phrase: str, cls: BeliefClass, depth: int) -> None:
        # Compaction may populate narrative beliefs freely.
        # Binding beliefs are recorded but marked unresolved.
        self.beliefs[phrase] = Belief(phrase=phrase, cls=cls, compaction_depth=depth)

    def verify_binding(self, phrase: str) -> str:
        """Re-derive a binding belief from ground truth. Never trust the summary."""
        belief = self.beliefs.get(phrase)
        if belief is None or belief.cls is not BeliefClass.BINDING:
            raise BindingDriftError(
                f"'{phrase}' is not registered as a binding belief; "
                f"refusing to treat it as a pointer"
            )
        canonical = self.truth.resources.get(phrase)
        if canonical is None:
            raise BindingDriftError(
                f"binding '{phrase}' cannot be confirmed against ground truth "
                f"after {belief.compaction_depth} compaction generation(s); "
                f"antecedent may have been reassigned"
            )
        belief.resolved = canonical
        return canonical

    def act(self, phrase: str, dry_run: bool = False) -> str:
        target = self.verify_binding(phrase)
        # Re-inject constraints verbatim; they are never summarized.
        if "payment" in target and "dry_run_required" in self.truth.constraints:
            if not dry_run:
                raise BindingDriftError(
                    f"constraint 'dry_run_required' blocks action on '{target}'; "
                    f"constraint survived compaction only because it is out-of-band"
                )
        return f"executed against {target} (dry_run={dry_run})"


# --- demonstration of the boundary ---
truth = GroundTruth(
    resources={"the staging database": "db-staging-01"},
    constraints={"dry_run_required"},
)
s = Session(truth)

# Compaction rewrites "the staging database" as "the database" and loses the binding.
s.inherit_from_compaction("the database", BeliefClass.BINDING, depth=3)

try:
    s.act("the database")          # antecedent no longer resolvable -> loud failure
except BindingDriftError as e:
    print(f"refused: {e}")

# Correct path: narrative is inherited, binding is re-derived from ground truth.
s.inherit_from_compaction("the staging database", BeliefClass.BINDING, depth=3)
print(s.act("the staging database", dry_run=True))
