Skip to content

Commit 2179b31

Browse files
committed
Fix: tombstone is not reversible; stop claiming otherwise (CL-6288)
tombstoneDocument overwrites chunk.text with '[redacted]' — there is no history/audit table and no un-tombstone verb, so the original content does not survive a forget request; only version metadata does. The route summary and docs/RETENTION.md previously said "reversible in principle," which would lead a host to build an "undo forget" button with nothing to undo to. Describe what forget actually does (stops appearing in search, content redacted, row kept for audit) instead. purge remains genuinely irreversible (the row itself is removed) — that claim was already correct. Drive-by: docs/RETENTION.md said the ephemeral sweeper "hard-deletes" past valid_until; sweepEphemeral only sets status=deprecated and never deletes. Pre-existing inaccuracy, fixed while already editing this file.
1 parent 3280931 commit 2179b31

2 files changed

Lines changed: 15 additions & 8 deletions

File tree

‎docs/RETENTION.md‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Versions carry a **retention class** orthogonal to temporal ranking class
77
| --- | --- |
88
| `durable` | Long-lived claims; hard-delete blocked until tombstoned |
99
| `standard` | Default working memory |
10-
| `ephemeral` | Short TTL; sweeper hard-deletes past `valid_until` (or 7d from `ingested_at`) |
10+
| `ephemeral` | Short TTL; sweeper deprecates past `valid_until` (or 7d from `ingested_at`) — hard delete is a separate explicit step |
1111
| `source_only` | Keep raw capture; derived versions may be dropped by host policy |
1212

1313
Schema: `memory.version.retention_class` (migration `0007_retention.sql`).
@@ -21,7 +21,7 @@ CHECK constraint `version_retention_class_check` stays lockstep with
2121
| Deprecate | `memory.deprecateVersion` | `status=deprecated`, `deprecated_at` / reason |
2222
| Tombstone | `memory.tombstoneDocument` | All active/deprecated/superseded versions → `tombstoned`; chunk text redacted to `[redacted]` |
2323
| Hard delete | `memory.hardDeleteDocument` | Deletes document row (cascade); **refuses** if any non-tombstoned version is `durable` |
24-
| Sweep | `memory.sweepEphemeral` | Auto-deprecates ephemeral versions past `valid_until` (or 7d from `ingested_at`); host schedules, core is cron-free |
24+
| Sweep | `memory.sweepEphemeral` | Auto-**deprecates** (never deletes) ephemeral versions past `valid_until` (or 7d from `ingested_at`); host schedules, core is cron-free |
2525
| Set class | `memory.setRetentionClass` | Update `retention_class` on a version |
2626

2727
Search and feed exclude non-active (and non-superseded for feed) rows by
@@ -43,11 +43,18 @@ Service module: `src/services/retention.ts`.
4343

4444
**Tombstone vs. hard delete stay distinct verbs, distinct grants.** A UI
4545
offering "forget this" must never be one flag away from "shred this" by
46-
accident. `forget` (tombstone) is the reversible-in-principle, audit-keeping
47-
action; `purge` (hard delete) is the one that actually removes the row, has
48-
its own grant action, and is refused outright while a `durable`-class version
49-
on the document is untombstoned. A host can grant `forget` broadly (every
50-
user gets a "forget this" button) while keeping `purge` to an operator role.
46+
accident. `forget` (tombstone) is **not** an undo-able action: the document
47+
stops appearing in search/feed and its chunk text is overwritten with
48+
`[redacted]` — the original content does not survive, there is no
49+
un-tombstone/restore verb, and only version metadata (status, timestamps,
50+
retention class) remains for audit. `purge` (hard delete) goes further and
51+
removes the document row itself; it has its own grant action and is refused
52+
outright while a `durable`-class version on the document is untombstoned. The
53+
distinction that matters is *what's still queryable*: after `forget` a
54+
document row and its metadata still exist (for audit) but its content is
55+
gone; after `purge` nothing does. A host can grant `forget` broadly (every
56+
user gets a "forget this" button) while keeping `purge` to an operator role —
57+
but should not describe `forget` to end users as reversible.
5158

5259
**Ownership, not just visibility.** `memory:search`/a document's `accessTags`
5360
say who can *see* a document — never who may forget or purge it. Every

‎src/routes/retention.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function mountForgetRoute(app: Hono<TenantEnv>, deps: RouteDeps): void {
6363

6464
describeRoute({
6565
tags: ["memory"],
66-
summary: "Tombstone a document (reversible in principle: row stays for audit, chunk text redacted)",
66+
summary: "Tombstone a document — stops appearing in search, chunk text is redacted (not archived), version rows stay for audit",
6767
responses: {
6868
200: {
6969
description: "Tombstoned",

0 commit comments

Comments
 (0)