Skip to content

feat(vault): name the record a superseded read serves - #369

Merged
LKSNDRTMLKV merged 1 commit into
mainfrom
feat/superseded-read-says-so-in-content-location
Sep 17, 2026
Merged

LKSNDRTMLKV merged 1 commit into
mainfrom
feat/superseded-read-says-so-in-content-location

Conversation

@LKSNDRTMLKV

Copy link
Copy Markdown
Member

Closes #363.

#354 made GET /vault/public/dpp/{dppId} serve the successor's record when the passport asked for is superseded. That is right for what the route actually is — a printed data carrier under ESPR Art. 9(1) cannot be recalled, so the door has to keep landing on whichever record is current — but it leaves a 200 whose body is not the resource that was requested, and nothing said so at the protocol layer.

A human following a QR code never notices. A client asserting response.id == requested_id does, and is right to.

What it does

Content-Location on the superseded branch only, naming the record actually served. RFC 9110 §8.7 is precisely this case: the representation is available at another URI and the response is still a 200 for the one asked for, which is what the carrier needs.

Its presence is the signal. An ordinary read carries no such header, so a client does not have to parse the value to learn anything — receiving it at all means "this is not the record you asked for". That is why the test asserts both directions.

Two decisions

A relative reference, ./{id}. This router is mounted at /vault by the node and at the root when the vault runs standalone, so no absolute path is correct in both. ./Y resolved against …/public/dpp/X gives …/public/dpp/Y under either mount, which is what RFC 9110 permits a partial-URI to do.

A header rather than a redirect. A 301/303 says the same thing and costs more: the resolver, the HTML, JSON-LD and AAS doors all sit in front of this route, so a redirect is four behaviours to re-verify rather than one header to add — and /01/{gtin} beside it does not redirect either, so redirecting here would make the two doors disagree again in the opposite direction. The served body is the successor's own signed view and its supersedesId already names the passport that was scanned; the header just surfaces that for a client that has not parsed the body yet.

Incidental

successor_view's return became a named SuccessorView struct. It needed a fourth member — the id, which is what the header names — and view.0 / view.1 / view.2 at the call site was already at the edge of readable. No behaviour change.

Tests

Extended an_amended_passports_printed_carrier_lands_on_the_successor, which already drives this exact path through the assembled node:

  • absent on the read before the amendment — the assertion that makes presence meaningful;
  • present, and equal to ./{successorId}, on the read after.

Run against Docker: 1/1.

The OpenAPI documents it under headers on the 200, beside the paragraph #354 added about id not always being dppId. Bundles regenerated; redocly lint clean.

just check green.

@LKSNDRTMLKV
LKSNDRTMLKV force-pushed the feat/superseded-read-says-so-in-content-location branch from 5e290d1 to fb4bc8c Compare September 17, 2026 13:49
@LKSNDRTMLKV
LKSNDRTMLKV merged commit 5c7a50a into main Sep 17, 2026
14 checks passed
@LKSNDRTMLKV
LKSNDRTMLKV deleted the feat/superseded-read-says-so-in-content-location branch September 17, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A superseded read should say so in Content-Location, not only in the body

1 participant