Skip to main content

rite_runtime/
system_info.rs

1//! Structured machine, build, and environment facts surfaced to frontends.
2//!
3//! Two distinct shapes live here, on opposite sides of the data-flow boundary
4//! described in `docs/development/runtime-and-frontend.md`:
5//!
6//! - [`SystemInfo`] is **static identity**: the build that is running and the
7//!   host it runs on. Gathered once at startup. The same typed host shape
8//!   ([`HostInfo`]) is what the `machine_info` action records to the
9//!   transcript, so the model is defined once and shared.
10//! - [`Environment`] is the **live device inventory** (disks today;
11//!   peripherals and network later). It is deliberately shaped to be
12//!   re-emitted: a future observer can resend it and a frontend replaces its
13//!   view wholesale. Items carry a stable key for later diffing.
14//!
15//! Both travel as UI-only signals ([`crate::UiSignal`]); neither is ever
16//! written to the transcript. Recording machine identity as evidence is the
17//! `machine_info` action's job, not the frontend's.
18
19use std::fmt;
20
21use serde::{Deserialize, Serialize};
22
23/// Failure to parse one of this module's string-backed enums.
24///
25/// Shared across the enums defined here, mirroring the pattern in
26/// `rite-sdk` (`rite_sdk::ParseError`), which is not constructible outside
27/// its own crate.
28#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
29#[error("unknown value: {0:?}")]
30pub struct ParseError(String);
31
32/// Static build and host identity for the System tab and
33/// `rite version --verbose`.
34///
35/// Assembled by the CLI, the only crate that holds the build-time constants,
36/// and echoed to the frontend once via [`crate::UiSignal::SystemInfo`].
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct SystemInfo {
39    /// Identity of the running binary.
40    pub build: BuildInfo,
41    /// Identity of the host it runs on.
42    pub host: HostInfo,
43    /// Linked cryptographic backend libraries.
44    pub backends: Vec<BackendVersion>,
45}
46
47/// Identity of the running `rite` binary. A property of the artifact, set at
48/// compile time; the strongly verifiable end of the trust boundary (checkable
49/// against a release tag and build provenance).
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BuildInfo {
52    /// Crate version (`CARGO_PKG_VERSION`).
53    pub version: String,
54    /// Git commit the binary was built from, or `"unknown"`.
55    pub commit: String,
56    /// Commit date, or `"unknown"`.
57    pub commit_date: String,
58    /// Date the binary was built.
59    pub build_date: String,
60    /// Target triple.
61    pub target: String,
62    /// Build profile (`debug` / `release`).
63    pub profile: String,
64    /// Enabled cargo features, comma-separated.
65    pub features: String,
66    /// Compiler version string.
67    pub rustc: String,
68}
69
70/// A linked cryptographic backend library and its runtime version.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct BackendVersion {
73    /// Provider key (e.g. `"openssl"`).
74    pub provider: String,
75    /// Runtime-linked library version.
76    pub version: String,
77    /// How the library was linked (e.g. `"vendored"` / `"system"`).
78    pub source: Option<String>,
79}
80
81/// Identity of the host machine. A property of the environment, the weakly
82/// verifiable end of the trust boundary (an operator assertion that software
83/// can only partly corroborate).
84///
85/// Optional fields are populated depending on the gathering scope: a cheap
86/// startup snapshot fills only the always-present basics, while the
87/// `machine_info` action fills the full set.
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct HostInfo {
90    /// CPU architecture (`std::env::consts::ARCH`). Always present.
91    pub arch: String,
92    /// OS name (e.g. `"Linux"`, `"Darwin"`).
93    pub os: Option<String>,
94    /// Long OS version string.
95    pub os_version: Option<String>,
96    /// Kernel version.
97    pub kernel_version: Option<String>,
98    /// Hostname.
99    pub hostname: Option<String>,
100    /// Hashed machine identifier (SHA-256), when available.
101    pub machine_id: Option<String>,
102    /// CPU brand string.
103    pub cpu_model: Option<String>,
104    /// Platform security posture, when gathered at full scope.
105    pub hardening: Option<Hardening>,
106}
107
108/// Snapshot of platform memory- and DMA-protection features.
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct Hardening {
111    /// Hardware RAM encryption (AMD SME, Intel TME, Apple Secure Enclave).
112    pub ram_encryption: FeatureCheck,
113    /// IOMMU-based DMA protection.
114    pub dma_protection: FeatureCheck,
115    /// Zeroing of freed pages.
116    pub freed_page_zeroing: FeatureCheck,
117}
118
119/// A single security-feature determination: a machine-comparable status plus
120/// optional human context. Replaces the free-form status strings the
121/// `machine_info` action used to emit.
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct FeatureCheck {
124    /// Whether the feature is active.
125    pub status: FeatureStatus,
126    /// Human-readable context (e.g. `"AMD SME"`, `"enable TME in BIOS"`).
127    pub detail: Option<String>,
128}
129
130impl FeatureCheck {
131    /// Construct a check with no detail.
132    #[must_use]
133    pub fn new(status: FeatureStatus) -> Self {
134        Self {
135            status,
136            detail: None,
137        }
138    }
139
140    /// Construct a check carrying human context.
141    #[must_use]
142    pub fn with_detail(status: FeatureStatus, detail: impl Into<String>) -> Self {
143        Self {
144            status,
145            detail: Some(detail.into()),
146        }
147    }
148}
149
150/// Status of a platform security feature.
151///
152/// Serialized via its [`Display`](fmt::Display) form so the transcript carries
153/// a stable, comparable token rather than a sentence. Follows the
154/// Display/serde alignment convention used by `rite-sdk` enums.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(into = "String", try_from = "String")]
157#[non_exhaustive]
158pub enum FeatureStatus {
159    /// Present and active.
160    Active,
161    /// Supported by the platform but currently off.
162    Inactive,
163    /// Not available on this hardware or platform.
164    Unavailable,
165    /// Not a meaningful concept on this platform.
166    NotApplicable,
167    /// Could not be determined.
168    Unknown,
169}
170
171impl fmt::Display for FeatureStatus {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        let s = match self {
174            FeatureStatus::Active => "active",
175            FeatureStatus::Inactive => "inactive",
176            FeatureStatus::Unavailable => "unavailable",
177            FeatureStatus::NotApplicable => "not_applicable",
178            FeatureStatus::Unknown => "unknown",
179        };
180        f.write_str(s)
181    }
182}
183
184impl std::str::FromStr for FeatureStatus {
185    type Err = ParseError;
186
187    fn from_str(s: &str) -> Result<Self, Self::Err> {
188        match s {
189            "active" => Ok(Self::Active),
190            "inactive" => Ok(Self::Inactive),
191            "unavailable" => Ok(Self::Unavailable),
192            "not_applicable" => Ok(Self::NotApplicable),
193            "unknown" => Ok(Self::Unknown),
194            _ => Err(ParseError(s.to_owned())),
195        }
196    }
197}
198
199impl From<FeatureStatus> for String {
200    fn from(s: FeatureStatus) -> String {
201        s.to_string()
202    }
203}
204
205impl TryFrom<String> for FeatureStatus {
206    type Error = ParseError;
207
208    fn try_from(s: String) -> Result<Self, Self::Error> {
209        s.parse()
210    }
211}
212
213/// Live inventory of the machine's attached devices.
214///
215/// UI-only and never serialized. Shaped to be **re-emitted**: a frontend
216/// replaces its whole view on each [`crate::UiSignal::Environment`], so a
217/// future live observer is purely additive. Each item carries a stable key
218/// (e.g. [`Disk::name`]) for later diffing.
219#[derive(Debug, Clone, Default)]
220pub struct Environment {
221    /// Block devices and their mounts.
222    pub disks: Vec<Disk>,
223    // Future: peripherals (USB / smartcard / TPM), network interfaces.
224}
225
226/// The one-shot UI snapshots emitted at ceremony start: static system
227/// identity and the initial device environment.
228///
229/// Assembled by the CLI (the only crate holding the build-time constants) and
230/// handed to the executor, which echoes the two pieces as
231/// [`crate::UiSignal::SystemInfo`] and [`crate::UiSignal::Environment`]. The
232/// runtime treats this purely as a pass-through; it never inspects the
233/// contents.
234#[derive(Debug, Clone)]
235pub struct StartupSnapshot {
236    /// Static build and host identity.
237    pub system: SystemInfo,
238    /// Initial device environment.
239    pub environment: Environment,
240}
241
242impl StartupSnapshot {
243    /// A placeholder snapshot with no resolved build or host data. Used by
244    /// runtime tests, which do not exercise the System tab.
245    #[cfg(test)]
246    pub(crate) fn placeholder() -> Self {
247        let unknown = || "unknown".to_string();
248        Self {
249            system: SystemInfo {
250                build: BuildInfo {
251                    version: unknown(),
252                    commit: unknown(),
253                    commit_date: unknown(),
254                    build_date: unknown(),
255                    target: unknown(),
256                    profile: unknown(),
257                    features: String::new(),
258                    rustc: unknown(),
259                },
260                host: HostInfo {
261                    arch: std::env::consts::ARCH.to_string(),
262                    os: None,
263                    os_version: None,
264                    kernel_version: None,
265                    hostname: None,
266                    machine_id: None,
267                    cpu_model: None,
268                    hardening: None,
269                },
270                backends: Vec::new(),
271            },
272            environment: Environment::default(),
273        }
274    }
275}
276
277/// A block device and its mount, as shown in the System tab.
278#[derive(Debug, Clone)]
279pub struct Disk {
280    /// Device name; the stable key for diffing across re-emissions.
281    pub name: String,
282    /// Mount point path.
283    pub mount_point: String,
284    /// Filesystem type, when known.
285    pub file_system: Option<String>,
286    /// Total capacity in bytes.
287    pub total_bytes: u64,
288    /// Free space in bytes.
289    pub available_bytes: u64,
290    /// Whether the device is removable.
291    pub removable: bool,
292    /// Device kind (e.g. `"SSD"`, `"HDD"`), when known.
293    pub kind: Option<String>,
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn feature_status_round_trips_through_display_and_serde() {
302        for status in [
303            FeatureStatus::Active,
304            FeatureStatus::Inactive,
305            FeatureStatus::Unavailable,
306            FeatureStatus::NotApplicable,
307            FeatureStatus::Unknown,
308        ] {
309            let text = status.to_string();
310            assert_eq!(text.parse::<FeatureStatus>().expect("parses"), status);
311            let json = serde_json::to_string(&status).expect("serializes");
312            assert_eq!(json, format!("\"{text}\""));
313            let back: FeatureStatus = serde_json::from_str(&json).expect("deserializes");
314            assert_eq!(back, status);
315        }
316    }
317
318    #[test]
319    fn unknown_feature_status_string_is_rejected() {
320        assert!("bogus".parse::<FeatureStatus>().is_err());
321    }
322}