Skip to main content

powerio_pkg/
provenance.rs

1//! Producer, origin, source descriptors, and source maps.
2//!
3//! These answer the trust questions a compiler artifact must answer: which tool
4//! produced it, what the source was, and which canonical field came from which
5//! source record by what kind of mapping.
6
7use std::collections::BTreeMap;
8
9use serde::{Deserialize, Serialize};
10
11/// The tool and build that produced the package.
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Producer {
14    pub tool: String,
15    pub version: String,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub git_commit: Option<String>,
18    #[serde(default, skip_serializing_if = "Vec::is_empty")]
19    pub features: Vec<String>,
20}
21
22impl Producer {
23    /// The producer for packages built by this crate version of PowerIO.
24    pub fn powerio() -> Self {
25        Self {
26            tool: "powerio".to_owned(),
27            version: env!("CARGO_PKG_VERSION").to_owned(),
28            git_commit: None,
29            features: Vec::new(),
30        }
31    }
32}
33
34/// Where the package came from. Internally tagged on `kind` in JSON, so a reader
35/// distinguishes an in-memory model, a single text file (with or without
36/// retained source), a folder dataset, a partially decoded binary, a derived
37/// product of a lowering pass, or a composite of several sources.
38///
39/// The `hash` field is named consistently across all `Origin` variants.
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(tag = "kind", rename_all = "snake_case")]
42#[non_exhaustive]
43pub enum Origin {
44    /// Built in process, no source artifact.
45    InMemory,
46    File {
47        path: String,
48        format: String,
49        #[serde(default, skip_serializing_if = "Option::is_none")]
50        hash: Option<String>,
51        /// Whether the original source text was retained for a byte-exact
52        /// same-format echo. The retained text itself is not embedded in the
53        /// package; this only records that it exists at the frontend.
54        #[serde(default)]
55        retained_source: bool,
56    },
57    Folder {
58        path: String,
59        format: String,
60        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
61        file_hashes: BTreeMap<String, String>,
62    },
63    BinaryFile {
64        path: String,
65        format: String,
66        #[serde(default, skip_serializing_if = "Option::is_none")]
67        hash: Option<String>,
68        #[serde(default, skip_serializing_if = "Vec::is_empty")]
69        decoded_sections: Vec<String>,
70    },
71    /// A model produced by a lowering/normalization pass from another package.
72    Derived {
73        #[serde(default, skip_serializing_if = "Option::is_none")]
74        parent_package_id: Option<String>,
75        pass: String,
76        #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
77        options: serde_json::Map<String, serde_json::Value>,
78    },
79    /// Several sources combined, e.g. a static case plus a profile set.
80    Composite { sources: Vec<String> },
81}
82
83/// A declared source artifact, referenced from source maps and diagnostics by
84/// its `id`. (`sources[]` in the package.)
85#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
86pub struct SourceDescriptor {
87    pub id: String,
88    pub kind: String,
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub path: Option<String>,
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub format: Option<String>,
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub hash: Option<String>,
95}
96
97/// A pointer into one source artifact: where a canonical field came from.
98#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
99pub struct SourceRef {
100    pub source_id: String,
101    #[serde(default, skip_serializing_if = "Option::is_none")]
102    pub line: Option<u32>,
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub column: Option<u32>,
105    /// Byte offset, for binary sources.
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub byte_offset: Option<u64>,
108    /// Record / section / object type, e.g. `bus`.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub record: Option<String>,
111    /// Canonical field / property name, e.g. `vm`.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub field: Option<String>,
114    /// Raw token / value, when safe to embed.
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub raw_token: Option<String>,
117}
118
119impl SourceRef {
120    /// Create a reference to a declared source artifact.
121    pub fn new(source_id: impl Into<String>) -> Self {
122        Self {
123            source_id: source_id.into(),
124            line: None,
125            column: None,
126            byte_offset: None,
127            record: None,
128            field: None,
129            raw_token: None,
130        }
131    }
132
133    /// Set the field or property name.
134    #[must_use]
135    pub fn with_field(mut self, field: impl Into<String>) -> Self {
136        self.field = Some(field.into());
137        self
138    }
139
140    /// Set the source record, section, or object type.
141    #[must_use]
142    pub fn with_record(mut self, record: impl Into<String>) -> Self {
143        self.record = Some(record.into());
144        self
145    }
146
147    /// Set the source line number.
148    #[must_use]
149    pub fn with_line(mut self, line: u32) -> Self {
150        self.line = Some(line);
151        self
152    }
153}
154
155/// How a canonical field relates to its source value.
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158#[non_exhaustive]
159pub enum MappingKind {
160    /// Copied verbatim from the source.
161    Exact,
162    /// Materialized from a format default rather than the source text.
163    Defaulted,
164    /// Inferred from other source data.
165    Inferred,
166    /// Converted into canonical units (e.g. ohms to per unit).
167    ConvertedUnits,
168    /// Produced by a lowering pass (e.g. positive-sequence equivalent).
169    Lowered,
170    /// One canonical field aggregated from several source records.
171    Aggregated,
172    /// One source record split into several canonical fields/elements.
173    Split,
174    /// Synthesized with no direct source (e.g. a generated bus id).
175    Synthetic,
176    /// Extra data from the source preserved verbatim.
177    RetainedExtra,
178}
179
180/// How confident the source map entry is.
181#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
182#[serde(rename_all = "snake_case")]
183pub enum Confidence {
184    Exact,
185    High,
186    Medium,
187    Low,
188}
189
190/// One `element_path -> source` mapping.
191#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
192pub struct SourceMapEntry {
193    /// JSON pointer (or best-effort locator) into the package payload.
194    pub element_path: String,
195    pub source_ref: SourceRef,
196    pub mapping_kind: MappingKind,
197    pub confidence: Confidence,
198}