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}