# Erasure

Erasing a person is a generated operation, `User.erase(user_id)`. You call
it from an action you write, for example behind a "delete my account"
route or in a support tool. It erases the person's record and decides what
happens to every record that belongs to them.

What happens to each of those records follows from the `on_delete:`
setting of the [reference](references-and-on-delete.md) that
links it to the person, so most references need nothing more:

- `OnDelete.cascade` means the record is deleted (`Erasure.deleted`).
- `OnDelete.cleared` means the record is kept and anonymised
  (`Erasure.anonymised`).
- `OnDelete.restricted` must say what erasure does, with `erase:`.

A restricted reference must say, because "a user with todos can't be
deleted" is the right rule for a mistaken click, but not for a person's
legal request to be forgotten. Most apps write one or two `erase:` lines,
and the compiler refuses an erasure it can't complete.

| `erase:` on a reference to a person | What happens to the record | Needs |
|---|---|---|
| `Erasure.deleted` | Deleted, with everything that cascades from it | |
| `Erasure.anonymised` | Kept; the reference and every marked field are cleared | the reference and marked fields nullable |
| `Erasure.retained` | Kept as it is until its own retention ends; only the reference is cleared | the reference nullable, and `retention:` on the entity |

A marked field is one with a `personal:` setting, which says it holds
personal data ([Marking personal fields](marking-personal-fields.md)). A
nullable field is one whose type ends in `?`, so it can hold no value
([Values that may be missing](absent-values.md)).

## Deleting the person's records: `Erasure.deleted`

Use it for records that are only the person's, such as their todos.
Here `Todo`'s reference to its owner is restricted, so ordinary code can't
delete a user who still has todos, but erasing the owner deletes their
todos:

```jadpo
// domains/planning/entities/todo.jadpo
entity Todo {
    id: Uuid
    owner_id: User.id {
        on_delete: OnDelete.restricted
        role: TodoRole.owner
        erase: Erasure.deleted
    }
    title: TodoTitle
    ...
}
```

## Keeping a record without the person: `Erasure.retained`

Use it for records the law makes you keep. Invoices are the usual case:
tax law requires them for several years, which GDPR Article 17(3) allows.
The invoice keeps its own copy of the billing name and address, so it no
longer needs the user. Erasing the user clears `customer_id` and keeps the
invoice:

```jadpo
// domains/billing/entities/invoice.jadpo
entity Invoice {
    id: Uuid
    customer_id: User.id? {
        on_delete: OnDelete.restricted
        erase: Erasure.retained
    }
    billing_name: PersonName {
        personal: PersonalData.identifying
    }
    issued_at: Instant {
        generated: Generated.on_create
    }

    retention: {
        after: config.invoice_retention
        since: issued_at
    }
}
```

The invoice is then deleted when its own [retention](retention.md) ends,
here `config.invoice_retention` after it was issued (six years in the UK).

A record you keep this way holds its own copy of the details it needs,
as the invoice holds the billing name. It never points at a stub left in
place of the person. It also needs a `retention:` of its own, which says
when it is deleted.

## The person's own record

The person's own record is erased field by field, because it can't always
go: an insurer, for example, must keep policy records for at least five
years.

- Where nothing must be kept, the record is deleted.
- Otherwise a field marked `erase: Erasure.retained` stays, which needs
  the entity's `retention:`, and every other marked field is cleared. The
  person can no longer sign in, receives nothing, and is left out of every
  query except those that serve the retained purpose. When the retention
  ends, the purge schedule deletes the rest.

## What erasure reaches

- **Every store and cache.** The main store's records go in one
  transaction, and cached copies are wiped at once
  ([Caches](caches.md)). An action can write to only one store,
  so records your app keeps in another store are erased straight after,
  by an event [subscriber](subscribers.md) that Jadpo
  generates.
- **Records with a lifecycle** are removed outright, not soft-deleted.
  Your own code can't do that, but erasure, like purge, is an operation
  the compiler generates, so it can
  ([Lifecycle](lifecycle.md)).
- **Files** attached to erased records are deleted with them
  ([Files](files.md)).
- **OAuth grants**, the permissions a person gave your app to act for them
  at a provider, are deleted with the person or record that owns them
  ([OAuth grants](acting-for-each-user-oauth-grants.md)).
  They are revoked at the provider where its package declares how.
- **Other companies.** Data already sent to a provider, such as an email
  or payment provider, is outside your database. Providers that can erase
  are asked to (see "Erasure at providers" below). The
  [personal-data report](the-personal-data-report.md) lists every connection that receives a marked field, and whether its
  copy is erased.

## What erasure doesn't rewrite

- **Backups** aren't rewritten. Each erasure leaves an entry in an erasure
  log, holding the person's `id` and the time and nothing else. Restoring a
  backup replays the log before the app starts. Backups themselves expire
  on the hosting platform's schedule, which the report states. This is
  what the UK regulator, the ICO, calls putting data "beyond use".
- **There are no per-person encryption keys**, so erasure never works by
  deleting a person's key ("crypto-shredding").
- **Logs** aren't rewritten either. They hold identifiers and hashes,
  never marked values, and expire on their own retention.

## Legal holds and restricted use

A legal hold, which stops a person's data being erased while a case is
open, is application data that you write. An action checks a hold flag and
refuses with its own failure before it calls `User.erase`. Restricting use
of someone's data while a complaint is open (Article 18) works the same
way.

## Erasure at providers

When your app sends a person's data to another company, such as a payment
provider, erasing the person also asks that provider to erase them. Two
declarations make that work. The provider's package declares, in its
`provider.jadpo`, which call erases a person and what it takes. Your app's
connection names the field that holds the person's key at that provider:

```jadpo
// packages/stripe/v2024_06_20/provider.jadpo
provider {
    ...
    erasure: {
        call: customers.delete
        by: CustomerId
    }
}

// app.jadpo
stripe: {
    package: Stripe.v2024_06_20
    domains: [payments]
    credential: config.stripe_secret_key
    erasure: {
        key: User.stripe_customer_id
    }
}
```

Erasure emits a `PersonErased` event. It is a declaration on the package,
not part of any [capability](capabilities.md), so Stripe
and Postmark are erased the same way.

- **Your app first, then the providers.** When `User.erase` runs, Jadpo
  copies each connection's key, here `stripe_customer_id`, into the
  erasure event. It then erases the person in one transaction, as above.
  After that commits, a generated subscriber calls each provider's erasure
  operation, retried until the provider confirms. So a provider never
  holds the only record of someone your app still has.
- **The completion email waits for every provider.** The
  erasure-completed email, one of the
  [built-in emails](built-in-emails.md), goes only
  once every provider has confirmed. A delivery that gives up is parked
  for an operator and shown in the privacy request's state and in
  `jadpo privacy requests`. It is never left quietly incomplete.
- **A copied key that is itself personal data**, such as an email address
  used as the provider's key, stays only in the outbox until it is
  delivered. The personal-data report lists it there.
- **A connection that receives personal data but can't erase it** is a
  compile error. That is a connection whose package declares no
  `erasure:`, or which names no key. An
  [acceptance](acceptances.md), a committed file
  in which a person states why the data stays there (tax records at an
  accounting provider, say), allows it, and the report lists the
  connection and the reason.
- **Anything else** your app sends data to, such as a CRM it calls
  through its own integration, can be erased by your own `PersonErased`
  subscribers.
