use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergenceAttestation {
pub artifact_hash: String,
pub control_hash: Option<String>,
pub intent_hash: String,
pub composed_root: String,
pub generation: u64,
pub previous_root: Option<String>,
}
#[must_use]
fn compose_root(
artifact_hash: &str,
control_hash: Option<&str>,
intent_hash: &str,
previous_root: Option<&str>,
) -> String {
let mut hasher = blake3::Hasher::new();
hasher.update(artifact_hash.as_bytes());
if let Some(ch) = control_hash {
hasher.update(ch.as_bytes());
}
hasher.update(intent_hash.as_bytes());
if let Some(prev) = previous_root {
hasher.update(prev.as_bytes());
}
format!("blake3:{}", hasher.finalize())
}
impl ConvergenceAttestation {
pub fn produce(
artifact_data: &[u8],
control_data: Option<&[u8]>,
intent_data: &[u8],
generation: u64,
previous_root: Option<String>,
) -> Self {
let artifact_hash = format!("blake3:{}", blake3::hash(artifact_data));
let control_hash = control_data.map(|d| format!("blake3:{}", blake3::hash(d)));
let intent_hash = format!("blake3:{}", blake3::hash(intent_data));
let composed_root = compose_root(
&artifact_hash,
control_hash.as_deref(),
&intent_hash,
previous_root.as_deref(),
);
Self {
artifact_hash,
control_hash,
intent_hash,
composed_root,
generation,
previous_root,
}
}
pub fn verify(&self) -> bool {
let expected = compose_root(
&self.artifact_hash,
self.control_hash.as_deref(),
&self.intent_hash,
self.previous_root.as_deref(),
);
self.composed_root == expected
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceResult {
pub framework: String,
pub controls_checked: Vec<String>,
pub controls_passed: Vec<String>,
pub controls_failed: Vec<String>,
pub all_passed: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_produce_attestation() {
let att = ConvergenceAttestation::produce(
b"workload running",
Some(b"nist ac-6 passed"),
b"desired: { replicas: 3 }",
1,
None,
);
assert!(att.artifact_hash.starts_with("blake3:"));
assert!(att.control_hash.as_ref().unwrap().starts_with("blake3:"));
assert!(att.intent_hash.starts_with("blake3:"));
assert!(att.composed_root.starts_with("blake3:"));
assert_eq!(att.generation, 1);
}
#[test]
fn test_verify_attestation() {
let att =
ConvergenceAttestation::produce(b"artifact", Some(b"controls"), b"intent", 0, None);
assert!(att.verify());
}
#[test]
fn test_tampered_attestation_fails_verify() {
let mut att =
ConvergenceAttestation::produce(b"artifact", Some(b"controls"), b"intent", 0, None);
att.artifact_hash = "blake3:tampered".into();
assert!(!att.verify());
}
#[test]
fn test_generational_chain() {
let gen0 = ConvergenceAttestation::produce(b"v1", None, b"intent", 0, None);
let gen1 = ConvergenceAttestation::produce(
b"v2",
None,
b"intent",
1,
Some(gen0.composed_root.clone()),
);
assert!(gen1.verify());
assert_eq!(
gen1.previous_root.as_deref(),
Some(gen0.composed_root.as_str())
);
assert_ne!(gen0.composed_root, gen1.composed_root);
}
#[test]
fn test_no_compliance() {
let att = ConvergenceAttestation::produce(b"artifact", None, b"intent", 0, None);
assert!(att.control_hash.is_none());
assert!(att.verify());
}
#[test]
fn test_deterministic() {
let a = ConvergenceAttestation::produce(b"x", Some(b"y"), b"z", 0, None);
let b = ConvergenceAttestation::produce(b"x", Some(b"y"), b"z", 0, None);
assert_eq!(a.composed_root, b.composed_root);
}
#[test]
fn compose_root_produce_and_verify_use_the_same_composition_by_construction() {
for (control, previous) in [
(None, None),
(Some(&b"controls-passed"[..]), None),
(None, Some("blake3:prev".to_string())),
(
Some(&b"controls-passed"[..]),
Some("blake3:prev".to_string()),
),
] {
let att = ConvergenceAttestation::produce(
b"artifact-payload",
control,
b"intent-payload",
7,
previous,
);
assert!(
att.verify(),
"attestation produced with (control={:?}, previous={:?}) must verify",
att.control_hash,
att.previous_root,
);
}
}
#[test]
fn compose_root_deterministic_across_calls() {
let a = compose_root("blake3:art", Some("blake3:ctl"), "blake3:int", Some("prev"));
let b = compose_root("blake3:art", Some("blake3:ctl"), "blake3:int", Some("prev"));
assert_eq!(a, b);
}
#[test]
fn compose_root_pillar_order_is_load_bearing_artifact_before_intent() {
let ordered = compose_root("blake3:A", None, "blake3:B", None);
let swapped = compose_root("blake3:B", None, "blake3:A", None);
assert_ne!(
ordered, swapped,
"artifact-before-intent order is load-bearing on the composed root",
);
}
#[test]
fn compose_root_optional_slot_absence_differs_from_presence_when_bytes_nonempty() {
let control_absent = compose_root("blake3:A", None, "blake3:B", None);
let previous_absent = compose_root("blake3:A", Some("blake3:C"), "blake3:B", None);
let both_present = compose_root("blake3:A", Some("blake3:C"), "blake3:B", Some("blake3:D"));
assert_ne!(control_absent, previous_absent);
assert_ne!(previous_absent, both_present);
assert_ne!(control_absent, both_present);
}
#[test]
fn compose_root_output_carries_blake3_scheme_prefix() {
let root = compose_root("blake3:A", None, "blake3:B", None);
assert!(
root.starts_with("blake3:"),
"composed root must carry the `blake3:` scheme prefix, got {root:?}",
);
}
#[test]
fn compose_root_matches_pre_lift_hand_authored_composition_bytewise() {
for (control, previous) in [
(None, None),
(Some("blake3:C"), None),
(None, Some("blake3:P")),
(Some("blake3:C"), Some("blake3:P")),
] {
let via_primitive = compose_root("blake3:A", control, "blake3:I", previous);
let via_pre_lift = {
let mut hasher = blake3::Hasher::new();
hasher.update(b"blake3:A");
if let Some(ch) = control {
hasher.update(ch.as_bytes());
}
hasher.update(b"blake3:I");
if let Some(pr) = previous {
hasher.update(pr.as_bytes());
}
format!("blake3:{}", hasher.finalize())
};
assert_eq!(via_primitive, via_pre_lift);
}
}
#[test]
fn test_compliance_result() {
let result = ComplianceResult {
framework: "nist-800-53".into(),
controls_checked: vec!["AC-6".into(), "AU-2".into()],
controls_passed: vec!["AC-6".into(), "AU-2".into()],
controls_failed: vec![],
all_passed: true,
};
assert!(result.all_passed);
assert_eq!(result.controls_checked.len(), 2);
}
}