feat(domain)!: say who is answerable, and under which law - #242
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 325 |
| Duplication | 106 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
Self-review caught a real defect in this PR, fixed in
Net effect: a new envelope field classified nowhere, which Public happens to be the right answer here, and now it is stated rather than defaulted: The failure message on that test already anticipates this — "or One adjacent correction in the same list: |
Annex III(k) asks for the responsible operator's name, contact details and identifier; none of the three was in the passport, and which law makes them responsible is not derivable from the product group.
responsibleOperator was absent from PASSPORT_WIRE_KEYS because the fully-populated helper left it None, so redaction defaulted it to Public and the completeness gate could not see it.
b447409 to
06fbda7
Compare
Closes #233. Built on the OJ read posted on the issue — sources are the original OJ L, 2024/1781, 28.6.2024, the consolidation 02024R1781-20240628, CELEX 32019R1020 and CELEX 32023R0988, all now held locally rather than cited from memory.
What the passport could not say
Annex III(k) asks for the name, contact details and unique operator identifier of the economic operator answerable for the product. None of the three was in the passport:
operator_identifierlooks like the third and is not — its own doc comment says it is the publisher, frozen at publish, explicitly not who is responsible now;ResponsibleOperatorinsideTransferChain, which is a separate table and never reaches the passport document or the public view;Passport::responsible_operatorcarries all three by value, the same choicefacilitymakes and for the same reason.Three things the issue did not anticipate
Art. 4 reaches two of our twelve product groups. Art. 4(5) of 2019/1020 limits the article to a closed list of instruments; only construction (305/2011) and toys (2009/48/EC) are on it. So the field cannot be named or documented as "the Art. 4 operator" — for ten of twelve groups that citation would simply be false.
ResponsibilityBasisrecords which limb of point (k) applies, and the catch-all carries its own citation.OperatorRolewas missing one of the four roles Art. 4(2) names. Manufacturer-in-Union, importer-where-the-manufacturer-is-not, authorised representative, and fulfilment service provider where no other is established in the Union — the fallback that stops a product having no answerable party at all. It had no variant, so that case could not be written down. MeanwhileDistributoris in our enum and is not one of the four; the existing enum is a supply-chain taxonomy that overlaps this set without coinciding, and the doc now says so.Frozen vs current was already answered. Art. 9(1): "The data in the digital product passport shall be accurate, complete and up to date." So point (k) wants the current operator, and
operator_identifieris correctly documented as a different, frozen fact. Since a published passport is immutable, "up to date" means a corrected successor when responsibility moves — which the amend route now provides.A citation defect worth knowing about
Annex III(k) prints "Article 15 of Regulation (EU) 2023/988". Article 15 of that regulation is Cooperation of economic operators with market surveillance authorities; the responsible-person provision is Article 16. Checked in both the original OJ and the consolidation — not a transcription artefact.
Nothing turns on it: Art. 16(1) applies Art. 4(2) and (3) of 2019/1020 wholesale, so both limbs land on the same person performing the same tasks. The variant is named
GeneralProductSafetyrather than for an article number so it need not choose between the citation as printed and the provision as intended.Why the module moved
passportis tier 4,transferis tier 5, and imports only point up the ladder — so the aggregate could not reach a type the satellite owned. This is not a workaround for the gate; it is what the placement rule prescribes. A thing used by two modules belongs at their nearest common parent, and an economic operator is a value object with no aggregate of its own: tier 2, besidemanufacturerandfacility. The chain records who has been responsible, the passport states who is, and neither owns the concept.Crate-root re-exports are unchanged, so
use dpp_domain::{ResponsibleOperator, OperatorRole}still compiles; onlydpp_domain::transfer::operator::*paths are gone.Contact details are a struct
Four provisions converge on one shape, verbatim: ESPR Art. 27(6) (manufacturer), Art. 29(3) (importer), Art. 4(4) of 2019/1020 and Art. 16(3) of 2023/988 — name, registered trade name or registered trade mark, postal address, electronic address.
electronic_addressis deliberately notdid: a DID document resolves keys, and nothing in it need be a mailbox, so reading it as a contact channel would satisfy the obligation on paper and not in fact.Not in this PR
ManufacturerInfois two fields short of Art. 27(6) — no registered trade name, no electronic contact — and 27(6)(a) is an explicit obligation to carry both on the public part of the DPP. Filed as #241 rather than folded in: different provision, different type, and #239 is already touching that struct.Green
just checkgreen, 1288 tests. Fourteen new tests across the three new modules cover the Art. 4(2) role set, the fallback role, the basis round-trip and its citation,role_fits_basisin both directions, and — the one that matters for a persisted shape — an operator stored before the contact fields existed still reading, asNone.