Feature: Message Retention #30
Labels
No labels
area:api
area:core
area:docs
area:infra
area:ux
dependencies
documentation
duplicate
good first issue
help wanted
invalid
question
rust
status:complete
status:partial
status:planned
type:bug
type:design
type:feature
type:infra
type:refactor
type:research
type:ux
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
icub3d/decentcom#30
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Migrated from GitHub issue icub3d/decentcom#30
Original Author: @icub3d
Original Date: 2026-04-15T14:16:00Z
Feature: Message Retention
Overview
Message retention allows server operators to configure automatic cleanup policies for messages and media. Policies define how long messages are kept (forever, a number of days, or a maximum count per channel). A background purge job periodically identifies and removes expired messages and orphaned media files, freeing storage space.
Background
The storage design doc (
docs/design/storage.md) defines four retention policies:forever,days: N,count: N, andmanual. Messages are soft-deleted first, then a background job purges soft-deleted records and orphaned media on a configurable schedule. The server-model doc (docs/design/server-model.md) listsretention_policyas a content policy configuration option. Media files are content-addressable (stored by hash), so media cleanup must check that no remaining message references a blob before deleting it.Depends on:
storage(feature #3),messages(feature #8),file-uploads(feature #17), all Phase 1 and Phase 2 features.Requirements
forever(no automatic deletion),days: N(delete messages older than N days),count: N(keep only the N most recent messages per channel)Design
API / Interface Changes
REST endpoints:
/api/v1/admin/retention/api/v1/admin/retention/api/v1/channels/{channel_id}/retention/api/v1/channels/{channel_id}/retentionRequest body for PUT:
Or:
Server config (TOML):
Data Model Changes
Modified
messagestable:deleted_atThis column may already exist from the messages feature. If not, add it.
New table:
channel_retention_overrideschannel_idpolicyforever,days, orcountvaluegrace_period_daysModified
mediatable:orphaned_atComponent Changes
Server (
server/):server/src/retention/— new module directoryserver/src/retention/mod.rs— retention manager: starts the background job, provides methods for immediate purgeserver/src/retention/policy.rs—RetentionPolicyenum and configuration parsingserver/src/retention/purge.rs— purge logic: identify expired messages by policy, soft-delete them, hard-delete after grace period, clean orphaned mediaserver/src/retention/scheduler.rs— tokio interval-based scheduler for the background purge jobserver/src/storage/trait.rs— extendMessageStorewithsoft_delete_expired(policy, channel_id),hard_delete_old_soft_deleted(grace_period), andcount_messages(channel_id)methodsserver/src/storage/trait.rs— extendMediaStorewithmark_orphaned_media()anddelete_orphaned_media(grace_period)methodsserver/src/storage/sqlite/messages.rs— implement retention-related queries for SQLiteserver/src/storage/sqlite/media.rs— implement orphaned media detection and cleanup for SQLiteserver/src/storage/postgres/messages.rs— same for PostgreSQL (if available)server/src/storage/postgres/media.rs— same for PostgreSQL (if available)server/src/routes/admin.rs— add retention policy endpointsserver/src/routes/channels.rs— add channel retention override endpointsserver/src/config.rs— parse[retention]config sectionClient (
client/src/):client/src/components/admin/RetentionSettings.tsx— new component: global retention policy editor with policy type selector and value inputclient/src/components/channel/ChannelRetention.tsx— new component: per-channel retention override in channel settingsclient/src/hooks/useRetention.ts— new hook: fetch and update retention policiesTask List
Phase A: Retention Policy Model
server/src/retention/policy.rs—RetentionPolicyenum (Forever,Days(u32),Count(u32)) with serialization and config parsing[retention]section parsing toserver/src/config.rschannel_retention_overridestable migration for SQLite (and PostgreSQL if available)deleted_atcolumn to messages table if not already presentorphaned_atcolumn to media tablePhase B: Purge Logic
MessageStoretrait withsoft_delete_expired()andhard_delete_old_soft_deleted()methodssoft_delete_expired()for SQLite — fordayspolicy:WHERE created_at < NOW() - N days AND pinned = false AND deleted_at IS NULL; forcountpolicy: soft-delete all but the N most recent per channelhard_delete_old_soft_deleted()for SQLite —WHERE deleted_at < NOW() - grace_periodMediaStoretrait withmark_orphaned_media()anddelete_orphaned_media()methodsdelete_orphaned_media()for SQLite — delete the metadata record and the file/S3 objectserver/src/retention/purge.rs— orchestrates the full purge cycle: resolve effective policy per channel, soft-delete expired, hard-delete after grace, clean orphaned mediaPhase C: Background Scheduler
server/src/retention/scheduler.rs— tokiointervaltask that runs the purge cycle on the configured intervalserver/src/retention/mod.rs—RetentionManagerthat starts the scheduler and providestrigger_immediate_purge()RetentionManagerinto the server startup (spawn the background task)Phase D: Admin API
server/src/routes/admin.rsserver/src/routes/channels.rsPhase E: Client UI
client/src/hooks/useRetention.ts— fetch and update retention policiesclient/src/components/admin/RetentionSettings.tsx— global retention policy editorclient/src/components/channel/ChannelRetention.tsx— channel-specific retention override in channel settingsTest List
RetentionPolicyparsing from TOML config produces correct enum valuessoft_delete_expired()withdayspolicy soft-deletes messages older than N dayssoft_delete_expired()withcountpolicy soft-deletes all but the N most recent messages per channelsoft_delete_expired()does not soft-delete pinned messageshard_delete_old_soft_deleted()permanently deletes messages soft-deleted longer than the grace periodhard_delete_old_soft_deleted()does not delete messages soft-deleted within the grace periodmark_orphaned_media()correctly identifies media not referenced by any active messagedelete_orphaned_media()removes the metadata record and the backing file/objectdays: 1policy, advance time, run purge, verify messages are soft-deletedcount: 100policy on a channel with 200 messages, verify the oldest 100 are purged after the next cycleOpen Questions
manualpolicy where admins manage retention by hand. Is this justforeverwith the expectation that admins delete messages manually, or does it need dedicated tooling?