#[cfg(kani)]
#[kani::proof]
#[kani::unwind(8)]
fn proof_blake3_idempotency() {
let data: [u8; 4] = kani::any();
let h1 = blake3::hash(&data);
let h2 = blake3::hash(&data);
assert_eq!(h1, h2, "BLAKE3 must be deterministic");
}
#[cfg(kani)]
#[kani::proof]
#[kani::unwind(8)]
fn proof_blake3_collision_resistance() {
let a: [u8; 4] = kani::any();
let b: [u8; 4] = kani::any();
kani::assume(a != b);
let ha = blake3::hash(&a);
let hb = blake3::hash(&b);
assert_ne!(ha, hb, "different inputs should produce different hashes");
}
#[cfg(kani)]
#[kani::proof]
#[kani::unwind(8)]
fn proof_converged_state_is_noop() {
let content: [u8; 4] = kani::any();
let desired_hash = blake3::hash(&content).to_hex().to_string();
let current_hash = blake3::hash(&content).to_hex().to_string();
let needs_change = desired_hash != current_hash;
assert!(
!needs_change,
"identical content must produce identical hash"
);
}
#[cfg(kani)]
#[kani::proof]
fn proof_status_transition_monotonic() {
let status: u8 = kani::any();
kani::assume(status <= 3);
if status == 2 {
let hash_matches: bool = kani::any();
if hash_matches {
let next_status = 2u8; assert_eq!(
next_status, 2,
"converged + matching hash = still converged"
);
}
}
}
#[cfg(kani)]
#[kani::proof]
#[kani::unwind(4)]
fn proof_plan_determinism() {
let n: u8 = kani::any();
kani::assume(n <= 3);
let mut changes_1 = 0u8;
let mut changes_2 = 0u8;
for _ in 0..n {
let current: u32 = kani::any();
let desired: u32 = kani::any();
if current != desired {
changes_1 += 1;
}
if current != desired {
changes_2 += 1;
}
}
assert_eq!(changes_1, changes_2, "plan must be deterministic");
}
#[cfg(kani)]
#[kani::proof]
fn proof_topo_sort_stability() {
let edge_01: bool = kani::any();
let edge_02: bool = kani::any();
let edge_12: bool = kani::any();
let order_1 = compute_order(edge_01, edge_02, edge_12);
let order_2 = compute_order(edge_01, edge_02, edge_12);
assert_eq!(order_1, order_2, "topo sort must be deterministic");
}
#[cfg(any(kani, test))]
pub(super) fn init_in_degree(e01: bool, e02: bool, e12: bool) -> [u8; 3] {
let mut d = [0u8; 3];
if e01 {
d[1] += 1;
}
if e02 {
d[2] += 1;
}
if e12 {
d[2] += 1;
}
d
}
#[cfg(any(kani, test))]
pub(super) fn remove_edges(node: u8, in_degree: &mut [u8; 3], e01: bool, e02: bool, e12: bool) {
if node == 0 && e01 {
in_degree[1] -= 1;
}
if node == 0 && e02 {
in_degree[2] -= 1;
}
if node == 1 && e12 {
in_degree[2] -= 1;
}
}
#[cfg(any(kani, test))]
pub(super) fn pick_next(used: &[bool; 3], in_degree: &[u8; 3]) -> u8 {
for j in 0..3u8 {
if !used[j as usize] && in_degree[j as usize] == 0 {
return j;
}
}
0
}
#[cfg(any(kani, test))]
pub(super) fn compute_order(e01: bool, e02: bool, e12: bool) -> [u8; 3] {
let mut in_degree = init_in_degree(e01, e02, e12);
let mut order = [0u8; 3];
let mut used = [false; 3];
for slot in &mut order {
let j = pick_next(&used, &in_degree);
*slot = j;
used[j as usize] = true;
remove_edges(j, &mut in_degree, e01, e02, e12);
}
order
}
#[cfg(kani)]
#[kani::proof]
fn proof_hash_determinism_bounded() {
use super::planner::hash_desired_state;
use super::types::{Resource, ResourceType};
let mut r = Resource::default();
r.resource_type = ResourceType::File;
let len: usize = kani::any();
kani::assume(len <= 8);
let buf: [u8; 8] = kani::any();
let content = String::from_utf8_lossy(&buf[..len]).to_string();
r.content = Some(content);
let h1 = hash_desired_state(&r);
let h2 = hash_desired_state(&r);
assert_eq!(h1, h2, "hash_desired_state must be deterministic");
}
#[cfg(kani)]
#[kani::proof]
fn proof_planner_idempotency_bounded() {
use super::planner::hash_desired_state;
use super::types::{Resource, ResourceType};
let mut r = Resource::default();
r.resource_type = ResourceType::Package;
let pkg_idx: u8 = kani::any();
kani::assume(pkg_idx < 4);
let pkg_names = ["vim", "curl", "git", "tmux"];
r.packages = vec![pkg_names[pkg_idx as usize].to_string()];
let stored_hash = hash_desired_state(&r);
let desired_hash = hash_desired_state(&r);
let is_converged = true;
let action_is_noop = is_converged && (stored_hash == desired_hash);
assert!(action_is_noop, "converged + matching hash must be NoOp");
}
#[cfg(kani)]
#[kani::proof]
fn proof_dag_ordering_bounded() {
let dep_01: bool = kani::any(); let dep_02: bool = kani::any(); let dep_12: bool = kani::any();
let order1 = super::compute_order(dep_01, dep_02, dep_12);
let order2 = super::compute_order(dep_01, dep_02, dep_12);
assert_eq!(order1, order2, "DAG ordering must be deterministic");
let pos = |node: u8| order1.iter().position(|&n| n == node).unwrap();
if dep_01 {
assert!(pos(0) < pos(1));
}
if dep_02 {
assert!(pos(0) < pos(2));
}
if dep_12 {
assert!(pos(1) < pos(2));
}
}
#[cfg(kani)]
#[kani::proof]
fn proof_handler_invariant_file() {
use super::planner::hash_desired_state;
use super::types::{Resource, ResourceType};
let mut r = Resource::default();
r.resource_type = ResourceType::File;
r.path = Some("/etc/test.conf".into());
r.content = Some("key=value".into());
let hash_base = hash_desired_state(&r);
r.tags = vec!["web".into(), "production".into()];
let hash_with_tags = hash_desired_state(&r);
r.depends_on = vec!["other-resource".into()];
let hash_with_deps = hash_desired_state(&r);
assert_eq!(hash_base, hash_with_tags, "tags must not affect hash");
assert_eq!(hash_base, hash_with_deps, "depends_on must not affect hash");
}
#[cfg(kani)]
#[kani::proof]
fn proof_handler_invariant_package() {
use super::planner::hash_desired_state;
use super::types::{Resource, ResourceType};
let mut r1 = Resource::default();
r1.resource_type = ResourceType::Package;
r1.packages = vec!["nginx".into()];
let mut r2 = Resource::default();
r2.resource_type = ResourceType::Package;
r2.packages = vec!["nginx".into()];
r2.tags = vec!["web".into()];
let h1 = hash_desired_state(&r1);
let h2 = hash_desired_state(&r2);
assert_eq!(h1, h2, "tags must not affect package hash");
}
#[cfg(kani)]
#[kani::proof]
#[kani::unwind(5)]
fn proof_layer_determinism() {
let n: u8 = kani::any();
kani::assume(n <= 4);
let mut digest1: u32 = 0;
let mut digest2: u32 = 0;
for _ in 0..n {
let file_hash: u32 = kani::any();
digest1 = digest1.wrapping_mul(31).wrapping_add(file_hash);
digest2 = digest2.wrapping_mul(31).wrapping_add(file_hash);
}
assert_eq!(digest1, digest2, "layer build must be deterministic");
}
#[cfg(kani)]
#[kani::proof]
fn proof_store_idempotency() {
let content: u32 = kani::any();
let addr1 = content.wrapping_mul(2654435761); let addr2 = content.wrapping_mul(2654435761);
assert_eq!(addr1, addr2, "store address must be deterministic");
let stored = addr1;
let re_stored = addr2;
assert_eq!(stored, re_stored, "store_put twice must be idempotent");
}
#[cfg(kani)]
#[kani::proof]
fn proof_idempotency_conditional() {
let desired: u32 = kani::any();
let stored: u32 = kani::any();
let status: u8 = kani::any();
kani::assume(status <= 3);
let handler_invariant = stored == desired;
let is_converged = status == 2;
if is_converged && handler_invariant {
let needs_apply = stored != desired;
assert!(
!needs_apply,
"converged + handler invariant must yield NoOp"
);
}
}
#[cfg(kani)]
#[kani::proof]
#[kani::unwind(5)]
fn proof_fleet_convergence() {
let n: u8 = kani::any();
kani::assume(n <= 4);
let mut all_noop = true;
for _ in 0..n {
let desired: u32 = kani::any();
let stored: u32 = kani::any();
kani::assume(stored == desired);
let needs_apply = stored != desired;
if needs_apply {
all_noop = false;
}
}
assert!(
all_noop,
"fleet with all converged resources must be all-NoOp"
);
}
#[cfg(kani)]
#[kani::proof]
fn proof_apply_then_noop() {
let config_hash: u32 = kani::any();
let stored_hash = config_hash; let desired_hash = config_hash; assert_eq!(
stored_hash, desired_hash,
"apply then re-plan must yield NoOp"
);
}