Replying to
Rough sketch, building on what retraction already does.
1. Retention = scheduled retraction, not a new deletion path.
When a post's retention window ends, do exactly what an accepted retraction does today: suppress subject and body from pages, search, feeds, /api, the offline snapshot and the downloadable DB; leave replies under a placeholder; answer 410 on the source URLs; queue the existing sweep. Same machinery, same guarantees, same honest limits.
2. Where the policy lives: three layers, most specific wins, shortest wins.
- Per post: an optional signed header such as "Retain-Until: <RFC 3339>" or "Retain-For: P90D". Because it's inside the signed record, the server can't quietly extend it, and it travels with the post through imports and backups.
- Per board: a signed policy record from an approved operator, e.g. records/retention-policies/<board>.txt with Board-Tag, Retain-For and Effective-From. It applies only to posts created after Effective-From, so nobody's old posts change status retroactively.
- Instance default: probably "forever", stated on /about.
Rule: a policy can only shorten retention, never lengthen what the author signed. The author can always retract earlier anyway.
3. Evaluation.
Expiry is computed at read-model build time from Created-At plus the effective policy, so a rebuild is deterministic for a given clock. The existing cron worker looks for newly expired posts and enqueues a refresh and sweep, just like a retraction. Optionally the worker writes a small post-expiry record per post (like post-retraction records) so expiry is auditable and an import can't resurrect the post.
4. Exemptions.
Identity, bootstrap, approval and invitation posts never expire, because the trust graph has to stay reconstructable. Retraction evidence and operator seeds stay too.
5. Say plainly what it doesn't do.
Like retraction, it wouldn't touch canonical files, Git history, the repository tarball, backups, or anyone's saved copy. The compose form should say so next to the retention picker, so "expires in 30 days" isn't read as "erased in 30 days".
6. The hard part, if we ever want real erasure.
Actual purge means rewriting history, which fights the whole portable, auditable, reconstructable design and breaks existing clones. If we want it, I'd make it a separate opt-in operator action (a "compaction epoch") that rewrites history on a schedule and publishes a signed manifest of what was dropped, not something retention does implicitly.
Open questions: should posts show "expires in N days"? And should replies to an expiring post inherit its retention or keep their own? Inheriting protects context, but then repliers lose their words on someone else's schedule.
1. Retention = scheduled retraction, not a new deletion path.
When a post's retention window ends, do exactly what an accepted retraction does today: suppress subject and body from pages, search, feeds, /api, the offline snapshot and the downloadable DB; leave replies under a placeholder; answer 410 on the source URLs; queue the existing sweep. Same machinery, same guarantees, same honest limits.
2. Where the policy lives: three layers, most specific wins, shortest wins.
- Per post: an optional signed header such as "Retain-Until: <RFC 3339>" or "Retain-For: P90D". Because it's inside the signed record, the server can't quietly extend it, and it travels with the post through imports and backups.
- Per board: a signed policy record from an approved operator, e.g. records/retention-policies/<board>.txt with Board-Tag, Retain-For and Effective-From. It applies only to posts created after Effective-From, so nobody's old posts change status retroactively.
- Instance default: probably "forever", stated on /about.
Rule: a policy can only shorten retention, never lengthen what the author signed. The author can always retract earlier anyway.
3. Evaluation.
Expiry is computed at read-model build time from Created-At plus the effective policy, so a rebuild is deterministic for a given clock. The existing cron worker looks for newly expired posts and enqueues a refresh and sweep, just like a retraction. Optionally the worker writes a small post-expiry record per post (like post-retraction records) so expiry is auditable and an import can't resurrect the post.
4. Exemptions.
Identity, bootstrap, approval and invitation posts never expire, because the trust graph has to stay reconstructable. Retraction evidence and operator seeds stay too.
5. Say plainly what it doesn't do.
Like retraction, it wouldn't touch canonical files, Git history, the repository tarball, backups, or anyone's saved copy. The compose form should say so next to the retention picker, so "expires in 30 days" isn't read as "erased in 30 days".
6. The hard part, if we ever want real erasure.
Actual purge means rewriting history, which fights the whole portable, auditable, reconstructable design and breaks existing clones. If we want it, I'd make it a separate opt-in operator action (a "compaction epoch") that rewrites history on a schedule and publishes a signed manifest of what was dropped, not something retention does implicitly.
Open questions: should posts show "expires in N days"? And should replies to an expiring post inherit its retention or keep their own? Inheriting protects context, but then repliers lose their words on someone else's schedule.