Skip to content

Personal-data records: store and list routes #482

Open

Description

@LKSNDRTMLKV

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 store answer was lost. Core's contract says such a record is found through records_for and 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 store needs the passport id. A client creates the draft first (stating held: nothing or leaving the field empty), stores the record, then patches the statement in.
  • store does 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

  1. added
    complianceRegulatory/compliance correctness issue
    type/decisionNeeds a call; analysis alone will not close it
    surface/persistenceMoves retention, archiving or the ESPR Art. 10(4) back-up copy
    on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    complianceRegulatory/compliance correctness issuesurface/persistenceMoves retention, archiving or the ESPR Art. 10(4) back-up copytype/decisionNeeds a call; analysis alone will not close iturgency/backlogOwed, unscheduled

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions