mod apply;
use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher};
use crate::checks::{run_checks, CoreLayer};
use crate::operation::MetadataParams;
use crate::state::SystemState;
use super::{Fix, FixPlan};
const MAX_ITERATIONS: usize = 5;
#[must_use]
pub fn simulate_cascade(
fixes: Vec<Fix>,
mut warnings: Vec<String>,
state: &SystemState,
) -> FixPlan {
if fixes.is_empty() {
return FixPlan { fixes, warnings };
}
let original_failures = run_checks(state, &MetadataParams::default()).failed_layers();
let groups = unique_layers(&fixes);
let mut mutated = state.clone();
let mut resolved: Vec<CoreLayer> = Vec::new();
let mut seen_hashes: HashSet<u64> = HashSet::new();
for (i, layer) in groups.iter().enumerate() {
if i >= MAX_ITERATIONS {
break;
}
let before = run_checks(&mutated, &MetadataParams::default()).failed_layers();
let state_hash = hash_failure_set(&before);
if !seen_hashes.insert(state_hash) {
warnings.push("fix cascade cycle detected \u{2014} manual review required".into());
break;
}
apply_first_fix_for_layer(&fixes, *layer, &mut mutated);
let after_failures = run_checks(&mutated, &MetadataParams::default()).failed_layers();
detect_new_failures(&after_failures, &original_failures, &mut warnings);
find_resolved(&before, &after_failures, &groups, *layer, &mut resolved);
}
let kept = prune_resolved(fixes, &resolved);
FixPlan {
fixes: kept,
warnings,
}
}
fn unique_layers(fixes: &[Fix]) -> Vec<CoreLayer> {
let mut layers = Vec::new();
for fix in fixes {
if !layers.contains(&fix.layer) {
layers.push(fix.layer);
}
}
layers
}
fn apply_first_fix_for_layer(fixes: &[Fix], layer: CoreLayer, mutated: &mut SystemState) {
if let Some(fix) = fixes.iter().find(|f| f.layer == layer) {
apply::apply_fix(fix, mutated);
}
}
fn detect_new_failures(current: &[CoreLayer], original: &[CoreLayer], warnings: &mut Vec<String>) {
for layer in current {
if !original.contains(layer) {
let msg = format!("applying fix revealed new {layer:?} failure (cascade break)");
if !warnings.contains(&msg) {
warnings.push(msg);
}
}
}
}
fn find_resolved(
before: &[CoreLayer],
after: &[CoreLayer],
all_layers: &[CoreLayer],
current: CoreLayer,
resolved: &mut Vec<CoreLayer>,
) {
for layer in all_layers {
if *layer == current || resolved.contains(layer) {
continue;
}
let failed_before = before.contains(layer);
let passes_after = !after.contains(layer);
if failed_before && passes_after {
resolved.push(*layer);
}
}
}
fn hash_failure_set(layers: &[CoreLayer]) -> u64 {
let mut hasher = DefaultHasher::new();
layers.hash(&mut hasher);
hasher.finish()
}
fn prune_resolved(fixes: Vec<Fix>, resolved: &[CoreLayer]) -> Vec<Fix> {
fixes
.into_iter()
.filter(|f| !resolved.contains(&f.layer))
.collect()
}
#[cfg(test)]
#[path = "cascade_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "cascade/apply_tests.rs"]
mod apply_tests;
#[cfg(test)]
#[path = "cascade/apply_mount_tests.rs"]
mod apply_mount_tests;