Personal data is minimized at the adapter boundary, before any persistence¶
Status: accepted Date: 2026-07-10 Deciders: Chris (solo founder) Depends on: ADR-0011 (local hub and ownership boundary) Applies first in: ADR-0005 (CVR deltager ingestion)
Context¶
Several current and future sources carry personal data about natural persons: CVR participants (deltagere), Ejerfortegnelsen, Tinglysningen (Personbog), Statstidende, Teledata, and eventually scraped people mentions. The prephase ADR-0003 addendum required one uniform decision instead of per-source improvisation.
The first concrete case forces the issue: the deltager type on
distribution.virk.dk returns natural persons' real names and residential
addresses with nothing more than a Basic Auth credential — while the same
data on Datafordeleren (CVR_CVRPerson) is gated behind OAuth + MitID
Erhverv. The upstream access-control asymmetry does not make the data less
sensitive.
The product needs the people/roles graph (directorships, ownership, board seats — the M:N core of the hub and its highest-value relationship signal). It has no identified need for persons' home addresses or other non-role attributes.
Decision¶
Personal data is minimized inside the adapter/mapper, before anything is
persisted anywhere — including raw snapshots (GCS extracts) and any raw
JSONB column. Concretely:
- Stored for a natural person: the registry surrogate key
(
enhedsNummer), current name, participant kind (person / company / other), and role/ownership data (role type, function, ownership and voting percentages, validity periods). - Dropped at the boundary: residential addresses and every other non-role person attribute. CPR numbers are never stored (this surface does not expose them; the rule still stands for any future surface that does).
- Key model: registry-sourced people are keyed on
enhedsNummer. Externally-sourced people (scraped, commercial) have no such key; they require a separate resolution/alias layer, which remains an open design problem and must not be solved by widening this rule. - Posture: where upstream platforms disagree about gating (the ES/Datafordeler asymmetry), we self-impose the stricter platform's posture.
- Lawful basis: processing of the retained fields rests on legitimate interest in publicly published business-registry data (roles people hold in companies are published by Erhvervsstyrelsen for exactly this transparency purpose). Minimization is what keeps that basis proportionate: we retain only what serves the business-relationship purpose.
This rule is cross-cutting: every future adapter touching people data (Ejerfortegnelse, Tinglysning, Statstidende, Teledata, scraped sources) inherits it and documents any source-specific extension in its own ADR.
Options considered¶
- Store full deltager records (rejected). Persisting home addresses of ~1.8M people with no product need is the hardest GDPR posture to defend and contaminates every downstream copy (raw snapshots, backups, GCS).
- Defer people/roles entirely (rejected). Loses the typed M:N edge table that the hub shape (prephase ADR-0003) and graph strategy (prephase ADR-0006) are built around, for a risk minimization already addresses.
- Minimize at the adapter boundary (chosen). The sensitive fields never enter any store, so there is nothing to purge, no raw copy to re-redact, and the lawful-interest argument stays proportionate.
Consequences¶
- The GCS raw-extract rule from ADR-0005 has one exception by design: the deltager extract is minimized before it is written, so raw-level rebuild for people is a re-fetch, not a re-read.
peoplerows carry norawJSONB column, unlikecompaniesandproduction_units— an accepted inconsistency in the storage contract, bounded to person entities.- If a future product feature genuinely needs a dropped field, that is a new ADR with its own lawful-basis analysis and a re-backfill from upstream — deliberately expensive, which is the point.
- Adapter tests must assert the absence of dropped fields in mapper output and in the extract writer, not just the presence of kept ones.
Implementation note (2026-07-19, issue #23)¶
Implemented in ingesters/cvr/mappers.py's map_deltager /
minimize_deltager_for_extract — see ADR-0005's 2026-07-19 people & roles
amendment for the delivery details. The minimization is structural
(allowlist reconstruction), not a denylist filter, so there's no dropped
field to accidentally miss; mapper and pipeline-seam tests assert the
absence of residential-address fields per this ADR's testing requirement.
Open questions¶
- Entity resolution for externally-sourced people (no
enhedsNummer): alias/resolution layer design deferred until a class-B source lands (prephase open question, unchanged). - Whether
people.nameitself warrants pseudonymization in exports/read contracts consumed outside the platform — decide when an external-facing consumer exists.