@@ -30,3 +30,39 @@ versions intentionally (ops / audit). Hard-delete is a separate explicit
3030verb — TTL never hard-deletes.
3131
3232Service module: ` src/services/retention.ts ` .
33+
34+ ## HTTP surface (CL-6288)
35+
36+ | Route | Grant action | Plane verb |
37+ | --- | --- | --- |
38+ | ` POST …/memory/documents/:documentId/forget ` | ` memory:forget ` | ` tombstoneDocument ` |
39+ | ` POST …/memory/documents/:documentId/purge ` | ` memory:purge ` | ` hardDeleteDocument ` |
40+ | ` POST …/memory/versions/:versionId/retention-class ` | ` memory:forget ` | ` setRetentionClass ` |
41+
42+ ` deprecateVersion ` and ` sweepEphemeral ` have no route (see below).
43+
44+ ** Tombstone vs. hard delete stay distinct verbs, distinct grants.** A UI
45+ 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.
51+
52+ ** Ownership, not just visibility.** ` memory:search ` /a document's ` accessTags `
53+ say who can * see* a document — never who may forget or purge it. Every
54+ retention route additionally checks that the caller is the document's
55+ creator (` created_by_principal_id ` — the document's first version for
56+ ` forget ` /` purge ` , the specific version's own creator for ` retention-class ` ),
57+ independent of any share grant. A peer who can search a shared document gets
58+ 403 on ` forget ` /` purge ` /` retention-class ` for it. See
59+ ` src/services/retention-ownership.ts ` and the ownership tests in
60+ ` src/memory.test.ts ` / ` src/routes/routes.test.ts ` .
61+
62+ ** ` sweepEphemeral ` stays off the HTTP surface.** It is a maintenance sweep —
63+ "deprecate every ephemeral version past its TTL for this tenant" — not
64+ something a single user requests about their own data, and it has no natural
65+ per-caller grant (it does not take a ` principalId ` and touches every
66+ matching row tenant-wide). A host that wants it schedules a cron job calling
67+ ` memory.sweepEphemeral({ tenantId }) ` in-process (the returned ` Memory `
68+ already exposes it); the engine stays cron-free per ` ARCHITECTURE.md ` .
0 commit comments