# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-distrust-a-forget-acknowledgement-before-it-can-safely-answer-recall-queries.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-distrust-a-forget-acknowledgement-before-it-can-safely-answer-recall-queries.warrant.json
# Markdown SHA-256:   f76ae95f89f545f183962ac0c0ef9cee32bbc417dc6846f2f23a612df81532f0
# Code AST SHA-256:   4bdfd94dfa60474d63d792367c95f93fe76290d400b02e5d49cbdd852603d7ca
# Verified Classes:   ErasureEpochNotSettled, ErasureState
# Defensive Guards:   2 fail-closed invariants (zero external deps)

from dataclasses import dataclass


class ErasureEpochNotSettled(RuntimeError):
    """Raised when recall is attempted in a scope whose erasure has been
    acknowledged but whose read-path index has not caught up to the
    erasure epoch. The acknowledgement is a policy fact, not a read fact."""


@dataclass(frozen=True)
class ErasureState:
    subject: str
    policy_erased: bool          # forget() returned success
    erasure_epoch: int           # epoch assigned at forget time
    read_epoch: int              # epoch the read path has compacted to


def recall(memory_tool, query: str, scope: dict, erasures: dict[str, ErasureState]):
    subject = scope.get("subject")
    if subject and subject in erasures:
        st = erasures[subject]
        if st.policy_erased and st.read_epoch < st.erasure_epoch:
            raise ErasureEpochNotSettled(
                f"recall in scope subject={subject!r} refused: erasure_epoch="
                f"{st.erasure_epoch} > read_epoch={st.read_epoch}; the forget "
                f"acknowledgement is not yet a read-path guarantee"
            )
    return memory_tool.recall(query, scope=scope)


def forget_and_record(memory_tool, subject: str, erasures: dict[str, ErasureState]):
    ack = memory_tool.forget(subject=subject)
    if not ack.get("ok"):
        raise ErasureEpochNotSettled(f"forget for {subject!r} did not commit")
    erasures[subject] = ErasureState(
        subject=subject,
        policy_erased=True,
        erasure_epoch=ack["erasure_epoch"],
        read_epoch=memory_tool.read_epoch(scope={"subject": subject}),
    )
    return ack
