Skip to content

The public passport response omits a field its own schema marks required #378

Open

Description

@LKSNDRTMLKV

api/components/schemas/passport/PassportResponse.yaml lists retentionLocked under required. The redacted views strip it, so the response served by the public routes does not validate against the schema those routes publish.

Where it bites

PassportResponse is the response schema for both the authenticated full read and three redacted doors:

  • GET /vault/public/dpp/{dppId}
  • GET /dpp/{dppId} (resolver)
  • GET /vault/credential/dpp/{dppId}

retentionLocked is classified Professional by ProductGroupAccessPolicy::passport_default, so it is removed for Audience::Public. Checking a real Public view against the required list, it is the only required property missing — the other ten (id, productName, productGroup, manufacturer, materials, status, schemaVersion, createdAt, updatedAt, version) are all present, so this is one field rather than a systematic mismatch.

Why nothing caught it

The OpenAPI contract test compares the status codes a handler can emit against the codes the spec documents. It does not validate a served body against the response schema, and no other gate does either. A consumer that generates a client from the published spec and validates responses gets a failure on every public read; one that does not validate never notices.

Pre-existing. This is not a consequence of #195 / #374 — the redaction has always stripped the field, and the schema has always required it. Found while checking whether #374's change to productGroupData contradicted the spec (it did not: that property is not in required, and its prose is corrected in #374).

The decision

  1. Drop retentionLocked from required. Truthful and one line. Weakens the contract for the authenticated read, where the field genuinely is always present.
  2. Split the schema — a full PassportResponse and a RedactedPassportResponse whose required is the intersection that survives Public redaction. Honest about there being two shapes, at the cost of a second schema and the drift risk that brings.
  3. Derive the redacted schema's required from the disclosure policy, so it cannot drift. Most work; the only option where adding a Professional classification to a currently-public field cannot silently invalidate the spec again.

I lean towards 1 for now and 3 when the spec is next generated rather than hand-maintained — the table in the route docs already carries a warning that hand-maintained API surface here drifts, and this is the same failure in a different file.

Worth pairing with

Whatever is chosen, the gap that let it through is the absence of any response-body-against-schema validation. One contract test that validates a public read's body against PassportResponse would have caught this and would catch the next one.

Activity

  1. added
    type/defectSomething published or encoded here is wrong or unbackable now
    surface/schemaMoves a JSON schema, instrument manifest or product group
    on Sep 19, 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

    surface/schemaMoves a JSON schema, instrument manifest or product grouptype/defectSomething published or encoded here is wrong or unbackable nowurgency/backlogOwed, unscheduled

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions