use super::*; use crate::model::*; use crate::transitions::Drift; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet, VecDeque}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct LinkedSemanticImpact { pub objects: KeyPair, pub traces: Vec, pub drift: Drift, pub unreviewed: bool, } const MAX_LINKED_OBJECTS: usize = 4_096; const MAX_LINKED_EDGES: usize = 32_768; /// Derived dependency diagnostics, never another historical baseline. Follow /// direct references/children, then only reusable dictionary/event definitions. /// Association endpoints and ordinary architecture neighborhoods do not expand. pub fn linked_semantic_impacts( project: &Project, trace: &Trace, references: &BTreeMap, ) -> Result, String> { let mut children: BTreeMap> = BTreeMap::new(); for element in project.elements.values() { if let Some(parent) = element.parent { children.entry(parent).or_default().push(element.id); } } let mut contracts: BTreeMap> = BTreeMap::new(); for relation in project.relations.values() { if let RelationBody::InterfaceItemAllocation { source, .. } = relation.body { contracts.entry(source).or_default().push(relation.id); } } let direct = |id| -> Vec { let mut out = vec![]; if let Some(element) = project.elements.get(&id) { out.extend( crate::references::element_references(element) .into_iter() .filter(|r| Some(*r) != element.parent), ); } if let Some(relation) = project.relations.get(&id) { out.extend(crate::references::relation_references(relation)); } out.extend(children.get(&id).into_iter().flatten().copied()); out.extend(contracts.get(&id).into_iter().flatten().copied()); out }; let expandable = |id| { project.elements.get(&id).is_some_and(|e| { matches!( e.kind(), Kind::DataType | Kind::DataField | Kind::ExchangeItem | Kind::Interface | Kind::Event ) }) || project .relations .get(&id) .is_some_and(|r| matches!(r.body, RelationBody::InterfaceItemAllocation { .. })) }; let mut edges = 0usize; let mut traverse = |root| -> Result, String> { let mut seen = BTreeSet::from([root]); let first = direct(root); edges += first.len(); if edges > MAX_LINKED_EDGES { return Err( "Linked semantic dependency work limit exceeded; narrow the review scope".into(), ); } let mut queue: VecDeque<_> = first.into(); while let Some(id) = queue.pop_front() { if !seen.insert(id) { continue; } if seen.len() > MAX_LINKED_OBJECTS || edges > MAX_LINKED_EDGES { return Err( "Linked semantic dependency work limit exceeded; narrow the review scope" .into(), ); } if expandable(id) { let next = direct(id); edges += next.len(); if edges > MAX_LINKED_EDGES { return Err( "Linked semantic dependency work limit exceeded; narrow the review scope" .into(), ); } queue.extend(next); } } seen.remove(&root); Ok(seen) }; let oa = traverse(trace.oa)?; let sa = traverse(trace.sa)?; let mut traced: BTreeMap<(Id, Id), Vec<&Trace>> = BTreeMap::new(); for t in project.traces.values() { traced.entry((t.oa, t.sa)).or_default().push(t); } let mut pairs = vec![]; let mut used = BTreeSet::new(); for id in oa { let target = references.get(&id).copied(); if let Some(id) = target { used.insert(id); } pairs.push(KeyPair { oa: Some(id), sa: target, }); } for id in sa.difference(&used) { pairs.push(KeyPair { oa: None, sa: Some(*id), }); } let mut result = vec![]; for objects in pairs { if objects.oa == Some(trace.oa) && objects.sa == Some(trace.sa) { continue; } let links = objects .oa .zip(objects.sa) .and_then(|pair| traced.get(&pair)); let mut flags = (false, false, links.is_none()); let mut ids = vec![]; for linked in links.into_iter().flatten() { ids.push(linked.id); let facts = super::catalog::own_semantic_facets(project, linked, references)?; if facts.is_empty() { flags.2 = true; } for fact in facts { match fact.drift { Drift::OaChanged => flags.0 = true, Drift::SaChanged => flags.1 = true, Drift::BothChanged => { flags.0 = true; flags.1 = true; } Drift::Unreviewed => flags.2 = true, _ => {} } } } let drift = match flags { (true, true, _) => Drift::BothChanged, (true, false, _) => Drift::OaChanged, (false, true, _) => Drift::SaChanged, (false, false, true) => Drift::Unreviewed, _ => Drift::Unchanged, }; result.push(LinkedSemanticImpact { objects, traces: ids, drift, unreviewed: flags.2, }); } Ok(result) }