Repository navigation
Personal-data records: store and list routes #482
Description
Follows #446. Needs a decision before any code; the options are below.
The gap
#481 (stacked on #479) holds the records a passport's personalData statement names, in odal.personal_data_record, and adds DELETE /vault/api/v1/personal-data/{record} to erase one. Nothing in the node calls PersonalDataPort::store or records_for:
- No route writes a record, so content cannot get into the store through the API.
- No route lists a passport's records, so an operator cannot learn the id of a record whose statement was never written (an orphan), or one whose
storeanswer was lost. Core's contract says such a record is found throughrecords_forand erased; today only code that holds the port can do that.
What constrains the answer
- A record is served by no resolver or view route and is never part of the back-up copy, the archive or any passport view. A listing therefore returns identifiers, the field, whether each record is present or erased, and the instants. Never content.
- A tombstone is not free of personal data: a passport identifier can relate to whoever owns the item (GDPR Recital 26). A listing is as sensitive as the passport's individual-item data and needs the same scope care as the erase route, which is admin.
- Order: the statement needs the record id, and
storeneeds the passport id. A client creates the draft first (statingheld: nothingor leaving the field empty), stores the record, then patches the statement in. storedoes not check that the field is marked; that is the passport's check, made when the statement is written. A route could add a check that the passport exists and is a draft.- Body size limits apply to anything that accepts content.
Options
| What it adds | Cost | |
|---|---|---|
| a. Store and list | POST /dpp/{dppId}/personal-data (content in, id out) and GET /dpp/{dppId}/personal-data (ids and state out) |
The node accepts personal data over its API. Needs an OpenAPI schema for content, a size limit, an idempotency decision (a retry stores a second record) |
| b. List only | The GET; the operator's own system writes records through the port |
Smallest change. Leaves "how does content get in" to an integration the node does not ship |
| c. Neither | Document that records are managed outside this API | Erasure is then a route for records nothing in the node can create |
Recommendation: a, admin-scoped, with the store route accepting only a draft passport, a non-empty field and content under a stated size cap, and the listing returning no content. Option b is the fallback if the founder does not want the node to accept personal data over HTTP.
Done when
A decision is recorded here, then, for the chosen option: the routes, OpenAPI, a test for every documented error condition, real-Postgres tests, a CHANGELOG entry, and just check (every step except audit while it is red on main), just lint-integration and just openapi-check green.
Activity