# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-of-a-self-editing-context-policy-silently-corrupt-its-own-working-set-when-a-model-authored-rewrite-is-applied-as-a-guarded-diff.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-of-a-self-editing-context-policy-silently-corrupt-its-own-working-set-when-a-model-authored-rewrite-is-applied-as-a-guarded-diff.warrant.json
# Markdown SHA-256:   f40cefd1af0e15baa8561f934112ae02ffd06b0b15d1a18d72aa41f2dd613e3d
# Code AST SHA-256:   47c9c396263ab41905541e2b01107e0cf79dc1a109abe5a931cd3d63ff348d03
# Verified Classes:   StaleRewriteBaseError, ContextBuffer, RewritePatch
# Defensive Guards:   4 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class StaleRewriteBaseError(RuntimeError):
    """Raised when a model-authored context rewrite is applied against a base
    revision other than the one it was authored against."""


@dataclass(frozen=True)
class ContextBuffer:
    revision: int
    lines: tuple[str, ...]

    def content_hash(self) -> str:
        h = hashlib.sha256()
        for line in self.lines:
            h.update(line.encode("utf-8"))
            h.update(b"\n")
        return h.hexdigest()


@dataclass(frozen=True)
class RewritePatch:
    authored_against_hash: str
    delete_span: tuple[int, int]
    insert_lines: tuple[str, ...]


def apply_guarded_rewrite(
    live: ContextBuffer,
    patch: RewritePatch,
    *,
    protected_markers: tuple[str, ...] = (),
) -> ContextBuffer:
    # Binding check: the patch must be relative to the live revision.
    if patch.authored_against_hash != live.content_hash():
        raise StaleRewriteBaseError(
            f"patch authored against {patch.authored_against_hash[:12]} but live "
            f"revision {live.revision} hashes to {live.content_hash()[:12]}; "
            f"re-author against current revision"
        )

    start, end = patch.delete_span
    if not (0 <= start <= end <= len(live.lines)):
        raise StaleRewriteBaseError(
            f"delete span {(start, end)} out of bounds for {len(live.lines)} lines"
        )

    removed = live.lines[start:end]
    for marker in protected_markers:
        if any(marker in line for line in removed):
            raise StaleRewriteBaseError(
                f"patch would delete protected region containing {marker!r}"
            )

    new_lines = live.lines[:start] + patch.insert_lines + live.lines[end:]
    candidate = ContextBuffer(revision=live.revision + 1, lines=new_lines)

    # Post-apply assertion: recompute independently, do not trust the diff engine.
    expected = hashlib.sha256(
        "\n".join(new_lines).encode("utf-8") + b"\n"
    ).hexdigest()
    if candidate.content_hash() != expected:
        raise StaleRewriteBaseError("post-apply hash mismatch; buffer corrupted")

    return candidate
