Skip to main content

sbom_tools/model/
metadata.rs

1//! Metadata structures for SBOM documents and components.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6/// SBOM format type
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub enum SbomFormat {
9    CycloneDx,
10    Spdx,
11}
12
13impl std::fmt::Display for SbomFormat {
14    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15        match self {
16            Self::CycloneDx => write!(f, "CycloneDX"),
17            Self::Spdx => write!(f, "SPDX"),
18        }
19    }
20}
21
22/// Document-level metadata
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct DocumentMetadata {
25    /// SBOM format type
26    pub format: SbomFormat,
27    /// Format version (e.g., "1.5" for `CycloneDX`)
28    pub format_version: String,
29    /// Specification version
30    pub spec_version: String,
31    /// Serial number or document namespace
32    pub serial_number: Option<String>,
33    /// Creation timestamp
34    pub created: DateTime<Utc>,
35    /// Creators/authors
36    pub creators: Vec<Creator>,
37    /// Document name
38    pub name: Option<String>,
39    /// Security contact for vulnerability disclosure (CRA requirement)
40    pub security_contact: Option<String>,
41    /// URL for vulnerability disclosure policy/portal
42    pub vulnerability_disclosure_url: Option<String>,
43    /// Support/end-of-life date for security updates
44    pub support_end_date: Option<DateTime<Utc>>,
45    /// SBOM lifecycle phase (e.g., "build", "pre-build", "operations")
46    pub lifecycle_phase: Option<String>,
47    /// Self-declared completeness level (from CycloneDX compositions)
48    pub completeness_declaration: CompletenessDeclaration,
49    /// Digital signature information (from CycloneDX signature field)
50    pub signature: Option<SignatureInfo>,
51    /// Distribution classification (e.g., TLP: CLEAR, GREEN, AMBER, RED)
52    pub distribution_classification: Option<String>,
53    /// Number of data provenance citations (CycloneDX 1.7+)
54    pub citations_count: usize,
55}
56
57/// Self-declared completeness level of the SBOM
58///
59/// Derived from CycloneDX compositions aggregate field, which declares
60/// whether the SBOM inventory is complete, incomplete, or unknown.
61#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
62#[non_exhaustive]
63pub enum CompletenessDeclaration {
64    /// SBOM author declares the inventory is complete
65    Complete,
66    /// SBOM author declares the inventory includes only first-party components
67    IncompleteFirstPartyOnly,
68    /// SBOM author declares the inventory includes only third-party components
69    IncompleteThirdPartyOnly,
70    /// SBOM author declares the inventory is incomplete
71    Incomplete,
72    /// No completeness declaration or explicitly unknown
73    #[default]
74    Unknown,
75    /// Completeness was declared but with an unrecognized value
76    NotSpecified,
77}
78
79impl std::fmt::Display for CompletenessDeclaration {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            Self::Complete => write!(f, "complete"),
83            Self::IncompleteFirstPartyOnly => write!(f, "incomplete (first-party only)"),
84            Self::IncompleteThirdPartyOnly => write!(f, "incomplete (third-party only)"),
85            Self::Incomplete => write!(f, "incomplete"),
86            Self::Unknown => write!(f, "unknown"),
87            Self::NotSpecified => write!(f, "not specified"),
88        }
89    }
90}
91
92/// Digital signature information for the SBOM document
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct SignatureInfo {
95    /// Signature algorithm (e.g., "ES256", "RS256", "Ed25519")
96    pub algorithm: String,
97    /// Whether the signature appears structurally valid (has algorithm + value)
98    pub has_value: bool,
99}
100
101impl Default for DocumentMetadata {
102    fn default() -> Self {
103        Self {
104            format: SbomFormat::CycloneDx,
105            format_version: String::new(),
106            spec_version: String::new(),
107            serial_number: None,
108            created: Utc::now(),
109            creators: Vec::new(),
110            name: None,
111            security_contact: None,
112            vulnerability_disclosure_url: None,
113            support_end_date: None,
114            lifecycle_phase: None,
115            completeness_declaration: CompletenessDeclaration::default(),
116            signature: None,
117            distribution_classification: None,
118            citations_count: 0,
119        }
120    }
121}
122
123/// Creator information
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct Creator {
126    /// Creator type
127    pub creator_type: CreatorType,
128    /// Creator name or identifier
129    pub name: String,
130    /// Optional email
131    pub email: Option<String>,
132}
133
134/// Type of creator
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
136pub enum CreatorType {
137    Person,
138    Organization,
139    Tool,
140}
141
142/// Organization/supplier information
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct Organization {
145    /// Organization name
146    pub name: String,
147    /// Contact URLs
148    pub urls: Vec<String>,
149    /// Contact emails
150    pub contacts: Vec<Contact>,
151}
152
153impl Organization {
154    /// Create a new organization with just a name
155    #[must_use]
156    pub const fn new(name: String) -> Self {
157        Self {
158            name,
159            urls: Vec::new(),
160            contacts: Vec::new(),
161        }
162    }
163}
164
165/// Contact information
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
167pub struct Contact {
168    /// Contact name
169    pub name: Option<String>,
170    /// Email address
171    pub email: Option<String>,
172    /// Phone number
173    pub phone: Option<String>,
174}
175
176/// Component type classification
177#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
178#[non_exhaustive]
179pub enum ComponentType {
180    Application,
181    Framework,
182    #[default]
183    Library,
184    Container,
185    OperatingSystem,
186    Device,
187    Firmware,
188    File,
189    Data,
190    MachineLearningModel,
191    Platform,
192    DeviceDriver,
193    Cryptographic,
194    Other(String),
195}
196
197impl std::fmt::Display for ComponentType {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        match self {
200            Self::Application => write!(f, "application"),
201            Self::Framework => write!(f, "framework"),
202            Self::Library => write!(f, "library"),
203            Self::Container => write!(f, "container"),
204            Self::OperatingSystem => write!(f, "operating-system"),
205            Self::Device => write!(f, "device"),
206            Self::Firmware => write!(f, "firmware"),
207            Self::File => write!(f, "file"),
208            Self::Data => write!(f, "data"),
209            Self::MachineLearningModel => write!(f, "machine-learning-model"),
210            Self::Platform => write!(f, "platform"),
211            Self::DeviceDriver => write!(f, "device-driver"),
212            Self::Cryptographic => write!(f, "cryptographic"),
213            Self::Other(s) => write!(f, "{s}"),
214        }
215    }
216}
217
218/// Cryptographic hash
219#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
220pub struct Hash {
221    /// Hash algorithm
222    pub algorithm: HashAlgorithm,
223    /// Hash value (hex encoded)
224    pub value: String,
225}
226
227impl Hash {
228    /// Create a new hash
229    #[must_use]
230    pub const fn new(algorithm: HashAlgorithm, value: String) -> Self {
231        Self { algorithm, value }
232    }
233}
234
235/// Hash algorithm types
236#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
237pub enum HashAlgorithm {
238    Md5,
239    Sha1,
240    Sha256,
241    Sha384,
242    Sha512,
243    Sha3_256,
244    Sha3_384,
245    Sha3_512,
246    Blake2b256,
247    Blake2b384,
248    Blake2b512,
249    Blake3,
250    Streebog256,
251    Streebog512,
252    Other(String),
253}
254
255impl std::fmt::Display for HashAlgorithm {
256    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257        match self {
258            Self::Md5 => write!(f, "MD5"),
259            Self::Sha1 => write!(f, "SHA-1"),
260            Self::Sha256 => write!(f, "SHA-256"),
261            Self::Sha384 => write!(f, "SHA-384"),
262            Self::Sha512 => write!(f, "SHA-512"),
263            Self::Sha3_256 => write!(f, "SHA3-256"),
264            Self::Sha3_384 => write!(f, "SHA3-384"),
265            Self::Sha3_512 => write!(f, "SHA3-512"),
266            Self::Blake2b256 => write!(f, "BLAKE2b-256"),
267            Self::Blake2b384 => write!(f, "BLAKE2b-384"),
268            Self::Blake2b512 => write!(f, "BLAKE2b-512"),
269            Self::Blake3 => write!(f, "BLAKE3"),
270            Self::Streebog256 => write!(f, "Streebog-256"),
271            Self::Streebog512 => write!(f, "Streebog-512"),
272            Self::Other(s) => write!(f, "{s}"),
273        }
274    }
275}
276
277/// External reference
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct ExternalReference {
280    /// Reference type
281    pub ref_type: ExternalRefType,
282    /// URL or locator
283    pub url: String,
284    /// Comment or description
285    pub comment: Option<String>,
286    /// Hash of the referenced content
287    pub hashes: Vec<Hash>,
288}
289
290/// External reference types
291#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
292pub enum ExternalRefType {
293    Vcs,
294    IssueTracker,
295    Website,
296    Advisories,
297    Bom,
298    MailingList,
299    Social,
300    Chat,
301    Documentation,
302    Support,
303    SourceDistribution,
304    BinaryDistribution,
305    License,
306    BuildMeta,
307    BuildSystem,
308    ReleaseNotes,
309    SecurityContact,
310    ModelCard,
311    Log,
312    Configuration,
313    Evidence,
314    Formulation,
315    Attestation,
316    ThreatModel,
317    AdversaryModel,
318    RiskAssessment,
319    VulnerabilityAssertion,
320    ExploitabilityStatement,
321    Pentest,
322    StaticAnalysis,
323    DynamicAnalysis,
324    RuntimeAnalysis,
325    ComponentAnalysis,
326    Maturity,
327    Certification,
328    QualityMetrics,
329    Codified,
330    Citation,
331    Patent,
332    PatentAssertion,
333    PatentFamily,
334    Other(String),
335}
336
337impl std::fmt::Display for ExternalRefType {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        match self {
340            Self::Vcs => write!(f, "vcs"),
341            Self::IssueTracker => write!(f, "issue-tracker"),
342            Self::Website => write!(f, "website"),
343            Self::Advisories => write!(f, "advisories"),
344            Self::Bom => write!(f, "bom"),
345            Self::MailingList => write!(f, "mailing-list"),
346            Self::Social => write!(f, "social"),
347            Self::Chat => write!(f, "chat"),
348            Self::Documentation => write!(f, "documentation"),
349            Self::Support => write!(f, "support"),
350            Self::SourceDistribution => write!(f, "distribution"),
351            Self::BinaryDistribution => write!(f, "distribution-intake"),
352            Self::License => write!(f, "license"),
353            Self::BuildMeta => write!(f, "build-meta"),
354            Self::BuildSystem => write!(f, "build-system"),
355            Self::ReleaseNotes => write!(f, "release-notes"),
356            Self::SecurityContact => write!(f, "security-contact"),
357            Self::ModelCard => write!(f, "model-card"),
358            Self::Log => write!(f, "log"),
359            Self::Configuration => write!(f, "configuration"),
360            Self::Evidence => write!(f, "evidence"),
361            Self::Formulation => write!(f, "formulation"),
362            Self::Attestation => write!(f, "attestation"),
363            Self::ThreatModel => write!(f, "threat-model"),
364            Self::AdversaryModel => write!(f, "adversary-model"),
365            Self::RiskAssessment => write!(f, "risk-assessment"),
366            Self::VulnerabilityAssertion => write!(f, "vulnerability-assertion"),
367            Self::ExploitabilityStatement => write!(f, "exploitability-statement"),
368            Self::Pentest => write!(f, "pentest-report"),
369            Self::StaticAnalysis => write!(f, "static-analysis-report"),
370            Self::DynamicAnalysis => write!(f, "dynamic-analysis-report"),
371            Self::RuntimeAnalysis => write!(f, "runtime-analysis-report"),
372            Self::ComponentAnalysis => write!(f, "component-analysis-report"),
373            Self::Maturity => write!(f, "maturity-report"),
374            Self::Certification => write!(f, "certification-report"),
375            Self::QualityMetrics => write!(f, "quality-metrics"),
376            Self::Codified => write!(f, "codified"),
377            Self::Citation => write!(f, "citation"),
378            Self::Patent => write!(f, "patent"),
379            Self::PatentAssertion => write!(f, "patent-assertion"),
380            Self::PatentFamily => write!(f, "patent-family"),
381            Self::Other(s) => write!(f, "{s}"),
382        }
383    }
384}
385
386/// Dependency relationship type
387#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
388#[non_exhaustive]
389pub enum DependencyType {
390    /// Direct dependency
391    DependsOn,
392    /// Optional dependency
393    OptionalDependsOn,
394    /// Development dependency
395    DevDependsOn,
396    /// Build dependency
397    BuildDependsOn,
398    /// Test dependency
399    TestDependsOn,
400    /// Runtime dependency
401    RuntimeDependsOn,
402    /// Provided dependency (e.g., Java provided scope)
403    ProvidedDependsOn,
404    /// Describes relationship (SPDX)
405    Describes,
406    /// Generates relationship
407    Generates,
408    /// Contains relationship
409    Contains,
410    /// Ancestor of
411    AncestorOf,
412    /// Variant of
413    VariantOf,
414    /// Distribution artifact
415    DistributionArtifact,
416    /// Patch for
417    PatchFor,
418    /// Copy of
419    CopyOf,
420    /// File added
421    FileAdded,
422    /// File deleted
423    FileDeleted,
424    /// File modified
425    FileModified,
426    /// Dynamic link
427    DynamicLink,
428    /// Static link
429    StaticLink,
430    /// Provides (CycloneDX 1.7: library provides/implements a crypto asset)
431    Provides,
432    /// Other relationship
433    Other(String),
434}
435
436impl std::fmt::Display for DependencyType {
437    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
438        match self {
439            Self::DependsOn => write!(f, "depends-on"),
440            Self::OptionalDependsOn => write!(f, "optional-depends-on"),
441            Self::DevDependsOn => write!(f, "dev-depends-on"),
442            Self::BuildDependsOn => write!(f, "build-depends-on"),
443            Self::TestDependsOn => write!(f, "test-depends-on"),
444            Self::RuntimeDependsOn => write!(f, "runtime-depends-on"),
445            Self::ProvidedDependsOn => write!(f, "provided-depends-on"),
446            Self::Describes => write!(f, "describes"),
447            Self::Generates => write!(f, "generates"),
448            Self::Contains => write!(f, "contains"),
449            Self::AncestorOf => write!(f, "ancestor-of"),
450            Self::VariantOf => write!(f, "variant-of"),
451            Self::DistributionArtifact => write!(f, "distribution-artifact"),
452            Self::PatchFor => write!(f, "patch-for"),
453            Self::CopyOf => write!(f, "copy-of"),
454            Self::FileAdded => write!(f, "file-added"),
455            Self::FileDeleted => write!(f, "file-deleted"),
456            Self::FileModified => write!(f, "file-modified"),
457            Self::DynamicLink => write!(f, "dynamic-link"),
458            Self::StaticLink => write!(f, "static-link"),
459            Self::Provides => write!(f, "provides"),
460            Self::Other(s) => write!(f, "{s}"),
461        }
462    }
463}
464
465/// Dependency scope
466#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
467pub enum DependencyScope {
468    #[default]
469    Required,
470    Optional,
471    Excluded,
472}
473
474impl std::fmt::Display for DependencyScope {
475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476        match self {
477            Self::Required => write!(f, "required"),
478            Self::Optional => write!(f, "optional"),
479            Self::Excluded => write!(f, "excluded"),
480        }
481    }
482}
483
484/// Format-specific extensions that don't map to the canonical model
485#[derive(Debug, Clone, Default, Serialize, Deserialize)]
486pub struct FormatExtensions {
487    /// CycloneDX-specific extensions
488    pub cyclonedx: Option<serde_json::Value>,
489    /// SPDX-specific extensions
490    pub spdx: Option<serde_json::Value>,
491}
492
493/// Component-level extensions
494#[derive(Debug, Clone, Default, Serialize, Deserialize)]
495pub struct ComponentExtensions {
496    /// Properties from `CycloneDX`
497    pub properties: Vec<Property>,
498    /// Annotations from SPDX
499    pub annotations: Vec<Annotation>,
500    /// Raw extension data.
501    ///
502    /// Occupied by the SPDX-3 AI-profile bridge (`parsers::spdx3`), which mirrors
503    /// non-typed AI signals here in CycloneDX `mlModel.modelCard` layout so the
504    /// AI-readiness scorer can read them. Do NOT repurpose this for round-trip
505    /// preservation — use [`source_json`](Self::source_json) instead.
506    pub raw: Option<serde_json::Value>,
507    /// Verbatim source JSON object for this component, captured for cross-format
508    /// conversion fidelity.
509    ///
510    /// Opt-in and convert-only: populated solely when the `convert`/`--preserve`
511    /// path is active (see [`crate::serialization::emit`]), keeping it out of the
512    /// normal parse hot path so memory stays bounded. Boxed to keep
513    /// [`ComponentExtensions`] small when the slot is empty (the common case),
514    /// and skipped on serialization when absent.
515    #[serde(default, skip_serializing_if = "Option::is_none")]
516    pub source_json: Option<Box<serde_json::Value>>,
517}
518
519/// Key-value property
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct Property {
522    pub name: String,
523    pub value: String,
524}
525
526/// Annotation/comment
527#[derive(Debug, Clone, Serialize, Deserialize)]
528pub struct Annotation {
529    pub annotator: String,
530    pub annotation_date: DateTime<Utc>,
531    pub annotation_type: String,
532    pub comment: String,
533}
534
535/// Machine learning model metadata (CycloneDX 1.5+)
536///
537/// Structured information about trained ML models, including architecture,
538/// approach, quantization, and environmental impact.
539#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
540#[non_exhaustive]
541pub struct MlModelInfo {
542    /// ML approach type: "supervised", "unsupervised", "reinforcement-learning", "semi-supervised"
543    pub approach: Option<String>,
544    /// Architecture family: "transformer", "cnn", "rnn", "llm", "gan", etc.
545    pub architecture_family: Option<String>,
546    /// Architecture name: "bert", "gpt", "resnet", etc.
547    pub architecture_name: Option<String>,
548    /// ML task: "nlp", "computer-vision", "audio", "tabular", etc.
549    pub task: Option<String>,
550    /// Quantization mode: "int4", "int8", "fp16", "bf16", "fp32", "mixed", etc.
551    pub quantization: Option<String>,
552    /// Limitations or known constraints of the model
553    pub limitations: Option<String>,
554    /// Training datasets used for this model
555    pub training_datasets: Vec<DatasetRef>,
556    /// Energy consumed during training in kWh (approximate)
557    pub energy_kwh_training: Option<f64>,
558    /// URL to detailed model card (from ExternalRefType::ModelCard)
559    pub model_card_url: Option<String>,
560    /// Fairness assessments (CycloneDX 1.5+ `considerations.fairnessAssessments`).
561    /// SPDX 3.0 has no direct analogue; the nearest AI-profile signals are
562    /// normalized into this shape so cross-format scoring is symmetric.
563    pub fairness: Vec<FairnessAssessment>,
564    /// Ethical considerations. CycloneDX emits structured objects
565    /// (`considerations.ethicalConsiderations[]`); SPDX emits free strings.
566    /// Both are normalized into this single shape.
567    pub ethical_considerations: Vec<EthicalConsideration>,
568    /// Intended use-cases (CycloneDX `considerations.useCases`, SPDX `ai_domain` /
569    /// `ai_informationAboutApplication`).
570    pub use_cases: Vec<String>,
571    /// Quantitative performance metrics (CycloneDX
572    /// `modelCard.quantitativeAnalysis.performanceMetrics`, SPDX `ai_metric`).
573    pub performance_metrics: Vec<MetricEntry>,
574    /// Data-preprocessing steps applied to the model's inputs (SPDX 3.0 AI
575    /// profile `ai_modelDataPreprocessing`). Surfaced for the BSI/G7
576    /// SBOM-for-AI "Models" cluster; CycloneDX has no direct analogue.
577    pub data_preprocessing: Vec<String>,
578}
579
580/// A fairness assessment for an ML model (CycloneDX 1.5+
581/// `considerations.fairnessAssessments[]`).
582#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
583#[non_exhaustive]
584pub struct FairnessAssessment {
585    /// The group(s) potentially at risk for the identified fairness concern.
586    pub group_at_risk: Option<String>,
587    /// Expected benefits to the group at risk.
588    pub benefits: Option<String>,
589    /// Potential harms to the group at risk.
590    pub harms: Option<String>,
591    /// Strategy used to mitigate the identified harms.
592    pub mitigation_strategy: Option<String>,
593}
594
595/// An ethical consideration for an ML model. Normalized from CycloneDX structured
596/// objects (`{ name, mitigationStrategy }`) and SPDX free-text strings alike.
597#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
598#[non_exhaustive]
599pub struct EthicalConsideration {
600    /// Name / description of the ethical risk.
601    pub name: Option<String>,
602    /// Strategy used to mitigate the ethical risk (CycloneDX only).
603    pub mitigation_strategy: Option<String>,
604}
605
606/// A single quantitative performance metric (CycloneDX
607/// `quantitativeAnalysis.performanceMetrics[]`).
608#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
609#[non_exhaustive]
610pub struct MetricEntry {
611    /// Metric type (e.g. "accuracy", "F1", "precision").
612    pub metric_type: Option<String>,
613    /// Metric value, retained verbatim (string form is spec-conformant and
614    /// preserves precision / non-numeric values such as confidence intervals).
615    pub value: Option<String>,
616    /// Data slice the metric was computed over (CycloneDX `slice`).
617    pub slice: Option<String>,
618}
619
620/// Reference to a dataset used for training or evaluation
621#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
622#[non_exhaustive]
623pub struct DatasetRef {
624    /// BOM-ref / BOM-Link to a dataset component (CycloneDX `modelParameters.datasets` `{ref}` form)
625    pub reference: Option<String>,
626    /// Dataset name (from an inline `componentData` dataset)
627    pub name: Option<String>,
628    /// Package URL (PURL). Not part of the CycloneDX spec for datasets; retained for
629    /// non-spec emitters and is `None` for spec-conformant input.
630    pub purl: Option<String>,
631}
632
633/// Dataset component metadata (CycloneDX 1.5+ data type)
634///
635/// Structured information about datasets, including type, sensitivity,
636/// governance, and content properties.
637#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
638#[non_exhaustive]
639pub struct DatasetInfo {
640    /// Dataset type: "training", "testing", "validation", "evaluation"
641    pub dataset_type: Option<String>,
642    /// Sensitivity classifications: "sensitive", "confidential", "pii", etc.
643    pub sensitivity_classifications: Vec<String>,
644    /// Data governance owners/custodians
645    pub governance_owners: Vec<String>,
646    /// Intended use of the dataset (SPDX 3.0 Dataset profile
647    /// `dataset_intendedUse`). Part of the BSI/G7 SBOM-for-AI "Datasets"
648    /// cluster provenance / intended-use element.
649    pub intended_use: Option<String>,
650    /// Confidentiality level of the dataset (SPDX 3.0 Dataset profile
651    /// `dataset_confidentialityLevel`). Distinct from the sensitivity
652    /// classifications above, which already fold this in for AI-Act scoring;
653    /// retained verbatim here for the BSI sensitivity-classification element.
654    pub confidentiality_level: Option<String>,
655    /// Data-preprocessing steps applied to the dataset (SPDX 3.0 Dataset
656    /// profile `dataset_dataPreprocessing`). Surfaced for the BSI/G7
657    /// SBOM-for-AI "Datasets" cluster provenance element.
658    pub preprocessing: Vec<String>,
659    /// Anonymization methods applied to the dataset (SPDX 3.0 Dataset profile
660    /// `dataset_anonymizationMethodUsed`). Surfaced for the BSI/G7
661    /// SBOM-for-AI "Datasets" cluster provenance element.
662    pub anonymization: Vec<String>,
663}