GDPR Art. 17 · applied since 25 May 2018
Every memory library has a delete call. Of the nine we read, none leaves a signed record that a named record was erased, under which request, and that it is gone. inspeximus writes each record with the subject it belongs to, erases by subject including what declares it as lineage, and issues a certificate a reader verifies with the public key alone. The transcript on this page is a real run; ids and hashes change per run, nothing else does.
the run, 2026-09-17, inspeximus 2.40.0
Three records, two of them Alice's. The request DSAR-17 arrives. First the dry run, then the erasure, then the certificate, then a verifier holding only the public key.
$ inspeximus writer-key --new --out key.txt
secret written to key.txt (0600)
public key: 90db3402bdba911466a781f830a5c851bc1fa1f350585e777c2a56551286b5d5
point the server at it: INSPEXIMUS_WRITER_KEY_FILE=key.txt
pin this writer as trusted with trust_seeds={'key:90db3402bdba911466a781f830a5c851bc1fa1f350585e777c2a56551286b5d5'}
KEEP THE SECRET OUT OF GIT. It attests AUTHORSHIP, not truth.
$ inspeximus --path mem.json --receipts --receipt-key-file key.txt remember 'Alice prefers email' --source crm/alice
remembered 029e96ab3b
$ inspeximus --path mem.json --receipts --receipt-key-file key.txt remember 'Alice phone is +100' --key alice::phone --source crm/alice
remembered a917d4a167 [key=alice::phone]
$ inspeximus --path mem.json --receipts --receipt-key-file key.txt remember 'Bob phone is +300' --key bob::phone --source crm/bob
remembered 8e5e75f8c6 [key=bob::phone]
$ inspeximus --path mem.json --receipts --receipt-key-file key.txt forget-subject crm/alice --request-id DSAR-17 --dry-run
would erase 2 record(s): 2 naming the subject, 0 reached through lineage
[direct ] 029e96ab3b Alice prefers email
[direct ] a917d4a167 Alice phone is +100
$ inspeximus --path mem.json --receipts --receipt-key-file key.txt forget-subject crm/alice --request-id DSAR-17 --basis 'Art. 17(1)(a)'
erased 2 record(s), 2 tombstone(s)
$ inspeximus --path mem.json --receipts --receipt-key-file key.txt erasure-certificate --request-id DSAR-17 --out DSAR-17.cert.json
wrote erasure certificate -> DSAR-17.cert.json (2 erasure(s) attested, scoped to DSAR-17)
$ inspeximus erasure-verify DSAR-17.cert.json --store mem.json --expected-pubkey 90db3402bdba911466a781f830a5c851bc1fa1f350585e777c2a56551286b5d5
OK chain_intact
OK signatures_valid
OK signed
OK anchor_matches_tip
OK anchor_consistent
n/a anchor_witnessed
OK summary_derivable
OK attests_an_erasure
OK store_absent
OK store_bound
OK scope_intact
NOTE NOT WITNESSED: no --expected-anchor, so the anchor was checked against itself only; a chain trimmed and re-anchored by the key holder passes. Pin an anchor you obtained outside the operator's control.
VERDICT: PASS (2 erasure(s) attested, absence checked)
$ inspeximus --path mem.json list
- Bob phone is +300 [key=bob::phone]
Bob is untouched. Alice's two records are gone from the store's live rows, two signed tombstones say when and under which request, and the verifier pinned the writer's public key from the writer-key output. The NOTE line is part of the run: nothing on this page witnessed the anchor, and the verifier says so rather than passing quietly.
A reader checks it with the public key alone. Against a party who does not hold the signing
key, the signatures and the chain prove the tombstones were not altered, the anchor describes the
chain in every field (count, RFC 6962 root, tip), and, when the verifier is handed the store,
that the ids in the chain are absent from that store's live rows and that the store is the one
the certificate was issued from. Against the operator, who holds the key, the certificate proves
consistency, not history: an operator can re-sign a rewritten chain. Pass
--expected-pubkey with a key you obtained out of band, and
--expected-anchor with an anchor witnessed outside the operator's control (a
co-signature, a timestamp, your own earlier copy); a chain trimmed after it was witnessed then
fails whoever holds the key.
It does not prove that no copy exists anywhere. Vector indexes, prompt logs, model weights and
backups are outside the store, and the certificate names that scope in its own text. The write
receipts beside the store keep a hash of each record's text and key; since 2.40.0 that hash
includes a nonce that lives only in the record, so a receipt that outlives an erased record
confirms no guessed text. Receipts written before 2.40.0 carry the unsalted hash, and
mem.json.receipts.json stays personal data for those records. A record whose
writer never declared
derived_from carries no lineage, so the audit reports UNAUDITED rather
than a pass, because a party that stops declaring lineage would otherwise always look clean.
Access and rectification travel the same path: export_subject() returns the copy
of the records an erasure would remove, with provenance, for an Art. 15(3) request, and
rectify() supersedes the value under its key and, when an action ledger is
attached, records the actor and the reason in a rights entry (Art. 16). The
EU AI Act and GDPR page maps every article to the call that
answers it and marks where the mapping stops.
Of the nine memory products whose documentation we read in July 2026 for the
evidence page, none documents an erasure that leaves a signed
tombstone and a certificate a third party can check. They document a plain
delete() or forget(). Outside those nine, Lethe (Apache-2.0, pgvector
and Pinecone connectors) issues an Ed25519-signed deletion certificate with an absence re-query,
and Deletable sells a crypto-erasure certificate; neither writes the subject into the record at
write time, follows declared lineage, or leaves a tombstone. That is a reading of their
documentation on those dates, not a proof of a universal negative. If a product added it since,
open an issue and the table changes.
pip install "inspeximus[crypto]" inspeximus writer-key --new --out key.txt inspeximus --path mem.json --receipts --receipt-key-file key.txt remember "..." --source <subject> inspeximus --path mem.json --receipts --receipt-key-file key.txt forget-subject <subject> --request-id <DSAR id> --dry-run inspeximus --path mem.json --receipts --receipt-key-file key.txt forget-subject <subject> --request-id <DSAR id> inspeximus --path mem.json --receipts --receipt-key-file key.txt erasure-certificate --request-id <DSAR id> --out cert.json inspeximus erasure-verify cert.json --store mem.json --expected-pubkey <the writer's public key>
The store has no dependencies; signing and signature
checks need the [crypto] extra. The verifier is the same package, so anyone with the
certificate, the store and the public key can run it. From Python the calls are
remember(source=...), forget_subject(), erasure_certificate()
and verify_erasure_certificate(). The MCP server exposes the same four to Claude Code,
Cursor and any other client; see Claude Code, one line.