Skip to content

Two new core passport fields have no writer on this side #256

Description

@LKSNDRTMLKV

The next dpp-core release adds two envelope fields to the passport. Both are Option, so the repin compiles without touching either — and both stay None forever unless this repo populates them. A field that exists and is never written is the failure #247 was about; this issue exists so the same shape is not recreated by a repin.

Neither can be done before the repin. Both should be done in the same release as it.


1. manufacturer.country

ManufacturerInfo gains country: Option<String> — an ISO 3166-1 alpha-2 code, validated against the assigned set rather than a two-letter shape, so XX and QZ are refused.

Five importers currently write the country into address:

Validator What it does today
aluminium.rs address: manufacturer_country
steel.rs address: manufacturer_country
textile.rs address: manufacturer_country
tyre.rs address: manufacturer_country
battery.rs address: manufacturer_address, aliased from manufacturerCountry, manufacturerAddress, manufacturer_country and manufacturer_address

The battery one is the worst of the five and the reason this is not a find-replace: the same field accepts a two-letter code and a full postal address under one alias list, so nothing downstream can tell which a given passport carries. Untangling it means splitting the alias list — manufacturerCountry/manufacturer_country to country, manufacturerAddress/manufacturer_address to address — and deciding what a row that supplies only a country should put in address, which is a required non-empty string.

That last question is the real work. Options, none obviously right:

  • Leave address as the country string and also set country, so nothing breaks and the country is at least addressable. Duplicated, but honest about what the row actually supplied.
  • Make address optional in core. Envelope change, needs a core release, and Passport::validate currently refuses an empty one.
  • Refuse a row that gives no address. Correct, and it breaks every existing battery/aluminium/steel/textile/tyre template and any operator using them.

Also in scope: the CSV templates the integrator generates, the OpenAPI ManufacturerInfo schema and the contract fixtures, and a decision on the four product groups whose validators do not collect a manufacturer country at all.

Explicitly not in scope: back-filling stored passports. The country is inside free text written by importers over an unknown period, and parsing it back out would be guessing. Existing records keep whatever address holds.

2. responsible_operator

Passport gains responsible_operator: Option<ResponsibleOperatorSnapshot> — the name, contact details and unique operator identifier of whoever is answerable for the product, plus the legal basis that makes them answerable. It is the passport's answer to Annex III(k), which nothing in the passport answered before.

The good news: operator_config already holds most of it. legal_name, trade_name, address, country, contact_email and did_web_url map onto the snapshot's name, registered trade name, postal address, country, electronic address and DID with nothing missing.

Two things it does not hold, and neither is derivable:

  • The role. The regulation admits four: a manufacturer established in the Union; an importer, where the manufacturer is not; an authorised representative; and a fulfilment service provider where no other is established in the Union. Which one an operator is, is a fact about their business, not about the node.
  • The legal basis. Annex III(k) is a disjunction, and which limb applies depends on the product's governing instruments. It reaches construction and toys for one basis and neither for the other ten product groups this node models, so it cannot be inferred from the product group.

Both are operator configuration. That means new operator_config columns, a migration, PATCH /api/v1/operator fields, the CLI's bootstrap prompts, and a decision about what publish does when they are unset — refuse, or publish without the snapshot.

Where the value is set. publish, not create: it is a statement about the passport being issued, and the operator identity is already read there. Worth checking against the transfer chain — TransferChain::current_operator() is the authoritative record of who is responsible now, so an amended passport following a transfer should take the snapshot from the chain rather than from operator_config.

Three things to verify rather than assume:

  • The public view. The field is classified public upstream, which is correct — the point of recording it is that a reader holding the product can reach the answerable party. But filter_by_audience matches by leaf name, and the snapshot nests name, country, did, role, postalAddress and electronicAddress. A policy entry gating any of those leaf names elsewhere would silently drop the same leaf here. There is already a test in this repo's upstream dependency demonstrating that collision with address.
  • The OpenAPI contract fixtures, which are exhaustive literals and will need the new shape including every role and basis variant.
  • The continuity snapshot renderer, if the responsible operator should appear on the rendered public page at all.

Why one issue and not two

They land in the same repin, they touch overlapping surfaces (operator_config, the OpenAPI description, the contract fixtures), and both are the same kind of debt: a core field with no engine consumer. Splitting them would mean two PRs racing for the same files. Split at PR time if the diff argues for it.

Green

An imported battery row supplying manufacturerCountry=DE produces a passport whose manufacturer.country is "DE" and whose address does not contain a bare country code; a published passport carries a responsibleOperator whose role and basis came from configuration rather than a default; and the public view of that passport still carries every leaf of it.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions