# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-a-memory-tool-reject-a-write-that-references-a-forgotten-scope.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-a-memory-tool-reject-a-write-that-references-a-forgotten-scope.warrant.json
# Markdown SHA-256:   1a4c3475839fbbb22d6ad15f6a9618997dc20ec189724d1c5a84abb98354144e
# Code AST SHA-256:   8d9d572dafa97802131f9d31ebc2993173d9aef72fbab2837f8ca366fe3aee1d
# Verified Classes:   ScopeForgottenError, KeyVersionDestroyedError, KeyVersion, KeyRegistry
# Defensive Guards:   3 fail-closed invariants (zero external deps)

import time
from dataclasses import dataclass


class ScopeForgottenError(RuntimeError):
    """Scope tombstoned and no re-key; write must not proceed."""


class KeyVersionDestroyedError(RuntimeError):
    """Caller supplied or resolved a key version whose key was destroyed."""


@dataclass(frozen=True)
class KeyVersion:
    scope: str
    version: int
    destroyed_at: float | None  # None = live


class KeyRegistry:
    def __init__(self):
        self._versions: dict[tuple[str, int], KeyVersion] = {}

    def resolve_live(self, scope: str) -> KeyVersion | None:
        # Authoritative, linearizable read in production.
        live = [v for (s, _), v in self._versions.items()
                if s == scope and v.destroyed_at is None]
        return max(live, key=lambda v: v.version) if live else None

    def is_destroyed(self, scope: str, version: int) -> bool:
        kv = self._versions.get((scope, version))
        return kv is not None and kv.destroyed_at is not None


def write_memory(registry: KeyRegistry, scope: str, payload: bytes,
                 explicit_version: int | None = None) -> dict:
    if explicit_version is not None:
        if registry.is_destroyed(scope, explicit_version):
            raise KeyVersionDestroyedError(
                f"scope={scope} version={explicit_version} key destroyed; "
                f"refusing to encrypt under a dead key"
            )
        kv = registry._versions.get((scope, explicit_version))
        if kv is None or kv.destroyed_at is not None:
            raise KeyVersionDestroyedError(f"no live key version {explicit_version}")
    else:
        kv = registry.resolve_live(scope)
        if kv is None:
            raise ScopeForgottenError(
                f"scope={scope} has no live key version; scope forgotten or never created"
            )
    return {
        "scope": scope,
        "key_version": kv.version,
        "write_ts": time.time(),
        "ciphertext": b"enc:" + payload,  # placeholder
    }
