# Retention

Personal data needs an end date: GDPR's storage limitation means you keep
it only as long as you need it. Nothing expires unless you say so: a value
or record without a `retention:` is kept until your code deletes it or the
person is erased. A `retention:` block gives the end date, in the same
`after:` and `since:` shape as a lifecycle's `purge:`
([Lifecycle](/docs/lifecycle.md)): how long to keep it, `after:`,
counted from one of the record's `Instant` fields, `since:`.

## On a field

The value is cleared when its time is up, and the record stays. Use it for
a value you need only for a while, such as the address a visitor signed
up from, kept for 30 days for fraud checks:

```jadpo
config {
    signup_ip_retention: Duration {
        default: 30d
    }
}

entity Visitor {
    id: Uuid
    signup_ip: IpAddress? {
        personal: PersonalData.online
        retention: {
            after: config.signup_ip_retention
            since: created_at
        }
    }
    created_at: Instant {
        generated: Generated.on_create
    }

    person: true
}
```

Once a visitor signed up more than 30 days ago, `signup_ip` becomes
`none`, whether a lifecycle hides the visitor or not. Clearing it is a
change to the record, so its `Generated.on_change` fields take the time
of the run that cleared it.

The field must be one that can be `none`: its type ends in `?`
([Values that may be missing](/docs/absent-values.md)),
or retention could never clear it
([`data.retention_field_required`](/docs/errors/data.retention_field_required.md)).
It can't be a field the entity's [lifecycle](/docs/lifecycle.md) owns
or reads, such as the `deleted_at` its `visible:` tests, because clearing
it would show a deleted record again or undo what a transition set
([`data.retention_lifecycle_field`](/docs/errors/data.retention_lifecycle_field.md)).

## On an entity

The record is deleted, with whatever cascades from it. Use it for
invoices, as in [Erasure](/docs/erasure.md#keeping-a-record-without-the-person-erasureretained),
or for accounts inactive for two years. Here an invoice goes six years
after it was issued, and its lines go with it:

```jadpo
config {
    invoice_retention: Duration {
        default: 2192d
    }
}

type InvoiceLineText = Text {
    max_length: 200
}

entity Invoice {
    id: Uuid
    number: InvoiceNumber
    issued_at: Instant {
        generated: Generated.on_create
    }

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

entity InvoiceLine {
    id: Uuid
    invoice_id: Invoice.id {
        on_delete: OnDelete.cascade
    }
    text: InvoiceLineText
}
```

A record is deleted whether a lifecycle hides it or not, and so is every
record that cascades from it, records with a lifecycle among them, since
retention, like purge, is an operation the compiler generates. References
that are `OnDelete.cleared` are set to `none`, as for any delete
([References and `on_delete`](/docs/references-and-on-delete.md)).

A record an `OnDelete.restricted` reference holds, directly or through
what deleting it cascades to, can't be deleted, so its retention is an
error ([`data.retention_held`](/docs/errors/data.retention_held.md)): say
what deleting it does to the record that holds it.

An entity that someone signs in as, the `user:` or `service:` of
[`principal { }`](/docs/identity.md), goes with its sign-in state when its
retention ends, or another record's retention cascades to it: its
sessions and API tokens, service keys, passkeys and sign-in links are
deleted in the same statement, so nobody can sign in as them again.

A `File` field's retention clears the field, and a record's deletes the
record, cascades included; either way the file's bytes are deleted once
no record holds them
([Deleting and erasing](/docs/files.md#deleting-and-erasing)).

## Where the periods come from

The periods are settings in `config.jadpo`
([Settings](/docs/settings-config-jadpo.md)), so they
are set per environment and appear in the report. `after:` names a
`Duration` setting that isn't secret, never a duration written in place
([`data.retention_period`](/docs/errors/data.retention_period.md)). A run uses
the value the app started with, and a period that isn't above zero
deletes and clears nothing: the run fails instead.

`since:` names one of the record's `Instant` fields, bare, as a lifecycle
names them ([`data.retention_since`](/docs/errors/data.retention_since.md)).
A record whose `since:` field is `none` isn't counted yet, so it is kept
until the field has a value and that time is past.

## When it runs

The compiler's built-in [purge schedule](/docs/schedules.md#the-purge-schedule)
applies retention, every hour, so an entity with a `retention:`, its own
or a field's, has a schedule such as `Invoice.purge` even without a
lifecycle. A value or record goes at the first run once its `since:` is
longer ago than `after:`. Each run applies the entity's retention first,
then its lifecycle's `purge:`, then each field's retention, in small
batches, each logged, oldest first; the next hour's run carries on with
what one leaves. Its run record counts the records it deleted and the
values it cleared, and it emits no events.

Retention changes the database only. An event that was emitted holding a
value keeps its copy until the runtime removes the event, once every
subscriber has had it and a day has passed since it was emitted
([Events](/docs/declaring-an-event.md)), and a backup keeps
what it held until it expires on the hosting platform's schedule.

No test runs retention, as none runs purge: the purge schedule has no
name a test can write, and moving a test's clock on runs no schedule
([Schedule tests](/docs/kinds-of-test.md#schedule-tests)). It is the compiler's
own operation, proven by the compiler's live tests on both executors.

A person entity, or an entity holding marked fields, with no retention
and no purge is listed in the report as "kept until erased".
