Skip to content

privacy: forget() deletes one row — raw artifacts are never deleted and wiki claims are only SET NULL, so PRIVACY.md overstates deletion #366

Description

@cdeust

PRIVACY.md makes two unqualified deletion claims to users:

  • line 43: "The forget tool deletes individual memories."
  • line 90: "forget — delete a specific memory."

Verified behaviour on main at 65395fb (2026-08-06) does not support those claims without qualification.

What forget actually does

mcp_server/handlers/forget.py:115 — a single call:

deleted = store.delete_memory(memory_id)

mcp_server/infrastructure/pg_store.py:995-996:

def delete_memory(self, memory_id: int) -> bool:
    cur = self._execute("DELETE FROM memories WHERE id = %s", (memory_id,))

forget.py contains no artifact, wiki, or embedding handling (grep for artifact|wiki|embedding returns only the store constructor at line 75).

What survives

1. The raw full text, on disk, forever. core/gist_extraction.py stores a gist in the memory row and puts the full raw output in a content-addressed artifact — infrastructure/artifact_store.py, at ~/.claude/methodology/artifacts/<yyyy-mm>/<sha256[:16]>.md. Grepping artifact_store.py for unlink|remove|delete returns nothing: no code path in the module deletes an artifact. So forgetting a gist-and-pointer memory removes the pointer and leaves the complete original content readable on the filesystem.

2. Wiki claims, deliberately. infrastructure/pg_schema.py:221:

memory_id       INTEGER REFERENCES memories(id) ON DELETE SET NULL,

wiki.claim_events rows derived from a memory survive forget with the back-pointer nulled. The claim text — which can carry the same content — persists and stays searchable, now unattributable.

For contrast, pg_schema.py:196-197 does cascade memory_entities, so the gap is specific rather than total; entity links are handled, durable content is not.

Why this matters more than a normal bug

The survey at arXiv 2604.16548 calls this the frontier case, stating "cross-substrate deletion is the frontier" and "forgetting is the strongest test of mnemonic sovereignty," with the forget-phase attack class being "residual toxins persisting after deletion attempts." Independently of any attacker, this is a published privacy policy that describes behaviour the code does not implement — the user-facing controls section of PRIVACY.md is what a user relies on to exercise deletion.

Ask

Either make deletion cross-substrate, or correct PRIVACY.md — the two must agree. Cross-substrate is the right answer for a control the policy advertises.

  1. forget deletes the memory row, its artifact(s), and its derived wiki claims — or explicitly reports what it could not delete, in the tool result.
  2. Decide the ON DELETE SET NULL semantics on wiki.claim_events.memory_id deliberately: if an orphaned claim is intended (it may be, for wiki integrity), then PRIVACY.md must say so and forget must tell the user at call time.
  3. Artifacts need a reachable lifecycle regardless of this issue — a store that only ever grows and is never pruned is also a §13 C2 resource-lifecycle gap.

Acceptance criteria

  • A test stores an oversized memory (forcing the gist+artifact path), forgets it, and asserts the artifact file is gone from disk — not merely that the row is gone.
  • A test covers a memory with a derived wiki claim and asserts the chosen behaviour explicitly, whichever way it is decided.
  • Partial-failure behaviour defined and tested (§13 A5): artifact unlink fails → what state remains, and what signal is emitted (asserted, §13 F1).
  • Both backends (§12.3).
  • PRIVACY.md and docs/mcp-tools.md updated in the same PR to match the shipped behaviour (§13 H4). If any content is intentionally retained after forget, PRIVACY.md says so in the "Your controls" section.
  • Zero surviving non-equivalent mutants on changed files (§12).

Relates to #365 (poisoning — remediation requires working deletion), #360.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions