Data lifecycle guarantees
What this covers / who it's for. What the platform keeps, for how long, what actually deletes it, what right-to-be-forgotten does (and refuses to do), and what freeze/closure mean for data. These are commitments you can repeat to a customer or a regulator — each row cites where the fact lives in code.
The universal floor: 7 years
One number governs everything regulated: the compliance retention floor — 2555 days (7
years) (foundation.compliance_retention_floor_days,
app-modules/foundation/config/foundation.php). Every prune that touches compliance-relevant
data clamps its cutoff up to this floor, even when its own configured window is shorter.
A mis-configured short window cannot delete regulated data early — the clamp wins.
And one universal convention: deleted ≠ destroyed. Domain records are soft-deleted (hidden, recoverable, still present for audit) and every write records who did it (the blamable convention). Hard deletion happens only through the named prune mechanisms below.
The retention table
| What | Window | Prune mechanism (today) | Source |
|---|---|---|---|
| Vendor request log (every outbound provider call) | 90 days | Daily prune, live. Telemetry posture: the authoritative KYB record lives elsewhere, so the floor does not apply; a deployment can flip it to compliance-relevant, which clamps it to the floor | app-modules/rails/config/rails.php |
| Inbound webhook deliveries | 30 days | Daily prune, live | app-modules/webhooks/config/webhook-client.php |
| Async requests (exports/statements poll records) | 30 days, terminal (succeeded/failed) rows only — a stuck non-terminal row is never pruned (it is an operational signal) | Daily prune, live. Never touches the produced artifacts, which live under the owning feature's retention | async-requests README |
| Task-run ledger (fleet/background work history) | 90 days, terminal runs only | Daily prune, live; the underlying batch bookkeeping ages out separately (48 hours finished / 7 days stuck-or-cancelled) | app-modules/task-runner/config/task-runner.php |
| Command idempotency claims (bus bookkeeping) | Soft-deleted after 30 days, hard-deleted 7 days after that | Daily prune, live. Bookkeeping, explicitly exempt from the floor | app-modules/foundation/config/foundation.php |
| Resolved drift records (provider divergence evidence) | Configured 365 days — but clamped to the floor, so effectively 7 years today; unresolved drift is never pruned | Daily prune, live | app-modules/provider-mirror/config/provider-mirror.php |
| Audit trail | Never pruned in-app. Append-only, tamper-evident; the growth strategy is off-platform WORM export — which ships disabled until a write-once bucket is provisioned | No in-app deletion exists | audit README |
| DLP access/egress logs | Never pruned in-app (part of the compliance floor) | No in-app deletion exists | dlp README |
| Comms log (every notification/email sent) | No window set — kept indefinitely today. The per-class prune mechanism is shipped and scheduled but inert until legal/compliance sign off windows; compliance notices will clamp to the floor when they do | Shipped, inert | notification README |
| Document access log (every download served) | No window set — kept indefinitely today. Same shipped-but-inert posture, floor-clamped per outcome class when configured | Shipped, inert | documents README |
Prune schedules (all daily, staggered) are enumerated in Observability & ops.
Right to be forgotten: crypto-shred
Document erasure is crypto-shred, not file deletion (documents README):
- The file's own encryption key is destroyed. Each document is encrypted under its own per-file key; dropping that key makes the stored ciphertext permanently unrecoverable without touching the blob. The subject-identifying filename is scrubbed in the same step.
- The row survives as a tombstone — the metadata proof that the document existed and was erased, with when and under which erasure reference. Every serve path refuses a shredded document explicitly.
- Blob deletion is optional and off by default for vault-encrypted documents (the key drop already guarantees unreadability; physical deletion awaits WORM sign-off). The exception: documents that arrived via the direct-to-storage path have no per-file key, so for those the verified object delete is the erasure and is mandatory.
- RTBF is refused inside the retention window — plainly: regulation beats RTBF. A shred request for a document still inside its regulatory window (never shorter than the 7-year floor) is hard-refused with an explicit error. This is by design; do not promise erasure of KYB/KYC material inside the window.
- It is a deliberate operator console action today — there is no HTTP endpoint and no self-service erasure surface.
Caveat from Where data lives: shredding our copy does not touch the copies the provider holds — a full erasure conversation includes the provider.
Freeze and closure: what happens to data
(Compliance & trust, compliance README)
- Freeze blocks actions, deletes nothing. A frozen tenant's money-movement and other state-changing actions are refused with an explicit "account frozen" error; data, history, and evidence remain fully intact and operator-visible. Unfreezing releases anything held in the meantime.
- Closure is terminal — and data is retained per the schedule above. Closing an account ends activity; it does not trigger deletion. Retained documents of closed/offboarded tenants are treated as frozen evidence: integrity checks, retention, and RTBF apply to them exactly as to live tenants.
What you can promise
- Nothing regulated is deleted before 7 years — enforced in code, not by policy memo.
- The audit trail and DLP logs are never deleted by the application.
- Comms and document-access history are currently kept indefinitely (pruning awaits signed-off windows — that is the honest ship-state, and also the current cost/growth story).
- Erased documents are cryptographically unrecoverable, provably tombstoned — and erasure inside the retention window is refused, not quietly skipped.
- Freezing or closing an account never destroys data.