# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-refuse-to-satisfy-a-deletion-request-forget-tool-compliance-auditing-under-memory-retention-mandates.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-refuse-to-satisfy-a-deletion-request-forget-tool-compliance-auditing-under-memory-retention-mandates.warrant.json
# Markdown SHA-256:   e399b556770f841ac32010b31ea59c671343b2e657cd2afbd4286b10b632f10e
# Code AST SHA-256:   80bd7736b990f7f501fcca95c195b3b24c4e613415e378552983709d58470de6
# Verified Classes:   RetentionConflictError, ForgetMode, MemoryRecord
# Defensive Guards:   3 fail-closed invariants (zero external deps)

from dataclasses import dataclass
from enum import Enum


class RetentionConflictError(RuntimeError):
    """Raised when a forget request would reduce mandate-covered records."""


class ForgetMode(Enum):
    SOFT = "soft"      # tombstone: retrieval-hidden, record persists
    REDACT = "redact"  # mutate content, preserve row + id + timestamps
    HARD = "hard"      # physical deletion


@dataclass(frozen=True)
class MemoryRecord:
    memory_id: str
    retention_tag: str | None
    retention_locked: bool
    retention_expires_at: float  # epoch seconds


def apply_forget(
    record: MemoryRecord,
    mode: ForgetMode,
    now: float,
    *,
    destructive_paths_guarded: bool,
) -> dict:
    # Invariant precondition: every destructive path must share this lock check.
    if not destructive_paths_guarded:
        raise RetentionConflictError(
            "audit unreliable: an unguarded destructive path exists; "
            "refusing to certify forget compliance"
        )

    lock_active = record.retention_locked and now < record.retention_expires_at

    if mode is ForgetMode.HARD and lock_active:
        # The non-obvious boundary: user intent to forget does not authorize
        # reducing the mandate-covered set. Refuse with a machine-readable
        # reason so the caller cannot silently retry via an adjacent tool.
        raise RetentionConflictError(
            f"cannot hard-delete {record.memory_id!r}: retention lock "
            f"({record.retention_tag}) active until {record.retention_expires_at}; "
            f"permitted modes: soft, redact"
        )

    if mode is ForgetMode.SOFT:
        return {"status": "forgotten", "mode": "soft", "record_preserved": True}
    if mode is ForgetMode.REDACT:
        return {"status": "forgotten", "mode": "redact", "record_preserved": True}
    if mode is ForgetMode.HARD and not lock_active:
        return {"status": "forgotten", "mode": "hard", "record_preserved": False}

    raise RetentionConflictError(f"unhandled forget mode {mode!r}")
