Compliance and Privacy
GDPR data deletion, data export, pseudonymization, retention scheduling, legal hold, and compliance keystore in Aether.
Aether's compliance system implements GDPR requirements for data deletion, portability, pseudonymization, and retention management. The aether-compliance crate provides a complete compliance pipeline that ensures the platform can legally operate in jurisdictions enforcing the General Data Protection Regulation.
Key Concepts
- Right to erasure (GDPR Article 17) -- Users can request deletion of all personal data.
- Right to data portability (GDPR Article 20) -- Users can export their data in a machine-readable format.
- Pseudonymization -- Personal identifiers in ledger and audit data are replaced with irreversible tokens.
- Retention limits -- Data is not kept longer than necessary, with configurable retention periods.
- Legal hold -- Court orders or investigations can defer deletion until the hold is released.
- Compliance keystore -- Encrypted storage for deletion salts with dual-approval access.
Architecture
The compliance system is composed of six modules working together:
Deletion Request
|
v
Legal Hold Check
|
[no hold] [hold active]
| |
v v
Deletion Pipeline OnHold State
| |
+----+----+----+ [hold released]
| | | | |
v v v v v
Export Pseudo Delete Resume Pipeline
Data Ledger Profile
|
v
Archive Salt in Keystore
|
v
Retention Schedule (7 years)
Pseudonymization
When a user requests deletion, their personal identifiers in ledger and audit records are replaced with a one-way pseudonym:
use aether_compliance::{pseudonymize_id, generate_salt};
// Generate a random 32-byte salt for this deletion
let salt: Vec<u8> = generate_salt();
// Compute the pseudonym: SHA-256(user_id || salt)
let pseudonym: String = pseudonymize_id(user_id, &salt);
// The pseudonym replaces user_id in all ledger/audit rows
// This is a one-way operation -- the original user_id cannot be
// recovered without the salt
The salt is stored encrypted in the compliance keystore for potential audit needs. Without the salt, the mapping from pseudonym back to user ID is computationally infeasible.
Deletion Pipeline
Account deletion follows a step-based state machine that executes deletion stages in order:
use aether_compliance::{DeletionPipeline, DeletionRequest, DeletionStatus};
let request = DeletionRequest {
user_id: user_uuid,
requested_at: now,
reason: "User requested account deletion".to_string(),
};
let mut pipeline = DeletionPipeline::new(request);
// Advance through each deletion step
loop {
match pipeline.advance() {
DeletionStatus::InProgress { step } => {
println!("Executing step: {:?}", step);
}
DeletionStatus::Completed => {
println!("Deletion complete");
break;
}
DeletionStatus::Failed { step, error } => {
// Retry or escalate
break;
}
DeletionStatus::OnHold { case_id } => {
// Legal hold is active, cannot proceed
break;
}
}
}
Deletion steps execute in this order:
| Step | Action | Description |
|---|---|---|
| 1 | ExportUserData | Generate a final data export before deletion |
| 2 | PseudonymizeLedger | Replace user_id with pseudonym in all ledger rows |
| 3 | DeleteProfile | Remove profile and personal data |
| 4 | DeleteSocialData | Remove social graph data (friends, blocks, groups) |
| 5 | DeleteSessionData | Remove session and telemetry data |
| 6 | ArchiveDeletionSalt | Store encrypted salt in keystore for audit |
The state machine enforces valid transitions:
Requested->InProgress(start deletion)Requested->OnHold(legal hold is active)OnHold->Requested(hold released, can resume)InProgress->Completed(all steps done)InProgress->Failed(step error, retryable)Failed->InProgress(retry after fixing the issue)
Legal Hold
Legal holds prevent deletion from proceeding when data must be preserved for legal proceedings:
use aether_compliance::{HoldManager, LegalHold};
let mut holds = HoldManager::new();
// Place a hold (e.g., from a court order)
holds.place_hold(LegalHold {
case_id: "CASE-2026-001".to_string(),
user_id: user_uuid,
reason: "Active investigation".to_string(),
placed_at: now,
});
// Check if a user has active holds
if holds.has_active_holds(user_uuid) {
// Deletion pipeline transitions to OnHold state
}
// Release the hold when the legal matter is resolved
holds.release_hold("CASE-2026-001");
// Deletion can now resume
Multiple holds can exist for the same user. Deletion only resumes when all holds are released.
Retention Schedule
Pseudonymized financial data is retained for 7 years to satisfy financial regulations. After the retention period, data is automatically purged:
use aether_compliance::{RetentionSchedule, RetentionRecord};
let mut schedule = RetentionSchedule::new(7); // 7-year retention
// Track a pseudonymized record
schedule.add_record(RetentionRecord {
record_id: record_uuid,
pseudonym: pseudonym.clone(),
category: "ledger".to_string(),
expires_at: now + Duration::from_secs(7 * 365 * 86400),
});
// Collect expired records for purging
let expired = schedule.collect_expired(now);
for record in expired {
// Purge the record from storage
}
Legal holds override retention expiry -- data under hold is kept until the hold is released, even if the retention period has elapsed.
Data Export
Users can request a complete export of their data in machine-readable format (GDPR Article 20):
use aether_compliance::{DataExporter, ExportBundle};
let mut exporter = DataExporter::new(user_uuid);
// Add data sections
exporter.add_section("profile", &profile_data);
exporter.add_section("social", &social_data);
exporter.add_section("economy", &economy_data);
exporter.add_section("sessions", &session_data);
// Finalize the export bundle with integrity hash
let bundle: ExportBundle = exporter.finalize();
// bundle.manifest -- JSON listing all included data categories
// bundle.hash -- SHA-256 digest for integrity verification
// bundle.sections -- the actual data per category
The export bundle includes:
| Section | Contents |
|---|---|
profile | Display name, bio, avatar URL, settings |
social | Friends list, group memberships, blocked users |
economy | Transaction history, wallet balances |
sessions | Login history, device information |
The bundle is hashed with SHA-256 for integrity verification, ensuring the export has not been tampered with during delivery.
Compliance Keystore
Deletion salts are stored encrypted in the compliance keystore with dual-approval access:
use aether_compliance::{ComplianceKeystore, KeystoreEntry};
let mut keystore = ComplianceKeystore::new(master_key);
// Store an encrypted deletion salt
keystore.store(KeystoreEntry {
key_id: key_uuid,
purpose: "deletion_salt".to_string(),
encrypted_data: encrypted_salt,
approvers: vec![admin_a, admin_b], // dual-approval required
created_at: now,
});
// Retrieve for audit (requires matching approvers)
if let Some(entry) = keystore.lookup(key_uuid) {
// Decrypt and use the salt for audit verification
}
Dual-approval means at least two authorized administrators must be recorded as approvers for any keystore entry. This prevents single points of compromise in the deletion salt chain.
Configuration
Compliance behavior is driven by environment variables: AETHER_RETENTION_YEARS (default 7), AETHER_DELETION_TIMEOUT_HOURS (default 72), and AETHER_EXPORT_MAX_SIZE_MB (default 500). All modules use in-memory storage at the domain layer, with persistence handled by the aether-persistence crate in production.