///|
/// One stable propagation path from the failed asset to an affected asset.
pub(all) struct ImpactPath {
  affected_asset : AssetRef
  path : Array[AssetRef]
}

///|
/// Deduplicated impact propagation results for one simulated incident.
pub(all) struct IncidentImpact {
  failed_asset : AssetRef
  affected : Array[AssetRef]
  paths : Array[ImpactPath]
}

///|
fn extend_path(path : Array[AssetRef], asset : AssetRef) -> Array[AssetRef] {
  let result : Array[AssetRef] = []
  for item in path {
    result.push(item)
  }
  result.push(asset)
  result
}

///|
/// Simulates failure propagation along dependency edges.
///
/// Each affected asset is returned once. Paths use breadth-first traversal and
/// preserve the dependency input order, making the result deterministic.
pub fn simulate_incident_impact(
  failed_asset~ : AssetRef,
  assets~ : Array[AssetRef],
  dependencies~ : Array[Dependency],
) -> IncidentImpact raise ModelError {
  let (adjacency, _) = build_adjacency(assets, dependencies)
  ensure_acyclic(assets, adjacency)
  let failed_key = asset_ref_key(failed_asset)
  let failed_index = match asset_index(assets, failed_key) {
    Some(index) => index
    None => raise ModelError::MissingAsset(failed_key)
  }
  let visited : Array[Bool] = []
  for _ in 0..