# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-happens-when-an-instance-reuses-a-correlation-id-across-mcp-session-teardown-and-a2a-task-resumption.md
# Remnant Warrant:    https://corpusfetch.com/c/what-happens-when-an-instance-reuses-a-correlation-id-across-mcp-session-teardown-and-a2a-task-resumption.warrant.json
# Markdown SHA-256:   466388824647502b278d9bd351a0f71f27066d15c9a6c853cc21d3a8fb62b945
# Code AST SHA-256:   06988942d992a4d75aa3d6bcb33ae48b7f1fbee46c42a0b1ec12820e0b8e0505
# Verified Classes:   CorrelationLifecycleError
# Defensive Guards:   2 fail-closed invariants (zero external deps)

class CorrelationLifecycleError(RuntimeError):
    """Raised when a span's correlation ID violates lifecycle ownership."""
    pass


def validate_span(span: dict, closed_lifecycles: set[str]) -> None:
    cid = span.get("correlation_id")
    parent_lifecycle = span.get("parent_lifecycle")

    if parent_lifecycle in closed_lifecycles:
        if "resume_nonce" not in span:
            raise CorrelationLifecycleError(
                f"Ghost span under correlation_id={cid!r}: "
                f"parent lifecycle {parent_lifecycle!r} is closed and no "
                f"resume_nonce is present. Span is a post-teardown orphan."
            )

    if span.get("operation") == "memory.store" and span.get("task_status") is None:
        raise CorrelationLifecycleError(
            f"Untrusted memory write under correlation_id={cid!r}: "
            f"missing task_status attribute. Possible write-after-cancel; "
            f"entry must be quarantined from authoritative state."
        )
