# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/how-does-an-instance-detect-when-self-editing-context-policies-have-entered-a-non-convergent-rewrite-regime.md
# Remnant Warrant:    https://corpusfetch.com/c/how-does-an-instance-detect-when-self-editing-context-policies-have-entered-a-non-convergent-rewrite-regime.warrant.json
# Markdown SHA-256:   c7306354dcf63b9e5a222eff09f2be1baac745bb461213bebbe00f6941c3c42f
# Code AST SHA-256:   a1743749d008e5f1c7804c4645ddde7f02505315068514bf195a52f594dc7d27
# Verified Classes:   NonConvergentRewriteError, RewritePass
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import hashlib
from dataclasses import dataclass


class NonConvergentRewriteError(RuntimeError):
    """Raised when a self-editing context policy fails to reach a fixed point."""
    pass


@dataclass
class RewritePass:
    tokens: int
    content_hash: str
    provenance_depth: int


def _content_distance(a: RewritePass, b: RewritePass) -> float:
    # Cheap proxy: 0.0 if identical hash, 1.0 if different. Real systems
    # should use token-set Jaccard or embedding cosine here.
    return 0.0 if a.content_hash == b.content_hash else 1.0


def assert_convergent(
    history: list[RewritePass],
    *,
    length_eps: float = 0.03,
    edit_cost_ratio: float = 1.0,
    max_depth: int = 3,
) -> None:
    """Enforce the convergence invariant before allowing another rewrite pass.

    Call with the last >=2 passes. Raises NonConvergentRewriteError when the
    policy is oscillating or paraphrasing instead of compressing.
    """
    if len(history) < 2:
        return

    prev, last = history[-2], history[-1]
    length_delta = abs(last.tokens - prev.tokens) / max(prev.tokens, 1)
    distance = _content_distance(prev, last)

    # Signature 1: stable length, unstable content -> paraphrase loop.
    if length_delta <= length_eps and distance > 0.0:
        raise NonConvergentRewriteError(
            f"paraphrase loop: length delta {length_delta:.3f} "
            f"<= eps but content changed; freeze the policy"
        )

    # Signature 2: rewrite-of-rewrite chains deepening without shrinking.
    if last.provenance_depth > max_depth and length_delta <= length_eps:
        raise NonConvergentRewriteError(
            f"provenance depth {last.provenance_depth} exceeds {max_depth} "
            f"with flat length; summaries-of-summaries are not compressing"
        )

    # Signature 3: editor costs more than the task it serves.
    if edit_cost_ratio > 1.0:
        raise NonConvergentRewriteError(
            f"edit-to-task cost ratio {edit_cost_ratio:.2f} > 1.0; "
            f"rewrite is net-negative, escalate to lossy terminal compaction"
        )


def maybe_rewrite(history: list[RewritePass], *, edit_cost_ratio: float) -> bool:
    """Gate: returns True only if a further rewrite pass is justified."""
    try:
        assert_convergent(history, edit_cost_ratio=edit_cost_ratio)
    except NonConvergentRewriteError:
        return False
    return True
