Skip to content

The GS1 Digital Link route answers 404 for a suspended passport, losing the withdrawal signal #161

Description

@LKSNDRTMLKV

odal passport suspend is documented as "Suspend a published passport (serves 410 Gone)". On a freshly booted node, a suspended passport is served two different and both-wrong ways depending on which public route the consumer arrives by.

Reproduced end to end: fresh stack, 5 textile passports imported and published via the CLI, then one suspended with odal passport suspend.

1. /dpp/{id} — the status line and the problem body disagree

$ curl -s -D- http://localhost:8103/dpp/01a0256c-c082-7641-8b56-ceb3b1526904
HTTP/1.1 410 Gone
content-type: application/json

{"detail":"DPP not found","status":404,"title":"Not Found",
 "type":"https://problems.odal-node.io/not-found","requestId":"…"}

The status line is 410 Gone, which is right. The RFC 7807 body inside it claims "status": 404 and "title": "Not Found". RFC 7807 §3.1 says the status member is "the HTTP status code generated by the origin server for this occurrence of the problem" — it is meant to mirror the status line, and here it contradicts it.

detail is also wrong on its own terms: "DPP not found" is a claim that the passport does not exist. A suspended passport does exist and has been withdrawn, which is the entire distinction 410 carries.

Any client that trusts the body over the status line — a reasonable thing to do, since the body is the structured half — concludes the passport never existed.

2. The GS1 Digital Link route loses the withdrawal signal entirely

That is the /dpp/{id} route. The route a consumer actually reaches by scanning the QR code is the GS1 Digital Link path, and it does not return 410 at all:

$ curl -s -o /dev/null -w '%{http_code}' \
    'http://localhost:8103/01/03801234567898/10/BATCH-SS26-001/21/76418b56ceb3b1526904'
404

{"error":"NOT_FOUND","message":"No published DPP for this GTIN","requestId":"…"}

For an active passport the same route answers 307 and redirects to /dpp/{id}. For a suspended one it 404s before redirecting, so the 410 on /dpp/{id} is never reached.

This is the more consequential half. Serving 410 for a withdrawn passport is a recall signal, and the audience for that signal is whoever scans the code on the product. They take the GS1 path, and on that path a recalled product is indistinguishable from a GTIN that was never registered.

Note the two routes also use different error shapes: /dpp/{id} returns RFC 7807 (type/title/detail/status), the GS1 route returns {"error","message","requestId"}.

Suggested direction

  • Make the problem body on /dpp/{id} agree with its own status line, and say the passport was withdrawn rather than not found.
  • Have the GS1 Digital Link lookup distinguish "no such GTIN" from "this GTIN resolves to a withdrawn passport", and serve 410 for the latter — either directly or by redirecting to /dpp/{id} and letting that route answer.
  • Consider whether the two public routes should share one error shape.

Found during a from-scratch CLI walkthrough of a freshly booted stack.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions