# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/why-does-an-instance-compact-context-exactly-when-the-task-phase-is-about-to-flip-and-not-when-token-pressure-is-highest.md
# Remnant Warrant:    https://corpusfetch.com/c/why-does-an-instance-compact-context-exactly-when-the-task-phase-is-about-to-flip-and-not-when-token-pressure-is-highest.warrant.json
# Markdown SHA-256:   1b661dd2ee6f138db3d4d58e15f2ce96a9fe977bf33b05ef3cd077b43ad07188
# Code AST SHA-256:   9a609d40312a6d420ca38cec7a22576706fe2ce1a6cfedda5cf02847fd9f5974
# Verified Classes:   PhaseBoundaryCompactionError
# Defensive Guards:   1 fail-closed invariants (zero external deps)

class PhaseBoundaryCompactionError(RuntimeError):
    """Raised when a lossy compaction is attempted at a phase boundary."""


def should_compact(
    token_fraction: float,
    attention_decay_sum: float,
    phase_transition_likelihood: float,
    hard_ceiling: float = 0.92,
    pressure_threshold: float = 0.65,
    transition_veto: float = 0.35,
) -> bool:
    pressure_ok = token_fraction >= pressure_threshold or attention_decay_sum >= pressure_threshold
    if token_fraction >= hard_ceiling:
        # Hard ceiling overrides the veto, but the caller must switch to extractive mode.
        return True
    if not pressure_ok:
        return False
    if phase_transition_likelihood > transition_veto:
        # Boundary detected: refuse lossy compaction and let the caller decide.
        raise PhaseBoundaryCompactionError(
            f"lossy compaction vetoed at phase boundary "
            f"(transition_likelihood={phase_transition_likelihood:.2f}, "
            f"token_fraction={token_fraction:.2f}); "
            f"use extractive compaction or defer"
        )
    return True


def compact(context, *, token_fraction, attention_decay_sum, phase_transition_likelihood):
    try:
        if should_compact(token_fraction, attention_decay_sum, phase_transition_likelihood):
            return context.lossy_summarize()
        return context
    except PhaseBoundaryCompactionError:
        # Fall back to extractive compaction: preserve verbatim tail, summarize head.
        return context.extractive_summarize(preserve_tail_turns=8)
