use super::catalog::{collection, order_collection, sides, write_scalar}; use super::*; use crate::model::*; use crate::transitions::TransitionDirection; use std::collections::{BTreeMap, BTreeSet}; pub(crate) fn set_collection( p: &mut Project, id: Id, which: ReferenceCollection, list: Vec, ) -> Result<(), String> { if list.iter().collect::>().len() != list.len() { return Err("Collection contains duplicate members".into()); } use ReferenceCollection as C; if which == C::Steps { let e = p.elements.get_mut(&id).ok_or("Missing sequence")?; if let Some(steps) = e.steps_mut() { *steps = list; return Ok(()); } if let Some(SharedBody::Occurrence { body: OccurrenceBody::Branch { occurrences, .. }, .. }) = e.shared_mut() { *occurrences = list; return Ok(()); } return Err("Target has no ordered steps".into()); } let slot = match which { C::Steps => unreachable!(), C::FragmentBranches => match p.elements.get_mut(&id).and_then(Element::shared_mut) { Some(SharedBody::Occurrence { body: OccurrenceBody::Fragment { branches, .. }, .. }) => Some(branches), _ => None, }, C::FunctionInputItems => match p.elements.get_mut(&id).and_then(Element::shared_mut) { Some(SharedBody::Port { contract: PortContract::FunctionInput { incoming_items }, .. }) => Some(incoming_items), _ => None, }, C::FunctionOutputItems => match p.elements.get_mut(&id).and_then(Element::shared_mut) { Some(SharedBody::Port { contract: PortContract::FunctionOutput { outgoing_items }, .. }) => Some(outgoing_items), _ => None, }, C::SignalItems => match p.elements.get_mut(&id).and_then(Element::shared_mut) { Some(SharedBody::Event { body: EventBody::Signal { items }, }) => Some(items), _ => None, }, C::ExchangeItems => match p.relations.get_mut(&id).map(|r| &mut r.body) { Some( RelationBody::Interaction { items, .. } | RelationBody::FunctionalExchange { items, .. } | RelationBody::BoundaryExchange { items, .. } | RelationBody::CommunicationMean { items, .. }, ) => Some(items), _ => None, }, C::TransitionTriggers => match p.relations.get_mut(&id).map(|r| &mut r.body) { Some(RelationBody::StateTransition { triggers, .. }) => Some(triggers), _ => None, }, C::TransitionEffects => match p.relations.get_mut(&id).map(|r| &mut r.body) { Some(RelationBody::StateTransition { effects, .. }) => Some(effects), _ => None, }, C::Constrained => match p.elements.get_mut(&id).and_then(Element::shared_mut) { Some(SharedBody::Constraint { constrained, .. }) => Some(constrained), _ => None, }, C::CategoryExchanges => match p.elements.get_mut(&id).and_then(Element::shared_mut) { Some(SharedBody::ExchangeCategory { exchanges, .. }) => Some(exchanges), _ => None, }, } .ok_or("Collection is inapplicable on target")?; *slot = list; Ok(()) } pub(crate) fn chosen(pair: &KeyPair, layer: Layer) -> Option<&T> { match layer { Layer::Oa => pair.oa.as_ref(), Layer::Sa => pair.sa.as_ref(), } } pub(crate) fn value(review: &Reviewed, layer: Layer) -> Result<&T, String> { match match layer { Layer::Oa => &review.oa, Layer::Sa => &review.sa, } { Observed::Value(v) => Ok(v), Observed::NotApplicable => { Err("Facet does not exist in the selected constructor branch".into()) } } } pub(crate) fn directional_map( map: &BTreeMap, direction: TransitionDirection, ) -> Result, String> { if direction == TransitionDirection::OaToSa { return Ok(map.clone()); } let mut inverse = BTreeMap::new(); for (oa, sa) in map { if inverse.insert(*sa, *oa).is_some_and(|old| old != *oa) { return Err(format!( "Reverse reference {sa} has multiple OA correspondences; supply an unambiguous mapping" )); } } Ok(inverse) } pub(crate) fn remap_record( record: &mut SemanticRecord, map: &BTreeMap, ) -> Result<(), String> { let mut missing = BTreeSet::new(); let mut remap = |id: &mut Id| { if let Some(new) = map.get(id) { *id = *new; } else { missing.insert(*id); } }; match record { SemanticRecord::Element(e) => crate::references::visit_element_refs(e, &mut remap), SemanticRecord::Relation(r) => crate::references::visit_relation_refs(r, &mut remap), SemanticRecord::Artifact(a) => remap(&mut a.owner), } if missing.is_empty() { Ok(()) } else { Err(format!( "Missing explicit reference correspondence: {missing:?}" )) } } pub(crate) fn remap_fact( fact: &FacetReview, map: &BTreeMap, source: Layer, ) -> Result { // Remap only the selected observation. Keys remain in canonical OA/SA space. let mut json = serde_json::to_value(fact).map_err(|e| e.to_string())?; let base = SemanticBaseline { facets: vec![fact.clone()], }; let side = match source { Layer::Oa => "oa", Layer::Sa => "sa", }; let mut missing = BTreeSet::new(); for (path, kind) in historical_source_slots(&base) { if kind != HistoricalSourceKind::Reference { continue; } let rest = &path[2..]; if !rest.windows(2).any(|w| w[0] == side && w[1] == "Value") { continue; } let mut leaf = &mut json; for part in rest { leaf = if leaf.is_array() { leaf.get_mut( part.parse::() .map_err(|_| "Invalid historical array index")?, ) } else { leaf.get_mut(part) } .ok_or("Invalid historical source slot")?; } let id: Id = serde_json::from_value(leaf.clone()).map_err(|e| e.to_string())?; if let Some(new) = map.get(&id) { *leaf = serde_json::to_value(new).unwrap(); } else { missing.insert(id); } } if !missing.is_empty() { return Err(format!( "Missing explicit reference correspondence: {missing:?}" )); } serde_json::from_value(json).map_err(|e| e.to_string()) } pub(crate) fn check_keys(selector: &FacetSelector, map: &BTreeMap) -> Result<(), String> { fn pair(keys: &KeyPair, map: &BTreeMap) -> Result<(), String> { if let (Some(oa), Some(sa)) = (keys.oa, keys.sa) && map.get(&oa) != Some(&sa) { return Err("Key pair disagrees with frozen reference correspondence".into()); } if keys.oa.is_none() && keys.sa.is_none() { return Err("Empty key pair".into()); } Ok(()) } match selector { FacetSelector::Member { keys, .. } | FacetSelector::Binding(keys) | FacetSelector::Child(keys) | FacetSelector::Incident { keys, .. } | FacetSelector::Artifact { keys, .. } => pair(keys, map), FacetSelector::Annotation(keys) => { if let (Some(oa), Some(sa)) = (&keys.oa, &keys.sa) { if oa.path.len() != sa.path.len() { return Err("Annotation path pairing disagrees".into()); } for (oa, sa) in oa .path .iter() .chain([&oa.field]) .zip(sa.path.iter().chain([&sa.field])) { pair( &KeyPair { oa: Some(*oa), sa: Some(*sa), }, map, )?; } } if keys.oa.is_none() && keys.sa.is_none() { return Err("Empty annotation pair".into()); } Ok(()) } FacetSelector::Literal(keys) => { if keys .oa .as_ref() .zip(keys.sa.as_ref()) .is_some_and(|(oa, sa)| oa != sa) { return Err( "Enum literal identity is exact text; renaming is removal/addition".into(), ); } Ok(()) } _ => Ok(()), } } /// Apply just one selected fact to an in-memory expected project. The caller /// subsequently prepares ordinary Commands and checks every induced difference. pub(crate) fn write_fact( p: &mut Project, target: Id, fact: &FacetReview, from: Layer, target_layer: Layer, preserve_target_only: bool, ) -> Result<(), String> { match fact { FacetReview::Member { collection: which, keys, review, } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let present = *value(review, from)?; let key = chosen(keys, target_layer) .copied() .ok_or("Map the target collection member explicitly")?; let mut list = collection(p, target, *which).ok_or("Inapplicable target collection")?; if present { if !list.contains(&key) { list.push(key); } } else { list.retain(|id| *id != key); } set_collection(p, target, *which, list) } FacetReview::Order { collection: which, review, } => { let supplied = value(review, from)?.clone(); let current = collection(p, target, order_collection(*which)) .ok_or("Inapplicable target order")?; let supplied_set: BTreeSet<_> = supplied.iter().copied().collect(); let current_set: BTreeSet<_> = current.iter().copied().collect(); if supplied_set != current_set { if preserve_target_only && supplied_set.is_subset(¤t_set) && current .iter() .filter(|id| supplied_set.contains(id)) .copied() .collect::>() == supplied { return Ok(()); } return Err("Order must contain every surviving target member exactly once; target-only insertion requires an explicit final order".into()); } set_collection(p, target, order_collection(*which), supplied) } FacetReview::Literal { keys, review } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let present = *value(review, from)?; let key = chosen(keys, target_layer).ok_or("Missing literal key on target")?; let Some(SharedBody::DataType { definition: DataTypeDefinition::Enumeration { literals }, .. }) = p.elements.get_mut(&target).and_then(Element::shared_mut) else { return Err("Target is not an enumeration".into()); }; if present { if !literals.contains(key) { literals.push(key.clone()); } } else { literals.retain(|v| v != key); } Ok(()) } FacetReview::Annotation { keys, review } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let key = chosen(keys, target_layer).ok_or("Missing target annotation key")?; let row = value(review, from)?.clone(); if row.as_ref().is_some_and(|v| AnnotationKey::from(v) != *key) { return Err( "Annotation candidate changes its row key; review removal/addition".into(), ); } let Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { values, .. }, .. }) = p.elements.get_mut(&target).and_then(Element::shared_mut) else { return Err("Target has no message annotations".into()); }; replace_keyed_row(values, row, |v| AnnotationKey::from(v) == *key); Ok(()) } FacetReview::Binding { keys, review } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let key = *chosen(keys, target_layer).ok_or("Missing target binding key")?; let row = value(review, from)?.clone(); if row.as_ref().is_some_and(|v| v.referenced != key) { return Err( "Binding candidate changes its row key; review removal/addition".into(), ); } let Some(SharedBody::Occurrence { body: OccurrenceBody::ScenarioUse { bindings, .. }, .. }) = p.elements.get_mut(&target).and_then(Element::shared_mut) else { return Err("Target has no scenario bindings".into()); }; replace_keyed_row(bindings, row, |v| v.referenced == key); Ok(()) } FacetReview::Child { keys, review } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let key = chosen(keys, target_layer).ok_or("Map target child explicitly")?; let e = p .elements .get_mut(key) .ok_or("Target child does not exist; create it through transition mapping")?; if *value(review, from)? { e.parent = Some(target); } else if e.parent == Some(target) { e.parent = None; } Ok(()) } FacetReview::Incident { keys, end, review } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let key = chosen(keys, target_layer).ok_or("Map incident relation explicitly")?; if !*value(review, from)? { return Err("Unlinking an incident relation requires an explicit final endpoint through the relation trace".into()); } let relation = p .relations .get_mut(key) .ok_or("Mapped incident relation is absent")?; let (a, b) = relation.body.endpoints(); match end { IncidentEnd::Source => relation.body.set_endpoints(target, b), IncidentEnd::Target => relation.body.set_endpoints(a, target), } Ok(()) } FacetReview::Artifact { keys, review } => { if preserve_target_only && chosen(keys, from).is_none() { return Ok(()); } let key = chosen(keys, target_layer).ok_or("Map artifact explicitly")?; if let ArtifactReview::Presence(review) = review { if !*value(review, from)? { p.artifact_links.remove(key); return Ok(()); } if p.artifact_links.get(key).is_some_and(|a| a.owner == target) { return Ok(()); } return Err("Artifact creation requires a complete typed candidate".into()); } let a = p .artifact_links .get_mut(key) .ok_or("Artifact candidate is absent")?; if a.owner != target { return Err( "Artifact metadata can only be reviewed on its currently attached owner trace" .into(), ); } match review { ArtifactReview::Presence(_) => unreachable!(), ArtifactReview::Name(r) => a.name = value(r, from)?.clone(), ArtifactReview::Description(r) => a.description = value(r, from)?.clone(), ArtifactReview::Owner(r) => a.owner = *value(r, from)?, ArtifactReview::Relation(r) => a.relation = *value(r, from)?, ArtifactReview::Uri(r) => a.uri = value(r, from)?.clone(), ArtifactReview::Revision(r) => a.revision = value(r, from)?.clone(), ArtifactReview::Fragment(r) => a.fragment = value(r, from)?.clone(), } Ok(()) } _ => { let (oa, sa) = sides(fact); let side = match from { Layer::Oa => oa, Layer::Sa => sa, }; let value = side .get("Value") .ok_or("Selected branch is not applicable")? .clone(); write_scalar(p, target, &fact.selector(), value) } } } // Reviewing an unordered row does not authorize changing its authored position. // Preserve surviving entries in place; only genuinely new rows append. fn replace_keyed_row( rows: &mut Vec, row: Option, matches: impl FnMut(&T) -> bool, ) { match (rows.iter().position(matches), row) { (Some(index), Some(row)) => { if rows[index] != row { rows[index] = row; } } (Some(index), None) => { rows.remove(index); } (None, Some(row)) => rows.push(row), (None, None) => {} } } pub(crate) fn constructor_contains(group: &FacetSelector, facet: &FacetSelector) -> bool { use FacetSelector as F; match group { F::Data(DataFacet::Constructor) => { matches!(facet,F::Data(f) if *f != DataFacet::Unit) || matches!(facet, F::Literal(_)) } F::Port(PortFacet::Constructor) => matches!( facet, F::Port(_) | F::Member { collection: ReferenceCollection::FunctionInputItems | ReferenceCollection::FunctionOutputItems, .. } ), F::Occurrence(OccurrenceFacet::Constructor) => { matches!(facet,F::Occurrence(f) if !matches!(f,OccurrenceFacet::Precondition | OccurrenceFacet::Postcondition)) || matches!( facet, F::Annotation(_) | F::Binding(_) | F::Member { collection: ReferenceCollection::Steps | ReferenceCollection::FragmentBranches, .. } | F::Order(_) ) } F::Occurrence(OccurrenceFacet::MessageKind) => matches!( facet, F::Occurrence( OccurrenceFacet::MessageKind | OccurrenceFacet::TimerExpression | OccurrenceFacet::Operation ) ), F::Event(EventFacet::Constructor) => matches!( facet, F::Event(_) | F::Member { collection: ReferenceCollection::SignalItems, .. } ), F::State(StateFacet::Kind) => facet == group, _ => false, } } pub(crate) fn copy_constructor( p: &mut Project, target: Id, source: &SemanticRecord, group: &FacetSelector, ) -> Result<(), String> { let SemanticRecord::Element(source) = source else { return Err("Constructor source must be an element".into()); }; let target = p .elements .get_mut(&target) .ok_or("Missing target element")?; let a = source.shared().ok_or("Source has no shared constructor")?; let b = target .shared_mut() .ok_or("Target has no shared constructor")?; match (group, a, b) { ( FacetSelector::Data(DataFacet::Constructor), SharedBody::DataType { definition, default, .. }, SharedBody::DataType { definition: d, default: v, .. }, ) => { *d = definition.clone(); *v = default.clone(); } ( FacetSelector::Port(PortFacet::Constructor), SharedBody::Port { contract, legacy }, SharedBody::Port { contract: c, legacy: l, }, ) => { *c = contract.clone(); *l = legacy.clone(); } ( FacetSelector::Occurrence(OccurrenceFacet::Constructor), SharedBody::Occurrence { body, .. }, SharedBody::Occurrence { body: b, .. }, ) => *b = body.clone(), ( FacetSelector::Occurrence(OccurrenceFacet::MessageKind), SharedBody::Occurrence { body: OccurrenceBody::Exchange { message_kind, .. }, .. }, SharedBody::Occurrence { body: OccurrenceBody::Exchange { message_kind: k, .. }, .. }, ) => *k = message_kind.clone(), ( FacetSelector::Event(EventFacet::Constructor), SharedBody::Event { body }, SharedBody::Event { body: b }, ) => *b = body.clone(), ( FacetSelector::State(StateFacet::Kind), SharedBody::State { kind, .. }, SharedBody::State { kind: k, .. }, ) => *k = *kind, _ => return Err("Constructor family mismatch".into()), } Ok(()) }