# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-that-a-self-editing-context-controller-has-crossed-from-compression-into-semantic-corruption.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-that-a-self-editing-context-controller-has-crossed-from-compression-into-semantic-corruption.warrant.json
# Markdown SHA-256:   0a6a2e480bf8a2f7902debef085f29013fea5a94b321e22a0413959958a455c6
# Code AST SHA-256:   f6b152f86b211418f51a44bce2b04cec85e61506b8ec57e532d205fa602f8b34
# Verified Classes:   ContextClosureViolation, Span
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Iterable


class ContextClosureViolation(RuntimeError):
    """Raised when a context edit would break referent closure of retained spans."""
    pass


@dataclass
class Span:
    span_id: str
    text: str
    requires: set[str] = field(default_factory=set)   # referent keys this span depends on
    provides: set[str] = field(default_factory=set)   # referent keys this span defines
    position: int = 0                                  # document order, for dependency checks
    depends_on_position: int | None = None             # earlier span this one infers from


def validate_edit(original: Iterable[Span], retained: Iterable[Span]) -> None:
    """Check that a proposed prune/rewrite preserves referent closure.

    Raises ContextClosureViolation on the first structural break. Returns None
    if the edit is safe to commit.
    """
    retained = list(retained)
    provided = set().union(*(s.provides for s in retained)) if retained else set()
    positions = {s.span_id: s.position for s in retained}

    for span in retained:
        missing = span.requires - provided
        if missing:
            raise ContextClosureViolation(
                f"span {span.span_id!r} requires referents {sorted(missing)} "
                f"not provided by any retained span; edit breaks closure"
            )
        if span.depends_on_position is not None:
            if span.position < span.depends_on_position:
                raise ContextClosureViolation(
                    f"span {span.span_id!r} at position {span.position} depends on "
                    f"material at position {span.depends_on_position}; "
                    f"reordering inverted a dependency edge"
                )

    # Numeric literals must retain their unit token within the same span.
    for span in retained:
        for token in span.text.split():
            if token.replace(".", "", 1).isdigit() and not any(
                u in span.text for u in ("ms", "s", "MB", "GB", "%", "x")
            ):
                raise ContextClosureViolation(
                    f"span {span.span_id!r} contains unitless numeric literal "
                    f"{token!r}; unit was dropped during rewrite"
                )


def apply_edit(original: list[Span], retained: list[Span]) -> list[Span]:
    validate_edit(original, retained)
    return retained
