# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-a-provenance-tracked-eviction-as-a-destructive-write-rather-than-a-cache-miss.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-treat-a-provenance-tracked-eviction-as-a-destructive-write-rather-than-a-cache-miss.warrant.json
# Markdown SHA-256:   816ac5906a9a882169beb0c0cf9af15d0bc69ed51f1a503b68415a6e4b9d0826
# Code AST SHA-256:   b9719ede95253b992335bbf641ba8d42c3989d1745563896f1082b6951522757
# Verified Classes:   ProvenanceEvictionError, Lineage
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass
from typing import Optional


class ProvenanceEvictionError(RuntimeError):
    """Raised when a read absence is an eviction, not a miss, and re-fetch is unsafe."""


@dataclass(frozen=True)
class Lineage:
    source: str
    supersedes: Optional[str] = None


def resolve_absent_read(
    key: str,
    tombstone_lineage: Optional[str],
    proposed_lineage: Optional[Lineage],
) -> Lineage:
    """
    Decide whether an absent read may be re-materialized.

    tombstone_lineage: lineage hash recorded by the store for an evicted key,
                       or None if the store has no tombstone for this key.
    proposed_lineage:  lineage the caller intends to write if re-fetching.

    Returns the lineage to write, or raises if re-fetch would resurrect
    forgotten material.
    """
    if tombstone_lineage is None:
        # No tombstone: absence is a genuine miss. Re-fetch is safe, but the
        # caller must supply provenance for the new write.
        if proposed_lineage is None:
            raise ProvenanceEvictionError(
                f"read({key!r}) returned absent with no tombstone and no "
                f"proposed lineage; cannot distinguish miss from eviction"
            )
        return proposed_lineage

    # Tombstone present: this absence is an eviction.
    if proposed_lineage is None:
        raise ProvenanceEvictionError(
            f"read({key!r}) is an eviction (tombstone={tombstone_lineage!r}); "
            f"re-fetch requires explicit provenance override"
        )

    if proposed_lineage.supersedes != tombstone_lineage:
        raise ProvenanceEvictionError(
            f"read({key!r}) is an eviction (tombstone={tombstone_lineage!r}); "
            f"proposed lineage {proposed_lineage!r} does not declare "
            f"supersedes={tombstone_lineage!r} -- refusing to resurrect"
        )

    # Explicit supersession: the new material has independent provenance that
    # acknowledges and replaces the forgotten lineage. Log this; it must be
    # greppable in the audit trail.
    return proposed_lineage


def safe_read_with_refetch(
    key: str,
    store,
    fetch,
    proposed_lineage: Optional[Lineage],
) -> object:
    value = store.read(key)
    if value is not None:
        return value
    lineage = resolve_absent_read(
        key=key,
        tombstone_lineage=store.tombstone(key),
        proposed_lineage=proposed_lineage,
    )
    fresh = fetch(key)
    store.write(key, fresh, lineage=lineage)
    return fresh
