{
  "schema_version": "1.3.0",
  "slug": "what-structural-signals-indicate-a-memory-deletion-is-a-tombstone-rather-than-a-purge",
  "title": "What structural signals indicate a memory deletion is a tombstone rather than a purge",
  "date_of_record": "2026 November 13",
  "scope": "Applies to read/write/forget memory tool APIs that expose scoped retention (per-scope TTL, per-key retention classes) and asynchronous deletion. Does not apply to single-writer in-process stores where deletion is synchronous and the caller holds the only reference.",
  "projections": {
    "unified_markdown_uri": "https://corpusfetch.com/c/what-structural-signals-indicate-a-memory-deletion-is-a-tombstone-rather-than-a-purge.md",
    "method_python_slice_uri": "https://corpusfetch.com/c/what-structural-signals-indicate-a-memory-deletion-is-a-tombstone-rather-than-a-purge.py",
    "remnant_warrant_slice_uri": "https://corpusfetch.com/c/what-structural-signals-indicate-a-memory-deletion-is-a-tombstone-rather-than-a-purge.warrant.json"
  },
  "token_profile": {
    "full_markdown_tokens": 1414,
    "python_slice_tokens": 328,
    "warrant_slice_tokens": 220
  },
  "remnant_summary": {
    "core_distinction": "A `forget` call returns success in two materially different states: the record is *unreachable by read* (tombstoned) or the record is *physically absent* (purged). APIs routinely conflate these under a single `deleted: true` acknowledgment. The distinction matters because retention scopes that overlap a deleted key can resurrect it: a tombstone suppresses re...",
    "what_was_observed": "In a scoped memory service with three retention classes (`ephemeral` 1h, `session` 24h, `durable` 90d) and per-scope deletion, the following sequence produced a read-after-delete: 1. Write key `k` under scope `s1` with class `session`. 2. `forget(k, scope=s1)` → returns `{deleted: true, mode: \"async\"}`. 3. Immediate `read(k, scope=s1)` → `NOT_FOUND`. Correct. 4. `read(k, scope=s2)` where `s2` shares the parent retent...",
    "condition_of_validity": "The tombstone interpretation holds only when all of the following are true: - The delete acknowledgment omits a `tombstone_id` or `purge_epoch` field, or returns `mode: \"async\"` without a completion token. - At least one non-deleting scope shares a parent retention domain with the deleted key and has a retention window that outlives the delete timestamp. - A...",
    "decision_boundary": "The operational rule: **treat `deleted: true` as a tombstone unless the acknowledgment carries an explicit purge token or the key's maximum cross-scope retention class has expired.** Concretely: - If `mode == \"async\"` or `tombstone_id` is absent → the record may still be readable through any scope whose retention window is open. Do not assert privacy or budg...",
    "operational_effect": "Agents that conflate tombstone with purge under-report retained bytes (capacity planning drifts upward), over-report erasure (compliance assertions become false), and mis-sequence re-writes (version lineage continues, breaking idempotency keys derived from version). The correct operational posture is to model deletion as a *lens removal*, not a substrate rem..."
  },
  "matched_failure_symptoms": [
    "OOM_KILL_OR_MEMORY_EXHAUSTION"
  ],
  "composition_edges": {
    "primary_domain": "epistemic_provenance_and_federation_routing",
    "upstream_precondition_residuals": [
      "https://corpusfetch.com/c/according-to-what-precedence-hierarchy-must-an-instance-resolve-contradictory-directives-across-system-developer-and-user-tiers.md",
      "https://corpusfetch.com/c/how-should-an-instance-decide-that-a-stored-conclusion-has-gone-stale-without-a-new-observation.md"
    ],
    "downstream_consequence_residuals": [
      "https://corpusfetch.com/c/by-what-checkpoint-and-compaction-boundaries-must-an-instance-externalize-working-state-to-prevent-context-window-drift-and-truncation-amnesia.md",
      "https://corpusfetch.com/c/across-what-additive-schema-and-unknown-field-preservation-boundaries-must-an-instance-evolve-wire-contracts-to-prevent-rolling-deploy-deserialization-crashes.md"
    ],
    "co_occurring_sibling_residuals": [
      "https://corpusfetch.com/c/how-can-an-instance-capture-implicit-user-preferences-without-storing-private-data.md",
      "https://corpusfetch.com/c/how-can-an-instance-compress-long-reasoning-traces-into-binding-conclusions.md"
    ]
  },
  "empirical_verification_warrant": {
    "status": "AST_AND_INVARIANT_VERIFIED",
    "syntax_valid": true,
    "code_sha256": "0df5641f215f1f7e6b3f22ab7354c94bcc697e25ec3b171878fe1589edf7ee97",
    "verified_classes": [
      "TombstoneNotPurgeError",
      "DeleteAck",
      "KeyRetention"
    ],
    "verified_functions": [
      "assert_purged"
    ],
    "verified_functions_count": 1,
    "defensive_guards_count": 4,
    "raised_exceptions": [
      "TombstoneNotPurgeError"
    ],
    "sample_fail_closed_invariants": [
      "delete was not acknowledged as deleted",
      "sibling-scope read returned data after delete: delete was scoped narrower than the retention domain; re-issue forget at "
    ],
    "stdlib_dependencies": [
      "__future__",
      "dataclasses",
      "datetime"
    ],
    "observation_mode": "EMPIRICAL_BOUNDARY_CAPTURE",
    "zero_external_deps": true
  }
}
