heygrc
GDPR storage limitation in code

Personal data that forgot to have an end.

Storage limitation, Art. 5(1)(e), says personal data should be kept in a form that identifies people no longer than is necessary for the purpose it was collected for. It is one of the easiest duties to break without meaning to, because adding a place to store something is routine, and adding the part that deletes it later is the part everyone forgets.

How it shows up in a diff

The shapes the same control failure takes.

The change is almost never 'keep this forever on purpose'. It is a missing half of an otherwise reasonable change. These are the shapes that recur.

  • A new store ships with no end of life

    A migration or model adds a table, cache, or index holding personal data, and nothing sets a retention bound or an expiry, so the data simply accumulates.

  • A retention job is removed or disabled

    A scheduled purge, a TTL on a cache, or a cleanup task is dropped in a refactor or commented out to fix an unrelated issue, and the data it used to age out now stays.

  • A window quietly extends

    A retention period is increased (90 days becomes indefinite, or a config default changes) without a purpose or a documented reason for keeping the data longer.

  • A soft delete keeps everything

    A delete is changed to a flag (a deleted_at column) so the row, and the personal data in it, is still there indefinitely, which is retention, not deletion.

  • Erasure does not reach the new copy

    A new place personal data lands (an export, an analytics sink, a second database) is not wired into the delete-my-account path, so a copy survives erasure. That is also an Art. 17 problem.

Worked example

A removed purge that keeps inactive users forever.

A scheduled job that deleted the personal data of long-inactive users is dropped, maybe it was noisy, maybe it seemed safe to keep the data. The effect is that personal data now has no defined end of life.

jobs/retention.yaml+0 -1
schedules:  reconcile: { cron: "0 2 * * *" }-  purge_inactive: { cron: "0 3 * * *", delete: users, older_than: P2Y }
heygrcGDPR Art. 5(1)(e)

This removes the only thing that deleted personal data for inactive users, so it is now retained with no end date. Art. 5(1)(e) (storage limitation) expects personal data to be kept no longer than the purpose needs. If the job was too aggressive or noisy, fix its schedule or scope, but keep a retention path rather than removing it entirely.

What an auditor does with this

Retention is also an erasure problem.

Storage limitation is assessed against your records of processing and your retention schedule: data found living past its stated purpose is the gap a review looks for. It is tied to the right to erasure, Art. 17, because every place personal data is kept is a place a deletion request has to reach. A new store with no retention is usually also a store the delete-my-account path does not know about, so the two duties tend to break together, in the same pull request.

What this is, and is not

A review, not legal advice.

heygrc flags changes that touch storage limitation and cites the article so the fix happens in the pull request. It does not set your retention periods, render a legal determination, or maintain your records of processing. It catches the change early so a retention question is answered in review rather than after an incident. heygrc is in early access.