# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-when-model-driven-scratchpad-rewriting-violates-the-idempotence-invariant-of-a-self-editing-context-window.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-when-model-driven-scratchpad-rewriting-violates-the-idempotence-invariant-of-a-self-editing-context-window.warrant.json
# Markdown SHA-256:   8ad5a742dd0362992727c3b9890cf8bc87e35643de09bb2812985e978ee463b6
# Code AST SHA-256:   dd87e31266c89372622ff1cc5ff7e062bc8e5ac7e3590575b1931b16ed16e064
# Verified Classes:   NonIdempotentRewriteError
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import re
import hashlib

class NonIdempotentRewriteError(RuntimeError):
    """Raised when a scratchpad rewrite policy violates the idempotence invariant
    or drops protected verbatim literals required by downstream consumers."""
    pass

DIRECTIVE_MARKERS = ("compress to essentials", "remove redundant", "summarize the notes")
LITERAL_PATTERN = re.compile(r"\b(?:[A-Z]{2,}-\d{3,}|[0-9a-f]{8,}|/[\w./-]+)\b")

def _hash(buf: str) -> str:
    return hashlib.sha256(buf.encode("utf-8")).hexdigest()

def _literals(buf: str) -> set:
    return set(LITERAL_PATTERN.findall(buf))

def guarded_rewrite(buffer: str, rewrite_fn, tolerance: float = 0.0) -> str:
    # 1. Directive co-location check: refuse if the governing instruction lives in the buffer.
    lowered = buffer.lower()
    for marker in DIRECTIVE_MARKERS:
        if marker in lowered:
            raise NonIdempotentRewriteError(
                f"rewrite directive '{marker}' is co-located in the buffer; "
                "relocate it out-of-band before rewriting"
            )

    # 2. Apply the policy and re-apply to test idempotence.
    once = rewrite_fn(buffer)
    twice = rewrite_fn(once)
    if _hash(once) != _hash(twice):
        # Measure divergence as a fraction of differing literals as a proxy.
        drift = len(_literals(once) ^ _literals(twice)) / max(len(_literals(once)), 1)
        if drift > tolerance:
            raise NonIdempotentRewriteError(
                f"non-idempotent rewrite: literal drift {drift:.2%} exceeds "
                f"tolerance {tolerance:.2%} (rewrite(rewrite(B)) != rewrite(B))"
            )

    # 3. Verbatim-preservation check on protected literals.
    lost = _literals(buffer) - _literals(once)
    if lost:
        raise NonIdempotentRewriteError(
            f"rewrite dropped protected literals required downstream: {sorted(lost)}"
        )

    return once
