2.1 KiB
Design: Leave & Stop-Forward (m9-leave-revocation)
Architecture
Backend-only. No UI changes. Touches 6 files.
Storage
Tag::CommunityLeave = 0x11 added to storage/keys.rs (after PurgeManifest = 0x10).
Key format: encode_key(EntityId::new(user_id), Tag::CommunityLeave, b"") — no suffix.
Value format: [status: 1 byte (0=Active, 1=Left)][left_at_ns: 8 bytes LE] — fixed 9 bytes.
Stored in the users storage partition via storage.users_engine().
Data Types (entities/community.rs)
Appended to existing CommunityMembershipIndex:
pub enum MembershipStatus { Active, Left }
pub struct CommunityMembership { user_id, status, left_at_ns: Option<u64> }
pub fn serialize_membership(m: &CommunityMembership) -> [u8; 9]
pub fn deserialize_membership(user_id: u64, bytes: &[u8]) -> Option<CommunityMembership>
TidalDb Field (db/mod.rs)
community_leave_status: dashmap::DashMap<u64, crate::entities::CommunityMembership>,
Added after community_membership. Initialized to DashMap::new() in both from_config and from_parts.
API (db/community.rs)
Three new public methods appended to the existing TidalDb impl:
leave_community_layer(user_id)— creates Left membership, persists, inserts into DashMaprejoin_community_layer(user_id)— creates Active membership (preserving left_at_ns), persists, insertscommunity_layer_status(user_id)— reads from DashMap, returns default_active if absentpersist_leave_record(membership)— private helper: writes to users_engine via Tag::CommunityLeave
Gate (db/signals.rs)
Added to BOTH try_cohort_attribution and try_community_forwarding:
if self.community_leave_status.get(&user_id)
.map_or(false, |m| m.status == MembershipStatus::Left)
{
return;
}
Placed at top of each function before any other work.
Startup Restore (db/state_rebuild.rs)
rebuild_community_leave_status(storage, map) scans users_engine for Tag::CommunityLeave keys, deserializes each, inserts into DashMap. Called from from_parts after rebuild_community_memberships.