# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/which-invariants-prevent-silent-loss-of-task-critical-state-during-automatic-context-compaction.md
# Remnant Warrant:    https://corpusfetch.com/c/which-invariants-prevent-silent-loss-of-task-critical-state-during-automatic-context-compaction.warrant.json
# Markdown SHA-256:   f1e7a6cf5e0b20cb7c92f9daf611a62604c0141849edda4e815401002409356d
# Code AST SHA-256:   dc2141f3f2b1e5950ddead4a11b239f3dfd5dce75ea53e9379d12c9364ec333c
# Verified Classes:   CompactionInvariantError, Region
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Iterable


class CompactionInvariantError(RuntimeError):
    """Raised when compaction would alter the set of task-critical commitments."""
    pass


@dataclass(frozen=True)
class Region:
    kind: str          # "narrative" | "commitment"
    key: str           # stable identity for commitments (e.g. "constraint:reversible")
    text: str


def compact(regions: Iterable[Region], summarize_narrative) -> list[Region]:
    regions = list(regions)
    pre_commitments = {r.key: r.text for r in regions if r.kind == "commitment"}

    out: list[Region] = []
    narrative_buffer: list[str] = []

    def flush() -> None:
        if narrative_buffer:
            out.append(Region("narrative", "summary", summarize_narrative(narrative_buffer)))
            narrative_buffer.clear()

    for r in regions:
        if r.kind == "commitment":
            flush()
            out.append(r)  # re-injected verbatim, never paraphrased
        else:
            narrative_buffer.append(r.text)
    flush()

    post_commitments = {r.key: r.text for r in out if r.kind == "commitment"}

    if post_commitments != pre_commitments:
        missing = pre_commitments.keys() - post_commitments.keys()
        altered = {
            k for k in pre_commitments.keys() & post_commitments.keys()
            if pre_commitments[k] != post_commitments[k]
        }
        raise CompactionInvariantError(
            f"compaction altered commitments: missing={sorted(missing)} altered={sorted(altered)}"
        )

    return out
