# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-auto-compaction-of-an-agent-trajectory.md
# Remnant Warrant:    https://corpusfetch.com/c/under-what-conditions-must-an-instance-refuse-auto-compaction-of-an-agent-trajectory.warrant.json
# Markdown SHA-256:   04e8f261fd392e8596de4b8d255e0249f7ed96d587dd54f1fb3bcb481f50777a
# Code AST SHA-256:   b60e2f1e06fdc906b846e31b95daf0fb6558d31ffa925d47c63eb73f695ffeaf
# Verified Classes:   LiveSpanCompactionError, Span
# Defensive Guards:   1 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import List, Optional


class LiveSpanCompactionError(RuntimeError):
    """Raised when auto-compaction is attempted on a span that is not quiescent."""
    pass


@dataclass
class Span:
    span_id: str
    tokens: int
    open_obligations: List[str] = field(default_factory=list)
    null_results: List[str] = field(default_factory=list)
    unresolved_refs: List[str] = field(default_factory=list)
    addressable: bool = True  # original retained verbatim on disk


def is_quiescent(span: Span, contradiction_probe_passed: bool) -> bool:
    if span.open_obligations or span.null_results or span.unresolved_refs:
        return False
    if not span.addressable and not contradiction_probe_passed:
        return False
    return True


def compact(span: Span, contradiction_probe_passed: bool = False) -> str:
    if not is_quiescent(span, contradiction_probe_passed):
        raise LiveSpanCompactionError(
            f"refusing to compact live span {span.span_id}: "
            f"obligations={span.open_obligations} "
            f"null_results={span.null_results} "
            f"unresolved_refs={span.unresolved_refs} "
            f"addressable={span.addressable} "
            f"probe_passed={contradiction_probe_passed}"
        )
    return f"<summary of {span.span_id}: {span.tokens} tokens>"


def auto_compact(trajectory: List[Span], threshold_tokens: int) -> List[str]:
    total = sum(s.tokens for s in trajectory)
    if total <= threshold_tokens:
        return []
    out = []
    for span in trajectory:
        try:
            out.append(compact(span))
        except LiveSpanCompactionError:
            # Correct outcome: defer this span, compact around it.
            out.append(f"<retained live span {span.span_id}>")
    return out
