dpp_domain/instrument/reference.rs
1//! [`InstrumentRef`] — one act recorded on a passport as applicable to it.
2
3use serde::{Deserialize, Serialize};
4
5/// Where a recorded instrument came from.
6///
7/// Not a confidence ranking — both values are equally authoritative on a
8/// passport. What they distinguish is *who asserted it*, which matters when a
9/// record is audited years later.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum RecordedBasis {
14 /// Resolved from the instrument catalog at issuance: an act we hold a
15 /// manifest for, reaching this product group through a recorded binding.
16 Catalog,
17 /// Asserted by the economic operator placing the product on the market.
18 ///
19 /// **Not a fallback.** The catalog cannot be exhaustive: the Commission's
20 /// own preparatory analysis says horizontal ecodesign requirements cover
21 /// sets of products never shortlisted as product groups, so an act may
22 /// apply to a product while reaching no product group we model. An operator
23 /// who knows an act applies must be able to say so, and a model that only
24 /// accepted catalog-derived entries would force them to leave it out.
25 Operator,
26}
27
28/// One legal instrument recorded on a passport as applicable to it.
29///
30/// # Recorded, never recomputed
31///
32/// The applicable set is fixed when the product is placed on the market —
33/// `placedOnMarketDate` on the passport is the same moment — and is never
34/// re-derived afterwards. Two reasons, and the second is the load-bearing one:
35///
36/// 1. **The law that governs a product is the law at placing on the market.**
37/// Recomputing later would silently re-govern a published record by acts
38/// adopted after it was issued.
39/// 2. **There is no function to recompute it with.** Applicable instruments are
40/// not derivable from the product group — see [`RecordedBasis::Operator`] —
41/// so any "refresh" would quietly *narrow* the set to whatever the catalog
42/// happens to know, dropping exactly the entries a human had to supply.
43///
44/// It is therefore a protected field: not patchable, not in the
45/// retention-mutable set, and corrected only by superseding the passport with a
46/// new version. A mis-recorded legal basis is a fact about a published record,
47/// and the way to fix a published record is to publish a corrected one.
48///
49/// # Why there is no timestamp here
50///
51/// An earlier shape carried a per-entry `recordedAt`. It was **redundant** —
52/// the whole set is written at issuance, so every entry's timestamp equalled the
53/// passport's own `createdAt` — and a redundant copy of a date is a date that
54/// can disagree with the one it duplicates.
55///
56/// Removing it also sidesteps a live hazard worth knowing about: the access
57/// filter classifies nested keys **by name, not by path**, using a policy built
58/// from the *product group's* schema. The battery schema declares its own
59/// `recordedAt` as individual-tier data (Annex XIII point 4), so this field's
60/// timestamp inherited that class and was redacted out of every public battery
61/// projection — leaving a document that no longer deserialised. Any envelope
62/// field whose nested key name collides with a product-group field name will hit
63/// the same thing.
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct InstrumentRef {
67 /// The instrument's catalog id, e.g. `"battery-reg-2023-1542"`.
68 ///
69 /// Free-form rather than validated against the catalog, for the same reason
70 /// [`RecordedBasis::Operator`] exists: an act the catalog does not model is
71 /// still an act, and refusing to record it would lose the one piece of
72 /// information nothing else can supply.
73 pub instrument: String,
74 /// Who asserted that this act applies.
75 pub recorded: RecordedBasis,
76}
77
78impl InstrumentRef {
79 /// A reference resolved from the instrument catalog.
80 #[must_use]
81 pub fn from_catalog(instrument: impl Into<String>) -> Self {
82 Self {
83 instrument: instrument.into(),
84 recorded: RecordedBasis::Catalog,
85 }
86 }
87
88 /// A reference asserted by the economic operator.
89 #[must_use]
90 pub fn from_operator(instrument: impl Into<String>) -> Self {
91 Self {
92 instrument: instrument.into(),
93 recorded: RecordedBasis::Operator,
94 }
95 }
96}