Data Retention & Deletion Policy
Effective date: 2026-10-19
Policy version: retention-2026-10
Applies to: ATHELOS Cloud (multi-tenant SaaS), Dedicated instances, and Self-Hosted deployments operated by ZERODIN.
1. What this policy covers
ATHELOS stores three categories of data:
| Category | Where | Who controls it |
|---|---|---|
| Tenant business data | Per-tenant Postgres database, MinIO bucket, Meili index | The customer (data controller) |
| Control-plane data | Shared Postgres database (tenants, users, sessions, subscriptions, verification tokens, webhook events) | ZERODIN (as processor for login/billing, as controller for account metadata) |
| Audit logs | tenant_deletion_audit, webhook_events | ZERODIN |
This policy states how long each category is retained and when it is destroyed.
2. Tenant business data
Tenant business data lives in three places: the tenant's Postgres database (athelos_tenant_<slug>), the tenant's MinIO bucket (tenant-<slug>), and the tenant's Meilisearch index (tenant_<slug>_kb).
2.1 Retention by tenant state
| State | Retention period | Deletion triggered by |
|---|---|---|
| active | Indefinite (customer owns the data) | Customer request, cancellation, or suspension per below |
| pending_payment (abandoned signup) | 7 days from created_at | signup.purge_abandoned job @ 03:00 daily |
| cancelled | 30 days from updated_at (when status was set to cancelled) | tenant.retention_scan job @ 02:00 daily |
| suspended | 90 days from updated_at (when status was set to suspended) | tenant.retention_scan job @ 02:00 daily |
The clock-start column matters: for pending_payment, the abandonment clock starts at created_at (when signup happened). For cancelled and suspended, it starts at updated_at (when the status changed). A tenant that signed up 8 days ago but cancelled yesterday is not eligible for deletion as abandoned — the cancelled rule applies (30 days).
2.2 What "deletion" means
When a tenant is deleted via tenant.delete job, the following are destroyed:
- Cloudflare DNS A record (
<slug>.athelos.pro) — removed via API - MinIO bucket (
tenant-<slug>) — emptied, then removed - Meilisearch index (
tenant_<slug>_kb) — removed - Postgres database (
athelos_tenant_<slug>) — dropped withDROP DATABASE ... WITH (FORCE) - Control-plane tenant row — deleted, which cascades to
users,sessions, andsubscriptionsviaON DELETE CASCADE
After deletion, the tenant no longer exists in any form except for:
- A hashed audit entry in
tenant_deletion_audit(see §3) - The billing record in Stripe (required for tax/audit compliance — see §4)
2.3 Backups
The tenant's database, MinIO bucket, and Meili index are backed up via restic to Backblaze B2. The retention of backups follows the backup rotation policy, not this data-retention policy. When a tenant is deleted, the most recent backups are also removed by the next restic prune cycle.
3. Audit logs
3.1 tenant_deletion_audit
When a tenant is scheduled for deletion, a row is written to tenant_deletion_audit before the deletion job runs. The row records:
- A SHA-256 hash of the tenant ID (salted with
AUDIT_SUBJECT_SALT) — never the plaintext tenant ID - The tenant slug
- The reason (
cancelled,suspended, orabandoned) - The policy version (
retention-2026-10) - The scheduled timestamp
- The completion timestamp (when the delete job finishes)
- The status (
pending,completed, orfailed) - Any error message if the deletion failed
Retention: 7 years from scheduled_at. This matches the tax-record retention requirement and is the minimum required to demonstrate GDPR Article 30 compliance during an audit.
3.2 webhook_events
Every Stripe webhook event is recorded in webhook_events for idempotency and audit. Retention: 90 days from created_at. After 90 days, the row is eligible for archival (via a future scheduled cleanup job) but is currently kept indefinitely pending that implementation.
3.3 Verification tokens
verification_tokens rows are short-lived:
- Expire 24 hours after
expires_at - Deleted immediately upon successful use (the
used_attimestamp is set, and the row is retained only until the retention cleanup removes it) - Retention: the row is retained until the password hash is no longer needed. Currently kept until manual purge — a future scheduled cleanup will remove expired and used tokens after 30 days.
4. Billing records
Payment records (Stripe customer ID, subscription ID, invoices) are retained for 7 years to satisfy tax and accounting obligations in the EU and US. This retention period applies even after the tenant is deleted from the ATHELOS system — the billing record is held by Stripe, not by ATHELOS, and Stripe's own retention policy applies.
5. GDPR Article 17 (right to erasure)
A data subject may request deletion of their personal data at any time. The ZERODIN operator:
- Receives the request (via
privacy@athelos.proor the customer's tenant admin) - Verifies the request originates from an authorized party for that tenant
- Manually enqueues
tenant.deletewithreason: "gdpr" - The delete job runs immediately (not waiting for the retention threshold)
- A hashed audit row is written to
tenant_deletion_audit - The data subject is notified within 30 days of the request (per GDPR Article 12(3))
Note: the retention thresholds in §2.1 do not override a GDPR erasure request. A customer's GDPR request deletes the tenant immediately regardless of status or age. The retention schedule is the default deletion timeline, not the maximum.
6. How this policy is enforced
This policy is not aspirational — it is backed by code:
| Retention rule | Code location |
|---|---|
pending_payment > 7 days → delete | apps/worker/src/jobs/signup-purge-abandoned.ts |
cancelled > 30 days, suspended > 90 days → delete | apps/worker/src/jobs/tenant-retention-scan.ts |
| Actual deletion (DB, bucket, index, DNS) | apps/worker/src/jobs/tenant-delete.ts |
| Guard confirming age before deletion | apps/worker/src/jobs/tenant-delete.ts (guard block) |
| Audit row written before delete | apps/worker/src/jobs/tenant-retention-scan.ts and signup-purge-abandoned.ts |
| Nightly schedule | apps/worker/src/index.ts registerSchedules() |
| Salted subject hash | createHash("sha256").update(tenant.id + AUDIT_SUBJECT_SALT) |
Any change to the code above must be reflected in this document in the same commit. The policy version retention-2026-10 appears in every audit row so future audit can tell which policy was in effect when a deletion happened.
7. Changes to this policy
Material changes to this policy (extending or shortening a retention period, changing the deletion scope) require:
- A bump to the policy version constant (
POLICY_VERSIONin both scan jobs) - An update to this document
- Notification to affected customers 30 days before the change takes effect
Minor clarifications (spelling, examples) do not require the above.
8. Contact
For questions about this policy or to exercise a data subject right:
- Email:
privacy@athelos.pro - For urgent breach-related matters, see
docs/legal/INCIDENT-RESPONSE.md