dpp_domain/catalog/descriptor.rs
1//! [`SectorDescriptor`] — a single sector's catalog entry.
2
3use serde::{Deserialize, Serialize};
4
5use super::regime::Regime;
6use super::status::RegulatoryStatus;
7
8/// A single sector's catalog entry — the canonical record every component
9/// (schema registry, plugin host, passport model) resolves against.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct SectorDescriptor {
13 /// Canonical sector key, e.g. `"battery"`, `"unsold-goods"`. Matches the
14 /// schema-registry sector key and the plugin's `meta().sector`.
15 pub key: String,
16 /// Human-readable title.
17 pub title: String,
18 /// Regulatory status — gates whether determinations are binding.
19 pub status: RegulatoryStatus,
20 /// Which EU legal instrument family this sector derives from.
21 ///
22 /// Orthogonal to [`Self::status`]: the regime says *which law*, the status
23 /// says *whether it binds yet*. Determination gating must never branch on
24 /// this field.
25 pub regime: Regime,
26 /// EU legal instrument(s) this sector derives from.
27 pub legal_basis: Vec<String>,
28 /// ISO-8601 date the **passport obligation** applies from, when known.
29 ///
30 /// Scope note: this is when the DPP itself becomes mandatory. It is **not**
31 /// the determination gate and must not be used as one — a regulation may
32 /// bind long before its passport is required (see [`RegulatoryStatus`]).
33 /// Where a sector carries several obligations with different dates, this
34 /// records the earliest that binds an in-scope operator; the manifest
35 /// `notes` carry the rest.
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub dpp_applies_from: Option<String>,
38 /// Minimum data retention in years required by the applicable act.
39 pub retention_years: u32,
40 /// Schema versions available for this sector (semver strings).
41 pub schema_versions: Vec<String>,
42 /// The schema version applicable to *new* passports in this sector right
43 /// now. Decouples "current" from "latest embedded" so a future schema can
44 /// ship embedded without becoming current until its act is in force. Must
45 /// be one of `schema_versions`.
46 pub current_schema_version: String,
47 /// Product categories *within* this sector — sub-types a plugin may branch
48 /// on, never dispatch keys. See `DATA-MODEL.md` §3.5.
49 #[serde(default)]
50 pub product_categories: Vec<String>,
51 /// Per-field [`Disclosure`](crate::domain::identity::Disclosure) class for
52 /// this sector's data: field name → class; unlisted fields default to
53 /// public. Not an ordering — a class names which audiences may see the
54 /// field, and the audiences do not nest. Universal conformity fields
55 /// (signatures, audit trails) are folded in by the access-policy engine, so
56 /// they are not repeated per sector here.
57 #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
58 pub disclosure: std::collections::HashMap<String, crate::domain::identity::Disclosure>,
59 /// Plugin that handles this sector (crate / filename stem, e.g.
60 /// `"sector-battery"`). `None` if no plugin is bound yet.
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub plugin: Option<String>,
63 /// Free-text regulatory note (effective dates, scope, caveats).
64 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub notes: Option<String>,
66}