use palisade_config::{Config, ConfigChange, PolicyChange, PolicyConfig, RootTag};
use std::path::PathBuf;
fn main() {
println!("=== Config Diff: No Changes ===");
let config_v1 = Config::default();
let config_v2 = Config::default();
let all_changes = config_v1.diff(&config_v2).expect("config diff");
println!(" changes detected: {}", all_changes.len());
for change in &all_changes {
println!(" - {:?}", change);
}
println!("\n=== Config Diff: Path Changes ===");
let config_v1 = Config::default();
let mut config_v2 = Config::default();
config_v2.deception.decoy_paths = vec![
PathBuf::from("/tmp/.credentials"), PathBuf::from("/srv/.honeypot"), ]
.into_boxed_slice();
config_v2.deception.decoy_paths = config_v1
.deception
.decoy_paths
.iter()
.cloned()
.chain(std::iter::once(PathBuf::from("/srv/.honeypot-new")))
.collect::<Vec<_>>()
.into_boxed_slice();
let changes = config_v1.diff(&config_v2).expect("config diff");
for change in &changes {
match change {
ConfigChange::PathAdded { path } => {
println!(" PathAdded: {:?}", path);
}
ConfigChange::PathRemoved { path } => {
println!(" PathRemoved: {:?}", path);
}
ConfigChange::RootTagChanged { old_hash, new_hash } => {
println!(" RootTagChanged: {}... → {}...", old_hash, new_hash);
}
ConfigChange::CapabilitiesChanged { field, old, new } => {
println!(" CapabilitiesChanged: {} = {} → {}", field, old, new);
}
}
}
println!("\n=== Config Diff: Syscall Monitor Toggle ===");
let config_v1 = Config::default(); let mut config_v2 = Config::default();
config_v2.telemetry.enable_syscall_monitor = true;
let changes = config_v1.diff(&config_v2).expect("config diff");
for change in &changes {
if let ConfigChange::CapabilitiesChanged { field, old, new } = change {
println!(" {field}: {old} → {new}");
}
}
let has_cap_change = changes.iter().any(|c| {
matches!(c, ConfigChange::CapabilitiesChanged { field, .. } if *field == "enable_syscall_monitor")
});
assert!(has_cap_change, "Syscall monitor change must be detected");
println!(" [OK] Capability change detected.");
println!("\n=== Config Diff: Root Tag Rotation ===");
let config_v1 = Config::default();
let mut config_v2 = Config::default();
config_v2.deception.root_tag = RootTag::generate().expect("generate");
let changes = config_v1.diff(&config_v2).expect("config diff");
let tag_change = changes.iter().find_map(|c| {
if let ConfigChange::RootTagChanged { old_hash, new_hash } = c {
Some((old_hash.as_str(), new_hash.as_str()))
} else {
None
}
});
if let Some((old, new)) = tag_change {
println!(" old_hash prefix (8 bytes): {}", old);
println!(" new_hash prefix (8 bytes): {}", new);
assert_eq!(old.len(), 16, "Hash prefix must be 16 hex chars (8 bytes)");
assert_ne!(old, new);
println!(" [OK] Root tag rotation detected with minimal exposure.");
} else {
println!(
" [NOTE] Same root tag in both (both generated same entropy — astronomically unlikely)"
);
}
println!("\n=== Policy Diff: Threshold Change ===");
let mut policy_v1 = PolicyConfig::default();
let mut policy_v2 = PolicyConfig::default();
policy_v1.scoring.alert_threshold = 50.0;
policy_v2.scoring.alert_threshold = 70.0;
let changes = policy_v1.diff(&policy_v2).expect("policy diff");
for change in &changes {
if let PolicyChange::ThresholdChanged { field, old, new } = change {
println!(" {field}: {old:.1} → {new:.1}");
let direction = if new > old {
"tightened (higher bar for alerts)"
} else {
"relaxed (lower bar for alerts)"
};
println!(" interpretation: threshold {direction}");
}
}
println!("\n=== Policy Diff: Response Rules Change ===");
let policy_v1 = PolicyConfig::default(); let mut policy_v2 = PolicyConfig::default();
policy_v2.response.rules.pop();
let changes = policy_v1.diff(&policy_v2).expect("policy diff");
for change in &changes {
if let PolicyChange::ResponseRulesChanged {
old_count,
new_count,
} = change
{
println!(" response rules: {old_count} → {new_count}");
if new_count < old_count {
println!(" WARNING: Response coverage reduced — verify intentional");
}
}
}
println!("\n=== Policy Diff: Suspicious Processes ===");
let policy_v1 = PolicyConfig::default(); let mut policy_v2 = PolicyConfig::default();
policy_v2.deception.suspicious_processes = vec![
"mimikatz".to_string(),
"procdump".to_string(),
"bloodhound".to_string(), "rubeus".to_string(), ]
.into_boxed_slice();
let changes = policy_v1.diff(&policy_v2).expect("policy diff");
for change in &changes {
match change {
PolicyChange::SuspiciousProcessAdded { pattern } => {
println!(" Added : {:?}", pattern);
}
PolicyChange::SuspiciousProcessRemoved { pattern } => {
println!(" Removed : {:?}", pattern);
}
_ => {}
}
}
println!("\n=== Hot-Reload Pattern ===");
let running_policy = PolicyConfig::default();
let new_policy = PolicyConfig::default();
let changes = running_policy.diff(&new_policy).expect("policy diff");
let safe_to_apply = changes.iter().all(
|c| !matches!(c, PolicyChange::ResponseRulesChanged { new_count, .. } if *new_count == 0),
);
if safe_to_apply {
println!(
" Hot-reload: SAFE ({} changes, no critical degradation)",
changes.len()
);
} else {
println!(" Hot-reload: BLOCKED — would remove all response rules");
}
println!("\nAll diff examples completed.");
}