use super::*; fn port_id(choice: &CommunicationPortChoice) -> Id { match choice { CommunicationPortChoice::Reuse { port } => *port, CommunicationPortChoice::Create { id, .. } => *id, } } pub(super) fn prepare_ports( original: &Project, working: &mut Project, targets: &BTreeMap, plan: &mut TransitionPlan, ) -> BTreeMap { let mut pairs = BTreeMap::new(); for decision in plan.request.communication_endpoints.clone() { let result = (|| -> Result<(Id, Id), String> { if plan.request.direction != TransitionDirection::OaToSa { return Err( "SA endpoint choices apply only to OA→SA communication transition.".into(), ); } let means = original .relations .get(&decision.source) .filter(|r| r.kind() == RelationKind::CommunicationMean) .ok_or("Endpoint decision must select an OA communication means.")?; if !plan.request.sources.contains(&means.id) && !targets.contains_key(&means.id) { return Err("Endpoint decision is outside this transition selection.".into()); } if pairs.contains_key(&decision.source) { return Err("Choose one component-port pair per communication means.".into()); } let mut pending = vec![]; for (participant, choice) in [ (means.source(), &decision.source_port), (means.target(), &decision.target_port), ] { let owner = targets.get(&participant).copied().ok_or("Review each OA participant's SA counterpart before selecting its component port.")?; if working.elements.get(&owner).is_some_and(|e| { e.layer() != Layer::Sa || !matches!(e.kind(), Kind::System | Kind::SystemActor) }) { return Err( "Component-port owner must be the reviewed SA participant counterpart." .into(), ); } match choice { CommunicationPortChoice::Reuse { port } => { let e = working .elements .get(port) .ok_or("Selected component port does not exist.")?; if e.parent != Some(owner) || e.layer() != Layer::Sa || !matches!( crate::ports::port_contract(e), Some(PortContract::Component { .. }) ) { return Err("Selected component port must belong to the reviewed SA participant and have an explicit component contract.".into()); } } CommunicationPortChoice::Create { id, alias, name, description, kind, orientation, } => { if id.is_nil() || working.contains(*id) || pending.iter().any(|e: &Element| e.id == *id) { return Err("New component-port identity must be unused; choose Reuse for an existing port.".into()); } pending.push(Element { id: *id, alias: alias.clone(), name: name.clone(), description: description.clone(), parent: Some(owner), body: ElementBody::shared_in( Layer::Sa, SharedBody::Port { contract: PortContract::Component { kind: *kind, orientation: *orientation, }, legacy: None, }, ), }); } } } let pair = ( port_id(&decision.source_port), port_id(&decision.target_port), ); if pair.0 == pair.1 || targets.get(&means.source()) == targets.get(&means.target()) { return Err("Communication counterparts must have different participant owners; review the participant boundary mapping.".into()); } for e in pending { working.elements.insert(e.id, e.clone()); plan.commands.push(Command::CreateElement(e)); } Ok(pair) })(); match result { Ok(pair) => { pairs.insert(decision.source, pair); } Err(message) => conflict( plan, Some(decision.source), targets.get(&decision.source).copied(), None, message, ), } } pairs } /// Adapt only the transferred endpoint value. Reviewed baseline facts continue /// to retain the actual participant/port identities on their respective sides. pub(crate) fn review_endpoint_fact( project: &Project, request: &TransitionRequest, map: &BTreeMap, source: Id, target: Id, fact: &FacetReview, ) -> Result, String> { let (Some(a), Some(b)) = ( project.relations.get(&source), project.relations.get(&target), ) else { return Ok(None); }; let forward = a.kind() == RelationKind::CommunicationMean && b.kind() == RelationKind::BoundaryExchange; let reverse = a.kind() == RelationKind::BoundaryExchange && b.kind() == RelationKind::CommunicationMean; if !forward && !reverse { return Ok(None); } let (endpoint, is_source) = match fact { FacetReview::Relation(RelationReview::Source(_)) => (a.source(), true), FacetReview::Relation(RelationReview::Target(_)) => (a.target(), false), _ => return Ok(None), }; let mapped = if forward { let decision = request.communication_endpoints.iter().find(|d| d.source == source) .ok_or("Taking an OA endpoint requires this exchange's explicit SA component-port decision.")?; let port = port_id(if is_source { &decision.source_port } else { &decision.target_port }); let expected_owner = map.get(&endpoint).ok_or( "Review the participant counterpart before taking its communication endpoint.", )?; let e = project .elements .get(&port) .ok_or("Selected communication endpoint port does not exist.")?; if e.parent != Some(*expected_owner) || e.layer() != Layer::Sa || !matches!( crate::ports::port_contract(e), Some(PortContract::Component { .. }) ) { return Err( "Selected component port disagrees with the reviewed participant counterpart." .into(), ); } port } else { let owner = project .elements .get(&endpoint) .and_then(|e| e.parent) .ok_or("SA boundary port needs a participant owner.")?; let participant = *map.get(&owner).ok_or( "Review the SA port owner's OA participant counterpart before taking this endpoint.", )?; if !project.elements.get(&participant).is_some_and(|e| { e.layer() == Layer::Oa && matches!(e.kind(), Kind::Entity | Kind::Actor) }) { return Err("Boundary endpoint owner must map to an OA entity or actor.".into()); } participant }; let mut result = fact.clone(); if let FacetReview::Relation(RelationReview::Source(values) | RelationReview::Target(values)) = &mut result { match request.direction.source_layer() { Layer::Oa => values.oa = Observed::Value(mapped), Layer::Sa => values.sa = Observed::Value(mapped), } } Ok(Some(result)) }