# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-self-rewrite-its-working-set-the-coherence-budget-inversion.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-to-self-rewrite-its-working-set-the-coherence-budget-inversion.warrant.json
# Markdown SHA-256:   d131d43ccfaecb3acddaf41b688b5804c0bb45d5ce22bbe6e97ac3b748d7c3b6
# Code AST SHA-256:   526966f3f9e8f96f32a78b8609d2e95fa58be494124f9f824f5d6726862c3349
# Verified Classes:   CoherenceBudgetInversionError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class CoherenceBudgetInversionError(RuntimeError):
    """Raised when a self-rewrite would satisfy the token budget only by
    destroying antecedents of live references (net coherence loss)."""
    pass


def safe_self_rewrite(
    working_set: list[dict],
    budget: int,
    rederive_cost_per_ref: int,
    live_reference_ids: set[str],
) -> list[dict]:
    """Rewrite the working set only if the compression is budget-monotone
    with respect to coherence. Otherwise refuse and escalate."""

    tokens_before = sum(item["tokens"] for item in working_set)

    # Candidate rewrite: drop items not referenced, compress the rest.
    candidate = [i for i in working_set if i["id"] in live_reference_ids or not i.get("resolved")]
    tokens_after = sum(item["tokens"] for item in candidate)

    # Count references whose antecedent was dropped by the rewrite.
    surviving = {i["id"] for i in candidate}
    dangling = live_reference_ids - surviving
    penalty = len(dangling) * rederive_cost_per_ref
    saved = tokens_before - tokens_after

    # Budget assertion alone is insufficient: check coherence monotonicity.
    if tokens_after <= budget and saved <= penalty:
        raise CoherenceBudgetInversionError(
            f"rewrite saves {saved} tokens but incurs {penalty} in re-derivation "
            f"({len(dangling)} dangling refs); net loss. Evict closed subgoals or escalate."
        )

    if tokens_after > budget:
        # Cannot meet budget without coherence loss: escalate, do not compress.
        raise CoherenceBudgetInversionError(
            f"budget {budget} unreachable without destroying live antecedents; "
            f"best coherent size is {tokens_after}. Escalate to orchestrator."
        )

    return candidate
