Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
@@ -387,6 +387,23 @@ pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
}
|
||||
if let Some(shared) = e.shared() {
|
||||
match shared {
|
||||
SharedBody::ExchangeCategory { family, exchanges } => {
|
||||
for issue in crate::view_options::validate_category(
|
||||
p,
|
||||
e.layer(),
|
||||
e.parent,
|
||||
*family,
|
||||
exchanges,
|
||||
) {
|
||||
error(
|
||||
&mut out,
|
||||
e.id,
|
||||
"exchange_category",
|
||||
issue.field,
|
||||
issue.message,
|
||||
);
|
||||
}
|
||||
}
|
||||
SharedBody::DataType { .. }
|
||||
| SharedBody::DataField { .. }
|
||||
| SharedBody::Interface { .. }
|
||||
@@ -601,6 +618,14 @@ pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
}
|
||||
text(&mut out, d.id, "name", &d.name);
|
||||
text(&mut out, d.id, "description", &d.description);
|
||||
let option_issues = if d.projection == ProjectionKind::Traceability {
|
||||
crate::view_options::validate_options_cross_layer(p, &d.view_options)
|
||||
} else {
|
||||
crate::view_options::validate_options(p, d.layer, &d.view_options)
|
||||
};
|
||||
for issue in option_issues {
|
||||
error(&mut out, d.id, "view_options", issue.field, issue.message);
|
||||
}
|
||||
if let Some(machine) = d.projection.state_machine()
|
||||
&& !p
|
||||
.elements
|
||||
@@ -1256,40 +1281,149 @@ pub fn semantic_representation_compatible(
|
||||
pub fn lifeline_matches_endpoint(p: &Project, line: Id, endpoint: Id) -> Option<bool> {
|
||||
semantic_representation_compatible(p, represented(p, line)?, endpoint)
|
||||
}
|
||||
/// Effective semantic direction, including a Reply that reuses its Call exchange.
|
||||
/// An incomplete reply correlation and a local timer have no inferred endpoints.
|
||||
pub fn message_endpoints(p: &Project, body: &OccurrenceBody) -> Option<(Id, Id)> {
|
||||
/// A single shared answer for scenario admission, drawing, and endpoint pickers.
|
||||
/// Incomplete or ambiguous bindings never choose an arbitrary direction.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MessageEndpointResolution {
|
||||
NotApplicable,
|
||||
Incomplete,
|
||||
Resolved { source: Id, target: Id },
|
||||
Ambiguous,
|
||||
Conflict,
|
||||
}
|
||||
pub fn resolve_message_endpoints(p: &Project, body: &OccurrenceBody) -> MessageEndpointResolution {
|
||||
use MessageEndpointResolution as R;
|
||||
let OccurrenceBody::Exchange {
|
||||
exchange: Some(exchange),
|
||||
message_kind,
|
||||
reply_to,
|
||||
sender,
|
||||
receiver,
|
||||
..
|
||||
} = body
|
||||
else {
|
||||
return None;
|
||||
return R::NotApplicable;
|
||||
};
|
||||
if matches!(message_kind, MessageKind::Timer { .. }) {
|
||||
return None;
|
||||
return R::NotApplicable;
|
||||
}
|
||||
let Some(relation) = p.relations.get(exchange) else {
|
||||
return R::Incomplete;
|
||||
};
|
||||
let mut candidates = vec![relation.body.endpoints()];
|
||||
// A mapped unoriented SA boundary retains the OA communication's possible
|
||||
// directions. Fixed oriented/legacy exchange behavior remains unchanged.
|
||||
let communication = matches!(
|
||||
relation.body,
|
||||
RelationBody::CommunicationMean { .. }
|
||||
| RelationBody::BoundaryExchange {
|
||||
oriented: false,
|
||||
..
|
||||
}
|
||||
);
|
||||
if matches!(
|
||||
relation.body,
|
||||
RelationBody::CommunicationMean {
|
||||
oriented: false,
|
||||
..
|
||||
} | RelationBody::BoundaryExchange {
|
||||
oriented: false,
|
||||
..
|
||||
}
|
||||
) {
|
||||
candidates.push((relation.target(), relation.source()));
|
||||
}
|
||||
if matches!(
|
||||
relation.body,
|
||||
RelationBody::BoundaryExchange {
|
||||
oriented: false,
|
||||
..
|
||||
}
|
||||
) {
|
||||
candidates.retain(|(source, target)| {
|
||||
match (
|
||||
p.elements.get(source).and_then(crate::ports::port_contract),
|
||||
p.elements.get(target).and_then(crate::ports::port_contract),
|
||||
) {
|
||||
(
|
||||
Some(a @ PortContract::Component { .. }),
|
||||
Some(b @ PortContract::Component { .. }),
|
||||
) => {
|
||||
crate::ports::component_allows(a, true)
|
||||
&& crate::ports::component_allows(b, false)
|
||||
}
|
||||
// Legacy direction contracts remain fixed even for an
|
||||
// unoriented declaration; do not reinterpret old ports.
|
||||
_ => (*source, *target) == relation.body.endpoints(),
|
||||
}
|
||||
});
|
||||
}
|
||||
let relation = p.relations.get(exchange)?;
|
||||
if message_kind == &MessageKind::Reply {
|
||||
let Some(reply_to) = reply_to else {
|
||||
return R::Incomplete;
|
||||
};
|
||||
let Some(SharedBody::Occurrence {
|
||||
body:
|
||||
OccurrenceBody::Exchange {
|
||||
call_body @ OccurrenceBody::Exchange {
|
||||
message_kind: MessageKind::Call,
|
||||
exchange: Some(call_exchange),
|
||||
..
|
||||
},
|
||||
..
|
||||
}) = p.elements.get(&(*reply_to)?).and_then(Element::shared)
|
||||
}) = p.elements.get(reply_to).and_then(Element::shared)
|
||||
else {
|
||||
return None;
|
||||
return R::Incomplete;
|
||||
};
|
||||
if call_exchange == exchange {
|
||||
return Some((relation.target(), relation.source()));
|
||||
let R::Resolved { source, target } = resolve_message_endpoints(p, call_body) else {
|
||||
return R::Incomplete;
|
||||
};
|
||||
candidates = vec![(target, source)];
|
||||
} else if communication {
|
||||
let R::Resolved { source, target } = resolve_message_endpoints(p, call_body) else {
|
||||
return R::Incomplete;
|
||||
};
|
||||
let owner = |id| {
|
||||
p.elements
|
||||
.get(&id)
|
||||
.filter(|e| e.kind() == Kind::Port)
|
||||
.and_then(|e| e.parent)
|
||||
.unwrap_or(id)
|
||||
};
|
||||
candidates.retain(|(a, b)| {
|
||||
semantic_representation_compatible(p, owner(*a), owner(target)) == Some(true)
|
||||
&& semantic_representation_compatible(p, owner(*b), owner(source)) == Some(true)
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(relation.body.endpoints())
|
||||
if communication {
|
||||
candidates.retain(|(a, b)| {
|
||||
sender.is_none_or(|line| lifeline_matches_endpoint(p, line, *a) != Some(false))
|
||||
&& receiver.is_none_or(|line| lifeline_matches_endpoint(p, line, *b) != Some(false))
|
||||
});
|
||||
if candidates.is_empty() {
|
||||
return R::Conflict;
|
||||
}
|
||||
if sender.and_then(|line| represented(p, line)).is_none()
|
||||
|| receiver.and_then(|line| represented(p, line)).is_none()
|
||||
{
|
||||
return R::Incomplete;
|
||||
}
|
||||
if candidates.len() > 1 {
|
||||
return R::Ambiguous;
|
||||
}
|
||||
}
|
||||
let Some((source, target)) = candidates.first().copied() else {
|
||||
return R::Conflict;
|
||||
};
|
||||
R::Resolved { source, target }
|
||||
}
|
||||
/// Compatibility adapter: only a resolved direction has inferred endpoints.
|
||||
pub fn message_endpoints(p: &Project, body: &OccurrenceBody) -> Option<(Id, Id)> {
|
||||
match resolve_message_endpoints(p, body) {
|
||||
MessageEndpointResolution::Resolved { source, target } => Some((source, target)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Only report order guaranteed by a common sequence or an enclosing branch path.
|
||||
/// A call in one alternative does not become an unconditional call after the fragment.
|
||||
@@ -1442,7 +1576,9 @@ fn validate_message(p: &Project, e: &Element, body: &OccurrenceBody, out: &mut V
|
||||
ScenarioViewpoint::Functional => relation.kind() == RelationKind::FunctionalExchange,
|
||||
ScenarioViewpoint::ParticipantInteraction => matches!(
|
||||
relation.kind(),
|
||||
RelationKind::Interaction | RelationKind::BoundaryExchange
|
||||
RelationKind::Interaction
|
||||
| RelationKind::BoundaryExchange
|
||||
| RelationKind::CommunicationMean
|
||||
),
|
||||
ScenarioViewpoint::BoundaryInterface => matches!(
|
||||
relation.kind(),
|
||||
@@ -1922,6 +2058,7 @@ fn validate_occurrence(p: &Project, e: &Element, body: &OccurrenceBody, out: &mu
|
||||
RelationKind::Interaction
|
||||
| RelationKind::FunctionalExchange
|
||||
| RelationKind::BoundaryExchange
|
||||
| RelationKind::CommunicationMean
|
||||
) => {}
|
||||
_ => error(
|
||||
out,
|
||||
@@ -2136,18 +2273,20 @@ fn validate_relation(p: &Project, r: &Relation, out: &mut Vec<Issue>) {
|
||||
if let RelationBody::ExchangeAllocation { .. } = r.body {
|
||||
match (p.relations.get(&source), p.relations.get(&target)) {
|
||||
(Some(a), Some(b))
|
||||
if a.kind() == RelationKind::BoundaryExchange
|
||||
if ((a.kind() == RelationKind::BoundaryExchange
|
||||
&& matches!(
|
||||
b.kind(),
|
||||
RelationKind::FunctionalExchange | RelationKind::Interaction
|
||||
)
|
||||
))
|
||||
|| (a.kind() == RelationKind::CommunicationMean
|
||||
&& b.kind() == RelationKind::Interaction))
|
||||
&& p.layer_of(source) == p.layer_of(target) => {}
|
||||
_ => error(
|
||||
out,
|
||||
r.id,
|
||||
"exchange_allocation",
|
||||
"endpoints",
|
||||
"Exchange allocation relates a boundary exchange to a work exchange in the same layer.",
|
||||
"Exchange allocation relates communication means to an OA interaction, or a boundary exchange to a work exchange, in the same layer.",
|
||||
),
|
||||
};
|
||||
validate_exchange_allocation(p, r, out);
|
||||
@@ -2398,7 +2537,7 @@ pub fn trace_permits(p: &Project, t: &Trace) -> bool {
|
||||
_ => false,
|
||||
},
|
||||
_ => {
|
||||
matches!((p.relations.get(&t.oa),p.relations.get(&t.sa)),(Some(a),Some(b))if t.kind==TraceKind::Exchange&&a.kind()==RelationKind::Interaction&&b.kind()==RelationKind::FunctionalExchange)
|
||||
matches!((p.relations.get(&t.oa),p.relations.get(&t.sa)),(Some(a),Some(b))if t.kind==TraceKind::Exchange&&crate::transitions::exchange_counterparts(a.kind(), b.kind()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2739,6 +2878,23 @@ fn validate_occurrence_context(
|
||||
OccurrenceBody::Exchange {
|
||||
sender, receiver, ..
|
||||
} => {
|
||||
match resolve_message_endpoints(p, body) {
|
||||
MessageEndpointResolution::Conflict => error(
|
||||
out,
|
||||
e.id,
|
||||
"scenario_endpoint",
|
||||
"sender",
|
||||
"Sender and receiver must agree with one permitted communication direction and the correlated Call.",
|
||||
),
|
||||
MessageEndpointResolution::Ambiguous => warning(
|
||||
out,
|
||||
e.id,
|
||||
"ambiguous_message_direction",
|
||||
"sender",
|
||||
"Bind explicit participant lifelines to resolve the communication direction; shared roles allow both directions.",
|
||||
),
|
||||
_ => {}
|
||||
}
|
||||
if let Some((source, target)) = message_endpoints(p, body) {
|
||||
check(out, *sender, Some(source), "sender");
|
||||
check(out, *receiver, Some(target), "receiver");
|
||||
|
||||
Reference in New Issue
Block a user