#![allow(clippy::doc_markdown)]
#![doc = include_str!("../README.md")]
use serde::{Deserialize, Serialize};
pub const SCHEMA_VERSION: u32 = 1;
pub const ATTESTATION_SCHEMA_VERSION: u32 = 1;
pub const VERSION_SCHEMA_VERSION: u32 = 1;
pub const LIST_SCHEMA_VERSION: u32 = 1;
pub const ATTEST_LIST_SCHEMA_VERSION: u32 = 1;
pub const VERIFY_SCHEMA_VERSION: u32 = 1;
pub const UPDATE_SCHEMA_VERSION: u32 = 2;
pub const RECOMMEND_SCHEMA_VERSION: u32 = 1;
pub const COMPAT_SCHEMA_VERSION: u32 = 1;
pub const COMPAT_SUBMIT_SCHEMA_VERSION: u32 = 1;
pub const DOCTOR_SCHEMA_VERSION: u32 = 1;
pub const CLI_ERROR_SCHEMA_VERSION: u32 = 1;
pub const FAILURE_MICROREPORT_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Manifest {
pub schema_version: u32,
pub tool_version: String,
#[serde(rename = "manifest_sequence")]
pub sequence: u64,
pub device: Device,
pub esp_files: Vec<EspFileEntry>,
pub allowed_files_closed_set: bool,
pub expected_pcrs: Vec<PcrEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Device {
pub disk_guid: String,
pub partition_count: u32,
pub esp: EspPartition,
pub data: DataPartition,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct EspPartition {
pub partuuid: String,
pub type_guid: String,
pub fs_uuid: String,
pub first_lba: u64,
pub last_lba: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DataPartition {
pub partuuid: String,
pub type_guid: String,
pub fs_uuid: String,
pub label: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct EspFileEntry {
pub path: String,
pub sha256: String,
pub size_bytes: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct PcrEntry {
pub pcr_index: u32,
pub bank: String,
pub digest_hex: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Attestation {
pub schema_version: u32,
pub tool_version: String,
pub flashed_at: String,
pub operator: String,
pub host: HostInfo,
pub target: TargetInfo,
pub isos: Vec<IsoRecord>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct HostInfo {
pub kernel: String,
pub secure_boot: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct TargetInfo {
pub device: String,
pub model: String,
pub size_bytes: u64,
pub image_sha256: String,
pub image_size_bytes: u64,
pub disk_guid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct IsoRecord {
pub filename: String,
pub sha256: String,
pub size_bytes: u64,
pub sidecars: Vec<String>,
pub added_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Version {
pub schema_version: u32,
pub tool: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListReport {
pub schema_version: u32,
pub tool_version: String,
pub mount_path: String,
pub attestation: Option<ListAttestationSummary>,
pub count: u32,
pub isos: Vec<ListIsoSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListAttestationSummary {
pub flashed_at: String,
pub operator: String,
pub isos_recorded: u32,
pub manifest_path: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ListIsoSummary {
pub name: String,
pub folder: Option<String>,
pub size_bytes: u64,
pub has_sha256: bool,
pub has_minisig: bool,
pub display_name: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AttestListReport {
pub schema_version: u32,
pub tool_version: String,
pub attestations_dir: String,
pub count: u32,
pub attestations: Vec<AttestListEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum AttestListEntry {
Success(AttestListSuccess),
Error(AttestListError),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AttestListSuccess {
pub manifest_path: String,
pub schema_version: u32,
pub tool_version: String,
pub flashed_at: String,
pub operator: String,
pub target_device: String,
pub target_model: String,
pub disk_guid: String,
pub iso_count: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AttestListError {
pub manifest_path: String,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VerifyReport {
pub schema_version: u32,
pub tool_version: String,
pub mount_path: String,
pub summary: VerifySummary,
pub isos: Vec<VerifyEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VerifySummary {
pub total: u32,
pub verified: u32,
pub mismatch: u32,
pub unreadable: u32,
pub not_present: u32,
pub any_failure: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VerifyEntry {
pub name: String,
#[serde(flatten)]
pub verdict: VerifyVerdict,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "verdict")]
pub enum VerifyVerdict {
Verified {
digest: String,
source: String,
},
Mismatch {
actual: String,
expected: String,
source: String,
},
Unreadable {
source: String,
reason: String,
},
NotPresent,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UpdateReport {
pub schema_version: u32,
pub tool_version: String,
pub device: String,
#[serde(flatten)]
pub eligibility: UpdateEligibility,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(tag = "eligibility")]
pub enum UpdateEligibility {
#[serde(rename = "ELIGIBLE")]
Eligible {
disk_guid: String,
attestation_path: String,
host_chain: Vec<UpdateChainEntry>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
esp_diff: Vec<UpdateFileDiff>,
},
#[serde(rename = "INELIGIBLE")]
Ineligible {
reason: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UpdateChainEntry {
pub role: String,
pub path: String,
#[serde(flatten)]
pub result: UpdateChainResult,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum UpdateChainResult {
Ok {
sha256: String,
},
Error {
error: String,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct UpdateFileDiff {
pub role: String,
pub esp_path: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_sha256: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fresh_sha256: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fresh_error: Option<String>,
pub would_change: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum RecommendReport {
Catalog(RecommendCatalogReport),
Single(RecommendSingleReport),
Miss(RecommendMissReport),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RecommendCatalogReport {
pub schema_version: u32,
pub tool_version: String,
pub count: u32,
pub entries: Vec<RecommendEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RecommendSingleReport {
pub schema_version: u32,
pub tool_version: String,
pub entry: RecommendEntry,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RecommendMissReport {
pub schema_version: u32,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum CompatReport {
Catalog(CompatCatalogReport),
Single(CompatSingleReport),
Miss(CompatMissReport),
MyMachineMiss(CompatMyMachineMissReport),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CompatCatalogReport {
pub schema_version: u32,
pub tool_version: String,
pub report_url: String,
pub count: u32,
pub entries: Vec<CompatEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CompatSingleReport {
pub schema_version: u32,
pub tool_version: String,
pub report_url: String,
pub entry: CompatEntry,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CompatMissReport {
pub schema_version: u32,
pub report_url: String,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CompatMyMachineMissReport {
pub schema_version: u32,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CompatEntry {
pub vendor: String,
pub model: String,
pub firmware: String,
pub sb_state: String,
pub boot_key: String,
pub level: String,
pub reported_by: String,
pub date: String,
pub notes: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CompatSubmitReport {
pub schema_version: u32,
pub tool: String,
pub submit_url: String,
pub vendor: String,
pub model: String,
pub firmware: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DoctorReport {
pub schema_version: u32,
pub tool_version: String,
pub score: u32,
pub band: String,
pub has_any_fail: bool,
pub next_action: Option<String>,
pub rows: Vec<DoctorRow>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct DoctorRow {
pub verdict: String,
pub name: String,
pub detail: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct CliError {
pub schema_version: u32,
pub error: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct FailureMicroreport {
pub schema_version: u32,
pub tier: String,
pub collected_at: String,
pub aegis_boot_version: String,
pub vendor_family: String,
pub bios_year: String,
pub boot_step_reached: String,
pub failure_class: String,
pub failure_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct RecommendEntry {
pub slug: String,
pub name: String,
pub arch: String,
pub size_mib: u32,
pub iso_url: String,
pub sha256_url: String,
pub sig_url: String,
pub sb: String,
pub purpose: String,
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
fn sample_manifest() -> Manifest {
Manifest {
schema_version: SCHEMA_VERSION,
tool_version: "aegis-boot 0.14.1".to_string(),
sequence: 42,
device: Device {
disk_guid: "00000000-0000-0000-0000-000000000001".to_string(),
partition_count: 2,
esp: EspPartition {
partuuid: "aaa".to_string(),
type_guid: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B".to_string(),
fs_uuid: "1234-ABCD".to_string(),
first_lba: 2048,
last_lba: 821_247,
},
data: DataPartition {
partuuid: "bbb".to_string(),
type_guid: "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7".to_string(),
fs_uuid: "ABCD-1234".to_string(),
label: "AEGIS_ISOS".to_string(),
},
},
esp_files: vec![EspFileEntry {
path: "::/EFI/BOOT/BOOTX64.EFI".to_string(),
sha256: "0".repeat(64),
size_bytes: 947_200,
}],
allowed_files_closed_set: true,
expected_pcrs: vec![],
}
}
#[test]
fn schema_version_is_one() {
assert_eq!(SCHEMA_VERSION, 1);
}
#[test]
fn round_trip_preserves_all_fields() {
let m = sample_manifest();
let body = serde_json::to_string(&m).expect("serialize");
let parsed: Manifest = serde_json::from_str(&body).expect("parse");
assert_eq!(m, parsed);
}
#[test]
fn serialized_uses_manifest_sequence_wire_name() {
let m = sample_manifest();
let body = serde_json::to_string(&m).expect("serialize");
assert!(
body.contains("\"manifest_sequence\":42"),
"wire field should be manifest_sequence, got: {body}"
);
assert!(
!body.contains("\"sequence\":"),
"bare `sequence` leak would break verifiers: {body}"
);
}
fn sample_attestation() -> Attestation {
Attestation {
schema_version: ATTESTATION_SCHEMA_VERSION,
tool_version: "aegis-boot 0.14.1".to_string(),
flashed_at: "2026-04-19T14:30:00Z".to_string(),
operator: "alice".to_string(),
host: HostInfo {
kernel: "6.17.0-20-generic".to_string(),
secure_boot: "enforcing".to_string(),
},
target: TargetInfo {
device: "/dev/sda".to_string(),
model: "SanDisk Cruzer 32 GB".to_string(),
size_bytes: 32_000_000_000,
image_sha256: "f".repeat(64),
image_size_bytes: 536_870_912,
disk_guid: "00000000-0000-0000-0000-000000000001".to_string(),
},
isos: vec![IsoRecord {
filename: "ubuntu-24.04.iso".to_string(),
sha256: "a".repeat(64),
size_bytes: 5_368_709_120,
sidecars: vec!["ubuntu-24.04.iso.aegis.toml".to_string()],
added_at: "2026-04-19T14:35:00Z".to_string(),
}],
}
}
#[test]
fn attestation_schema_version_is_one() {
assert_eq!(ATTESTATION_SCHEMA_VERSION, 1);
}
#[test]
fn attestation_round_trip_preserves_all_fields() {
let a = sample_attestation();
let body = serde_json::to_string(&a).expect("serialize");
let parsed: Attestation = serde_json::from_str(&body).expect("parse");
assert_eq!(a, parsed);
}
#[test]
fn empty_isos_list_serializes_as_empty_array() {
let mut a = sample_attestation();
a.isos.clear();
let body = serde_json::to_string(&a).expect("serialize");
assert!(body.contains("\"isos\":[]"), "isos must be present: {body}");
}
fn sample_version() -> Version {
Version {
schema_version: VERSION_SCHEMA_VERSION,
tool: "aegis-boot".to_string(),
version: "0.14.1".to_string(),
}
}
#[test]
fn version_schema_version_is_one() {
assert_eq!(VERSION_SCHEMA_VERSION, 1);
}
#[test]
fn version_round_trip_preserves_all_fields() {
let v = sample_version();
let body = serde_json::to_string(&v).expect("serialize");
let parsed: Version = serde_json::from_str(&body).expect("parse");
assert_eq!(v, parsed);
}
#[test]
fn version_wire_field_order_matches_documented_shape() {
let v = sample_version();
let body = serde_json::to_string(&v).expect("serialize");
let sv_pos = body.find("\"schema_version\"").expect("sv");
let tool_pos = body.find("\"tool\"").expect("tool");
let ver_pos = body.find("\"version\"").expect("version");
assert!(sv_pos < tool_pos, "schema_version before tool: {body}");
assert!(tool_pos < ver_pos, "tool before version: {body}");
}
fn sample_list_report() -> ListReport {
ListReport {
schema_version: LIST_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
mount_path: "/run/media/alice/AEGIS_ISOS".to_string(),
attestation: Some(ListAttestationSummary {
flashed_at: "2026-04-19T14:30:00Z".to_string(),
operator: "alice".to_string(),
isos_recorded: 3,
manifest_path: "/home/alice/.local/share/aegis-boot/attestations/abc.json"
.to_string(),
}),
count: 2,
isos: vec![
ListIsoSummary {
name: "ubuntu-24.04.iso".to_string(),
folder: Some("ubuntu-24.04".to_string()),
size_bytes: 5_368_709_120,
has_sha256: true,
has_minisig: false,
display_name: Some("Ubuntu 24.04 Desktop".to_string()),
description: None,
},
ListIsoSummary {
name: "debian-12.iso".to_string(),
folder: None,
size_bytes: 3_221_225_472,
has_sha256: false,
has_minisig: false,
display_name: None,
description: None,
},
],
}
}
#[test]
fn list_schema_version_is_one() {
assert_eq!(LIST_SCHEMA_VERSION, 1);
}
#[test]
fn list_round_trip_preserves_all_fields() {
let r = sample_list_report();
let body = serde_json::to_string(&r).expect("serialize");
let parsed: ListReport = serde_json::from_str(&body).expect("parse");
assert_eq!(r, parsed);
}
#[test]
fn list_attestation_serializes_as_null_when_absent() {
let mut r = sample_list_report();
r.attestation = None;
let body = serde_json::to_string(&r).expect("serialize");
assert!(
body.contains("\"attestation\":null"),
"attestation must be explicit null: {body}"
);
}
#[test]
fn list_iso_summary_preserves_null_sidecar_fields() {
let mut r = sample_list_report();
r.isos[1].display_name = None;
r.isos[1].description = None;
let body = serde_json::to_string(&r).expect("serialize");
assert!(
body.contains("\"display_name\":null"),
"display_name missing or omitted: {body}"
);
assert!(
body.contains("\"description\":null"),
"description missing or omitted: {body}"
);
}
fn sample_attest_list_success() -> AttestListSuccess {
AttestListSuccess {
manifest_path: "/home/alice/.local/share/aegis-boot/attestations/abc.json".to_string(),
schema_version: ATTESTATION_SCHEMA_VERSION,
tool_version: "aegis-boot 0.14.1".to_string(),
flashed_at: "2026-04-19T14:30:00Z".to_string(),
operator: "alice".to_string(),
target_device: "/dev/sda".to_string(),
target_model: "SanDisk Cruzer".to_string(),
disk_guid: "00000000-0000-0000-0000-000000000001".to_string(),
iso_count: 3,
}
}
#[test]
fn attest_list_schema_version_is_one() {
assert_eq!(ATTEST_LIST_SCHEMA_VERSION, 1);
}
#[test]
fn attest_list_success_serializes_without_error_field() {
let entry = AttestListEntry::Success(sample_attest_list_success());
let body = serde_json::to_string(&entry).expect("serialize");
assert!(!body.contains("\"error\""), "must not have error: {body}");
assert!(body.contains("\"operator\":\"alice\""));
}
#[test]
fn attest_list_error_serializes_without_summary_fields() {
let entry = AttestListEntry::Error(AttestListError {
manifest_path: "/tmp/broken.json".to_string(),
error: "parse failed: missing field".to_string(),
});
let body = serde_json::to_string(&entry).expect("serialize");
assert!(body.contains("\"error\":"));
for success_field in &[
"schema_version",
"tool_version",
"flashed_at",
"operator",
"target_device",
"target_model",
"disk_guid",
"iso_count",
] {
let pattern = format!("\"{success_field}\"");
assert!(
!body.contains(&pattern),
"Error variant must not emit {success_field}: {body}"
);
}
}
#[test]
fn attest_list_entry_round_trips() {
let success = AttestListEntry::Success(sample_attest_list_success());
let body = serde_json::to_string(&success).expect("serialize");
let parsed: AttestListEntry = serde_json::from_str(&body).expect("parse");
assert_eq!(success, parsed);
let err = AttestListEntry::Error(AttestListError {
manifest_path: "/tmp/x.json".to_string(),
error: "nope".to_string(),
});
let body = serde_json::to_string(&err).expect("serialize");
let parsed: AttestListEntry = serde_json::from_str(&body).expect("parse");
assert_eq!(err, parsed);
}
fn sample_verify_report() -> VerifyReport {
VerifyReport {
schema_version: VERIFY_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
mount_path: "/run/media/alice/AEGIS_ISOS".to_string(),
summary: VerifySummary {
total: 4,
verified: 1,
mismatch: 1,
unreadable: 1,
not_present: 1,
any_failure: true,
},
isos: vec![
VerifyEntry {
name: "ubuntu.iso".to_string(),
verdict: VerifyVerdict::Verified {
digest: "a".repeat(64),
source: "sidecar".to_string(),
},
},
VerifyEntry {
name: "debian.iso".to_string(),
verdict: VerifyVerdict::Mismatch {
actual: "b".repeat(64),
expected: "c".repeat(64),
source: "sidecar".to_string(),
},
},
VerifyEntry {
name: "alpine.iso".to_string(),
verdict: VerifyVerdict::Unreadable {
source: "sidecar".to_string(),
reason: "permission denied".to_string(),
},
},
VerifyEntry {
name: "fedora.iso".to_string(),
verdict: VerifyVerdict::NotPresent,
},
],
}
}
#[test]
fn verify_schema_version_is_one() {
assert_eq!(VERIFY_SCHEMA_VERSION, 1);
}
#[test]
fn verify_round_trip_preserves_all_variants() {
let r = sample_verify_report();
let body = serde_json::to_string(&r).expect("serialize");
let parsed: VerifyReport = serde_json::from_str(&body).expect("parse");
assert_eq!(r, parsed);
}
#[test]
fn verify_entry_emits_name_before_verdict() {
let entry = VerifyEntry {
name: "x".to_string(),
verdict: VerifyVerdict::NotPresent,
};
let body = serde_json::to_string(&entry).expect("serialize");
let name_pos = body.find("\"name\"").expect("has name");
let verdict_pos = body.find("\"verdict\"").expect("has verdict");
assert!(
name_pos < verdict_pos,
"name must come before verdict: {body}"
);
}
#[test]
fn verify_notpresent_emits_no_variant_fields() {
let entry = VerifyEntry {
name: "x".to_string(),
verdict: VerifyVerdict::NotPresent,
};
let body = serde_json::to_string(&entry).expect("serialize");
for field in &["digest", "actual", "expected", "source", "reason"] {
let pattern = format!("\"{field}\"");
assert!(
!body.contains(&pattern),
"NotPresent must not emit {field}: {body}"
);
}
}
#[test]
fn verify_verdict_tags_match_strings() {
let v = VerifyEntry {
name: "x".to_string(),
verdict: VerifyVerdict::Verified {
digest: "d".to_string(),
source: "s".to_string(),
},
};
let body = serde_json::to_string(&v).expect("serialize");
assert!(body.contains("\"verdict\":\"Verified\""));
let m = VerifyEntry {
name: "x".to_string(),
verdict: VerifyVerdict::Mismatch {
actual: "a".to_string(),
expected: "e".to_string(),
source: "s".to_string(),
},
};
assert!(
serde_json::to_string(&m)
.expect("ok")
.contains("\"verdict\":\"Mismatch\"")
);
let u = VerifyEntry {
name: "x".to_string(),
verdict: VerifyVerdict::Unreadable {
source: "s".to_string(),
reason: "r".to_string(),
},
};
assert!(
serde_json::to_string(&u)
.expect("ok")
.contains("\"verdict\":\"Unreadable\"")
);
let n = VerifyEntry {
name: "x".to_string(),
verdict: VerifyVerdict::NotPresent,
};
assert!(
serde_json::to_string(&n)
.expect("ok")
.contains("\"verdict\":\"NotPresent\"")
);
}
fn sample_update_eligible() -> UpdateReport {
UpdateReport {
schema_version: UPDATE_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
device: "/dev/sda".to_string(),
eligibility: UpdateEligibility::Eligible {
disk_guid: "00000000-0000-0000-0000-000000000001".to_string(),
attestation_path: "/home/alice/.local/share/aegis-boot/attestations/abc.json"
.to_string(),
host_chain: vec![
UpdateChainEntry {
role: "shim".to_string(),
path: "/usr/lib/shim/shimx64.efi.signed".to_string(),
result: UpdateChainResult::Ok {
sha256: "a".repeat(64),
},
},
UpdateChainEntry {
role: "grub".to_string(),
path: "/usr/lib/grub/x86_64-efi/grubx64.efi".to_string(),
result: UpdateChainResult::Error {
error: "file not found".to_string(),
},
},
],
esp_diff: vec![UpdateFileDiff {
role: "shim".to_string(),
esp_path: "/EFI/BOOT/BOOTX64.EFI".to_string(),
current_sha256: Some("b".repeat(64)),
current_error: None,
fresh_sha256: Some("a".repeat(64)),
fresh_error: None,
would_change: true,
}],
},
}
}
fn sample_update_ineligible() -> UpdateReport {
UpdateReport {
schema_version: UPDATE_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
device: "/dev/sdb".to_string(),
eligibility: UpdateEligibility::Ineligible {
reason: "device is not removable (looks like an internal disk)".to_string(),
},
}
}
#[test]
fn update_schema_version_is_two() {
assert_eq!(UPDATE_SCHEMA_VERSION, 2);
}
#[test]
fn update_report_parses_v1_payload_without_esp_diff() {
let v1_body = r#"{
"schema_version": 1,
"tool_version": "0.14.1",
"device": "/dev/sda",
"eligibility": "ELIGIBLE",
"disk_guid": "00000000-0000-0000-0000-000000000001",
"attestation_path": "/tmp/att.json",
"host_chain": []
}"#;
let parsed: UpdateReport = serde_json::from_str(v1_body).expect("v1 parse");
match parsed.eligibility {
UpdateEligibility::Eligible { esp_diff, .. } => assert!(esp_diff.is_empty()),
UpdateEligibility::Ineligible { .. } => panic!("expected ELIGIBLE"),
}
}
#[test]
fn update_file_diff_omits_none_fields_on_wire() {
let d = UpdateFileDiff {
role: "shim".to_string(),
esp_path: "/EFI/BOOT/BOOTX64.EFI".to_string(),
current_sha256: Some("a".repeat(64)),
current_error: None,
fresh_sha256: Some("a".repeat(64)),
fresh_error: None,
would_change: false,
};
let body = serde_json::to_string(&d).expect("serialize");
assert!(body.contains("\"current_sha256\""));
assert!(body.contains("\"fresh_sha256\""));
assert!(!body.contains("current_error"), "{body}");
assert!(!body.contains("fresh_error"), "{body}");
assert!(body.contains("\"would_change\":false"));
}
#[test]
fn update_file_diff_round_trips_with_errors() {
let d = UpdateFileDiff {
role: "kernel".to_string(),
esp_path: "/vmlinuz".to_string(),
current_sha256: None,
current_error: Some("mtype exited non-zero".to_string()),
fresh_sha256: Some("f".repeat(64)),
fresh_error: None,
would_change: false,
};
let body = serde_json::to_string(&d).expect("serialize");
let back: UpdateFileDiff = serde_json::from_str(&body).expect("parse");
assert_eq!(d, back);
}
#[test]
fn update_round_trip_preserves_all_variants() {
let eligible = sample_update_eligible();
let body = serde_json::to_string(&eligible).expect("serialize");
let parsed: UpdateReport = serde_json::from_str(&body).expect("parse");
assert_eq!(eligible, parsed);
let ineligible = sample_update_ineligible();
let body = serde_json::to_string(&ineligible).expect("serialize");
let parsed: UpdateReport = serde_json::from_str(&body).expect("parse");
assert_eq!(ineligible, parsed);
}
#[test]
fn update_emits_header_fields_before_eligibility() {
let r = sample_update_ineligible();
let body = serde_json::to_string(&r).expect("serialize");
let sv_pos = body.find("\"schema_version\"").expect("sv");
let tv_pos = body.find("\"tool_version\"").expect("tv");
let dev_pos = body.find("\"device\"").expect("dev");
let elig_pos = body.find("\"eligibility\"").expect("eligibility");
assert!(sv_pos < tv_pos, "{body}");
assert!(tv_pos < dev_pos, "{body}");
assert!(dev_pos < elig_pos, "{body}");
}
#[test]
fn update_eligibility_tags_match_upper_case_wire_strings() {
let e = sample_update_eligible();
let body = serde_json::to_string(&e).expect("serialize");
assert!(body.contains("\"eligibility\":\"ELIGIBLE\""), "{body}");
let i = sample_update_ineligible();
let body = serde_json::to_string(&i).expect("serialize");
assert!(body.contains("\"eligibility\":\"INELIGIBLE\""), "{body}");
}
#[test]
fn update_chain_entry_variants_are_mutually_exclusive() {
let ok = UpdateChainEntry {
role: "shim".to_string(),
path: "/path/to/shim".to_string(),
result: UpdateChainResult::Ok {
sha256: "a".repeat(64),
},
};
let body = serde_json::to_string(&ok).expect("serialize");
assert!(body.contains("\"sha256\""));
assert!(!body.contains("\"error\""), "{body}");
let err = UpdateChainEntry {
role: "grub".to_string(),
path: "/path/to/grub".to_string(),
result: UpdateChainResult::Error {
error: "missing".to_string(),
},
};
let body = serde_json::to_string(&err).expect("serialize");
assert!(body.contains("\"error\""));
assert!(!body.contains("\"sha256\""), "{body}");
}
fn sample_recommend_entry() -> RecommendEntry {
RecommendEntry {
slug: "ubuntu-24.04-live-server".to_string(),
name: "Ubuntu 24.04 LTS Live Server".to_string(),
arch: "amd64".to_string(),
size_mib: 2_400_u32,
iso_url: "https://releases.ubuntu.com/24.04/ubuntu-24.04-live-server-amd64.iso"
.to_string(),
sha256_url: "https://releases.ubuntu.com/24.04/SHA256SUMS".to_string(),
sig_url: "https://releases.ubuntu.com/24.04/SHA256SUMS.gpg".to_string(),
sb: "signed:canonical".to_string(),
purpose: "Standard server install media".to_string(),
}
}
#[test]
fn recommend_schema_version_is_one() {
assert_eq!(RECOMMEND_SCHEMA_VERSION, 1);
}
#[test]
fn recommend_catalog_round_trips() {
let report = RecommendReport::Catalog(RecommendCatalogReport {
schema_version: RECOMMEND_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
count: 1,
entries: vec![sample_recommend_entry()],
});
let body = serde_json::to_string(&report).expect("serialize");
let parsed: RecommendReport = serde_json::from_str(&body).expect("parse");
assert_eq!(report, parsed);
assert!(body.contains("\"entries\""));
assert!(!body.contains("\"entry\""));
assert!(!body.contains("\"error\""));
}
#[test]
fn recommend_single_round_trips() {
let report = RecommendReport::Single(RecommendSingleReport {
schema_version: RECOMMEND_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
entry: sample_recommend_entry(),
});
let body = serde_json::to_string(&report).expect("serialize");
let parsed: RecommendReport = serde_json::from_str(&body).expect("parse");
assert_eq!(report, parsed);
assert!(body.contains("\"entry\""));
assert!(!body.contains("\"entries\""));
assert!(!body.contains("\"error\""));
}
#[test]
fn recommend_miss_round_trips_and_omits_tool_version() {
let report = RecommendReport::Miss(RecommendMissReport {
schema_version: RECOMMEND_SCHEMA_VERSION,
error: "no catalog entry matching 'x'".to_string(),
});
let body = serde_json::to_string(&report).expect("serialize");
let parsed: RecommendReport = serde_json::from_str(&body).expect("parse");
assert_eq!(report, parsed);
assert!(body.contains("\"error\""));
assert!(
!body.contains("\"tool_version\""),
"miss omits tool_version: {body}"
);
}
#[test]
fn recommend_untagged_dispatch_by_field_presence() {
let catalog_body = r#"{"schema_version":1,"tool_version":"0.1.0","count":0,"entries":[]}"#;
let parsed: RecommendReport = serde_json::from_str(catalog_body).expect("catalog parse");
assert!(matches!(parsed, RecommendReport::Catalog(_)));
let miss_body = r#"{"schema_version":1,"error":"not found"}"#;
let parsed: RecommendReport = serde_json::from_str(miss_body).expect("miss parse");
assert!(matches!(parsed, RecommendReport::Miss(_)));
}
fn sample_compat_entry() -> CompatEntry {
CompatEntry {
vendor: "Framework".to_string(),
model: "Laptop (12th Gen Intel Core) / A6".to_string(),
firmware: "INSYDE Corp. 03.19".to_string(),
sb_state: "enforcing".to_string(),
boot_key: "F12".to_string(),
level: "verified".to_string(),
reported_by: "@williamzujkowski".to_string(),
date: "2026-04-18".to_string(),
notes: vec!["Full chain validated".to_string()],
}
}
#[test]
fn compat_schema_versions_are_one() {
assert_eq!(COMPAT_SCHEMA_VERSION, 1);
assert_eq!(COMPAT_SUBMIT_SCHEMA_VERSION, 1);
}
#[test]
fn compat_report_catalog_round_trips() {
let report = CompatReport::Catalog(CompatCatalogReport {
schema_version: COMPAT_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
report_url: "https://example.com/report".to_string(),
count: 1,
entries: vec![sample_compat_entry()],
});
let body = serde_json::to_string(&report).expect("serialize");
let parsed: CompatReport = serde_json::from_str(&body).expect("parse");
assert_eq!(report, parsed);
assert!(body.contains("\"entries\""));
}
#[test]
fn compat_report_miss_omits_tool_version() {
let report = CompatReport::Miss(CompatMissReport {
schema_version: COMPAT_SCHEMA_VERSION,
report_url: "https://example.com/report".to_string(),
error: "no platform matching 'foo'".to_string(),
});
let body = serde_json::to_string(&report).expect("serialize");
assert!(body.contains("\"report_url\""));
assert!(body.contains("\"error\""));
assert!(
!body.contains("\"tool_version\""),
"miss omits tool_version: {body}"
);
}
#[test]
fn compat_report_my_machine_miss_has_minimal_shape() {
let report = CompatReport::MyMachineMiss(CompatMyMachineMissReport {
schema_version: COMPAT_SCHEMA_VERSION,
error: "--my-machine: DMI fields unavailable".to_string(),
});
let body = serde_json::to_string(&report).expect("serialize");
assert!(body.contains("\"error\""));
assert!(!body.contains("\"report_url\""));
assert!(!body.contains("\"tool_version\""));
}
#[test]
fn compat_untagged_dispatch_by_field_presence() {
let body =
r#"{"schema_version":1,"tool_version":"0.1","report_url":"x","count":0,"entries":[]}"#;
assert!(matches!(
serde_json::from_str::<CompatReport>(body).expect("catalog"),
CompatReport::Catalog(_)
));
let body = r#"{"schema_version":1,"tool_version":"0.1","report_url":"x","entry":{"vendor":"","model":"","firmware":"","sb_state":"","boot_key":"","level":"","reported_by":"","date":"","notes":[]}}"#;
assert!(matches!(
serde_json::from_str::<CompatReport>(body).expect("single"),
CompatReport::Single(_)
));
let body = r#"{"schema_version":1,"report_url":"x","error":"nope"}"#;
assert!(matches!(
serde_json::from_str::<CompatReport>(body).expect("miss"),
CompatReport::Miss(_)
));
let body = r#"{"schema_version":1,"error":"dmi"}"#;
assert!(matches!(
serde_json::from_str::<CompatReport>(body).expect("mymachine"),
CompatReport::MyMachineMiss(_)
));
}
#[test]
fn compat_submit_uses_tool_not_tool_version() {
let r = CompatSubmitReport {
schema_version: COMPAT_SUBMIT_SCHEMA_VERSION,
tool: "aegis-boot".to_string(),
submit_url: "https://example.com/new?vendor=x".to_string(),
vendor: "x".to_string(),
model: "y".to_string(),
firmware: "z".to_string(),
};
let body = serde_json::to_string(&r).expect("serialize");
let parsed: CompatSubmitReport = serde_json::from_str(&body).expect("parse");
assert_eq!(r, parsed);
assert!(body.contains("\"tool\":\"aegis-boot\""));
assert!(!body.contains("\"tool_version\""), "{body}");
assert!(body.contains("\"submit_url\""));
}
fn sample_doctor_report() -> DoctorReport {
DoctorReport {
schema_version: DOCTOR_SCHEMA_VERSION,
tool_version: "0.14.1".to_string(),
score: 85,
band: "GOOD".to_string(),
has_any_fail: false,
next_action: None,
rows: vec![
DoctorRow {
verdict: "PASS".to_string(),
name: "OS".to_string(),
detail: "Linux 6.17.0".to_string(),
},
DoctorRow {
verdict: "WARN".to_string(),
name: "Secure Boot (host)".to_string(),
detail: "disabled".to_string(),
},
],
}
}
#[test]
fn doctor_schema_version_is_one() {
assert_eq!(DOCTOR_SCHEMA_VERSION, 1);
}
#[test]
fn doctor_round_trips_and_preserves_all_fields() {
let r = sample_doctor_report();
let body = serde_json::to_string(&r).expect("serialize");
let parsed: DoctorReport = serde_json::from_str(&body).expect("parse");
assert_eq!(r, parsed);
}
#[test]
fn doctor_next_action_null_when_absent() {
let r = sample_doctor_report();
let body = serde_json::to_string(&r).expect("serialize");
assert!(
body.contains("\"next_action\":null"),
"next_action must be explicit null: {body}"
);
}
#[test]
fn doctor_next_action_populated_on_fail() {
let mut r = sample_doctor_report();
r.has_any_fail = true;
r.next_action = Some("install mcopy".to_string());
r.rows.push(DoctorRow {
verdict: "FAIL".to_string(),
name: "command: mcopy".to_string(),
detail: "not found in PATH".to_string(),
});
let body = serde_json::to_string(&r).expect("serialize");
assert!(body.contains("\"has_any_fail\":true"));
assert!(body.contains("\"next_action\":\"install mcopy\""));
}
#[test]
fn doctor_row_verdict_is_free_string_not_enum() {
let r = DoctorRow {
verdict: "FUTURE-VERDICT-LABEL".to_string(),
name: "some new check".to_string(),
detail: "novel condition".to_string(),
};
let body = serde_json::to_string(&r).expect("serialize");
let parsed: DoctorRow = serde_json::from_str(&body).expect("parse");
assert_eq!(r, parsed);
}
#[test]
fn cli_error_schema_version_is_one() {
assert_eq!(CLI_ERROR_SCHEMA_VERSION, 1);
}
#[test]
fn cli_error_round_trips_and_escapes_properly() {
let e = CliError {
schema_version: CLI_ERROR_SCHEMA_VERSION,
error: "mount failed: \"/dev/sdX\" is not removable".to_string(),
};
let body = serde_json::to_string(&e).expect("serialize");
let parsed: CliError = serde_json::from_str(&body).expect("parse");
assert_eq!(e, parsed);
assert!(
body.contains(r#"\"/dev/sdX\""#),
"embedded quotes must be escaped: {body}"
);
}
}