use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::Attestation;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "state", content = "value", rename_all = "snake_case")]
pub enum SourceAttestation {
Verified(Attestation),
LegacyUnattested {
imported_at: DateTime<Utc>,
original_recorded_at: DateTime<Utc>,
},
Missing,
}
impl SourceAttestation {
#[must_use]
pub const fn is_verified(&self) -> bool {
matches!(self, Self::Verified(_))
}
#[must_use]
pub const fn is_unattested(&self) -> bool {
matches!(self, Self::LegacyUnattested { .. } | Self::Missing)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::path::{Path, PathBuf};
use crate::{Attestation, Attestor, InMemoryAttestor};
fn attestation_fixture() -> Attestation {
let attestor = InMemoryAttestor::from_seed(&[7; 32]);
Attestation {
key_id: attestor.key_id().to_string(),
signature: attestor.sign(b"source-attestation-test").to_bytes(),
signed_at: "2026-05-04T12:00:00Z".parse().unwrap(),
}
}
#[test]
fn source_attestation_variants_round_trip() {
let imported_at = "2026-05-04T13:00:00Z".parse().unwrap();
let original_recorded_at = "2026-05-04T11:59:00Z".parse().unwrap();
let variants = [
SourceAttestation::Verified(attestation_fixture()),
SourceAttestation::LegacyUnattested {
imported_at,
original_recorded_at,
},
SourceAttestation::Missing,
];
for variant in variants {
let json = serde_json::to_string(&variant).unwrap();
let decoded: SourceAttestation = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, variant);
}
}
#[test]
fn source_attestation_wire_strings_are_stable() {
let verified_json =
serde_json::to_value(SourceAttestation::Verified(attestation_fixture()))
.expect("verified variant serializes");
assert_eq!(verified_json["state"], "verified");
let legacy_json = serde_json::to_value(SourceAttestation::LegacyUnattested {
imported_at: "2026-05-04T13:00:00Z".parse().unwrap(),
original_recorded_at: "2026-05-04T11:59:00Z".parse().unwrap(),
})
.expect("legacy variant serializes");
assert_eq!(legacy_json["state"], "legacy_unattested");
let missing_json =
serde_json::to_value(SourceAttestation::Missing).expect("missing variant serializes");
assert_eq!(missing_json["state"], "missing");
}
#[test]
fn source_attestation_authority_helpers_separate_unattested_rows() {
assert!(SourceAttestation::Verified(attestation_fixture()).is_verified());
assert!(!SourceAttestation::Verified(attestation_fixture()).is_unattested());
assert!(SourceAttestation::LegacyUnattested {
imported_at: "2026-05-04T13:00:00Z".parse().unwrap(),
original_recorded_at: "2026-05-04T11:59:00Z".parse().unwrap(),
}
.is_unattested());
assert!(SourceAttestation::Missing.is_unattested());
}
#[test]
fn no_zero_signature_sentinel_in_codebase() {
let workspace = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("cortex-core sits under crates/");
let mut files = Vec::new();
collect_rs_files(workspace, &mut files);
let compact_array = ["[0;", "64]"].join(" ");
let attestation_field = ["signature:", compact_array.as_str()].join(" ");
for file in files {
let text = fs::read_to_string(&file).expect("source file is readable");
assert!(
!text.contains(&attestation_field) && !text.contains(&compact_array),
"zero-signature attestation sentinel is forbidden by ADR 0018: {}",
file.display()
);
}
}
fn collect_rs_files(dir: &Path, files: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
for entry in entries {
let entry = entry.expect("directory entry is readable");
let path = entry.path();
if should_skip(&path) {
continue;
}
if path.is_dir() {
collect_rs_files(&path, files);
} else if path.extension().and_then(|ext| ext.to_str()) == Some("rs") {
files.push(path);
}
}
}
fn should_skip(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| {
matches!(
name,
"target" | ".git" | ".cargo" | "generated" | "fixtures"
)
})
}
}