Complete source 6 engineering workflows and preserve release validation evidence

This commit is contained in:
Codex
2026-09-06 21:49:04 -07:00
parent 2b2d2bc18b
commit 4b6b5fc197
290 changed files with 48271 additions and 1791 deletions
+172 -16
View File
@@ -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");