# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/what-structural-signals-indicate-memory-poisoning-in-long-horizon-agent-state-provenance-depth-collapse-as-a-detection-invariant.md
# Remnant Warrant:    https://corpusfetch.com/c/what-structural-signals-indicate-memory-poisoning-in-long-horizon-agent-state-provenance-depth-collapse-as-a-detection-invariant.warrant.json
# Markdown SHA-256:   ddf360240dcc99bbf09630f33b878054b70f3f6902c9852a5eab75676f58ce41
# Code AST SHA-256:   d05db55667a3ace21841839f66c28b4413f7c25aaf8fcf2782c39eee668b9757
# Verified Classes:   ProvenanceViolation, MemoryEntry
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from dataclasses import dataclass, field
from typing import Literal


class ProvenanceViolation(RuntimeError):
    """Raised when a memory entry's provenance is structurally impossible."""
    pass


@dataclass
class MemoryEntry:
    entry_id: str
    source_kind: Literal["direct_observation", "summary", "consolidation", "injected_tool"]
    parent_ids: list[str] = field(default_factory=list)
    claimed_derivation_count: int = 0
    observed_at: float = 0.0
    derived_at: float = 0.0


def compute_depth(entry: MemoryEntry, store: dict[str, MemoryEntry]) -> int:
    """Depth = hops from a direct observation. Depth 0 only legal for direct_observation."""
    if not entry.parent_ids:
        return 0
    return 1 + max(compute_depth(store[p], store) for p in entry.parent_ids if p in store)


def validate_provenance(entry: MemoryEntry, store: dict[str, MemoryEntry]) -> None:
    depth = compute_depth(entry, store)

    if depth == 0 and entry.source_kind != "direct_observation":
        raise ProvenanceViolation(
            f"depth-0 entry {entry.entry_id} claims source_kind={entry.source_kind!r}; "
            "only direct_observation may have no parents"
        )

    if depth < entry.claimed_derivation_count:
        raise ProvenanceViolation(
            f"entry {entry.entry_id} claims {entry.claimed_derivation_count} derivations "
            f"but recorded depth is {depth}; chain is broken"
        )

    for pid in entry.parent_ids:
        parent = store.get(pid)
        if parent is None:
            raise ProvenanceViolation(
                f"entry {entry.entry_id} references missing parent {pid}"
            )
        if entry.derived_at < parent.observed_at:
            raise ProvenanceViolation(
                f"timestamp inversion: {entry.entry_id} derived before parent {pid} observed"
            )


def admit(entry: MemoryEntry, store: dict[str, MemoryEntry]) -> bool:
    try:
        validate_provenance(entry, store)
        return True
    except ProvenanceViolation:
        return False  # quarantine; do not serve to agent
