Model typed SA ports and atomic exchange allocation workflows
This commit is contained in:
@@ -450,21 +450,7 @@ pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
});
|
||||
}
|
||||
}
|
||||
SharedBody::Port { interface, .. } => {
|
||||
if let Some(id) = interface {
|
||||
expect_element(p, &mut out, e.id, *id, "interface", |k| {
|
||||
k == Kind::Interface
|
||||
});
|
||||
} else {
|
||||
warning(
|
||||
&mut out,
|
||||
e.id,
|
||||
"missing_interface",
|
||||
"interface",
|
||||
"Choose a port interface or add an explicit provision/requirement contract.",
|
||||
);
|
||||
}
|
||||
}
|
||||
SharedBody::Port { .. } => crate::ports::validate_port(p, e, &mut out),
|
||||
SharedBody::Scenario {
|
||||
viewpoint,
|
||||
context,
|
||||
@@ -2140,82 +2126,7 @@ fn validate_relation(p: &Project, r: &Relation, out: &mut Vec<Issue>) {
|
||||
for item in r.body.items() {
|
||||
expect_element(p, out, r.id, *item, "items", |k| k == Kind::ExchangeItem);
|
||||
}
|
||||
if let RelationBody::FunctionalExchange {
|
||||
source_port,
|
||||
target_port,
|
||||
..
|
||||
} = &r.body
|
||||
{
|
||||
for (port, owner, outgoing, field) in [
|
||||
(*source_port, source, true, "source_port"),
|
||||
(*target_port, target, false, "target_port"),
|
||||
] {
|
||||
if let Some(port) = port {
|
||||
match p.elements.get(&port) {
|
||||
Some(e)
|
||||
if e.parent == Some(owner)
|
||||
&& matches!(e.shared(),Some(SharedBody::Port{direction,..})if if outgoing{*direction!=PortDirection::In}else{*direction!=PortDirection::Out}) =>
|
||||
{}
|
||||
_ => error(
|
||||
out,
|
||||
r.id,
|
||||
"functional_port",
|
||||
field,
|
||||
"Functional port must belong to the endpoint function with compatible direction.",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if r.kind() == RelationKind::BoundaryExchange {
|
||||
for (e, outgoing, field) in [(a, true, "source"), (b, false, "target")] {
|
||||
match e.shared() {
|
||||
Some(SharedBody::Port { direction, .. })
|
||||
if e.parent
|
||||
.and_then(|id| p.elements.get(&id))
|
||||
.is_some_and(|e| e.kind().is_performer())
|
||||
&& (if outgoing {
|
||||
*direction != PortDirection::In
|
||||
} else {
|
||||
*direction != PortDirection::Out
|
||||
}) => {}
|
||||
_ => error(
|
||||
out,
|
||||
r.id,
|
||||
"boundary_port",
|
||||
field,
|
||||
"Boundary exchange uses participant ports with output/input compatible direction.",
|
||||
),
|
||||
}
|
||||
}
|
||||
let interface_items = |port: &Element| {
|
||||
if let Some(SharedBody::Port {
|
||||
interface: Some(id),
|
||||
..
|
||||
}) = port.shared()
|
||||
&& let Some(SharedBody::Interface { items }) =
|
||||
p.elements.get(id).and_then(Element::shared)
|
||||
{
|
||||
return Some(items);
|
||||
}
|
||||
None
|
||||
};
|
||||
for port in [a, b] {
|
||||
if let Some(items) = interface_items(port) {
|
||||
for item in r.body.items() {
|
||||
if !items.contains(item) {
|
||||
error(
|
||||
out,
|
||||
r.id,
|
||||
"interface_contract",
|
||||
"items",
|
||||
"Exchange item is not included in a referenced port interface.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
crate::ports::validate_port_relation(p, r, out);
|
||||
if let RelationBody::StateTransition {
|
||||
trigger,
|
||||
guard,
|
||||
@@ -2417,51 +2328,7 @@ pub fn allocation_summary(p: &Project, work: Id) -> Option<Id> {
|
||||
}
|
||||
|
||||
fn validate_exchange_allocation(p: &Project, r: &Relation, out: &mut Vec<Issue>) {
|
||||
let (Some(boundary), Some(work)) = (p.relations.get(&r.source()), p.relations.get(&r.target()))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for (function, port, field) in [
|
||||
(work.source(), boundary.source(), "source"),
|
||||
(work.target(), boundary.target(), "target"),
|
||||
] {
|
||||
let performer = p
|
||||
.relations
|
||||
.values()
|
||||
.find(|a| a.kind() == RelationKind::Allocation && a.target() == function)
|
||||
.map(Relation::source);
|
||||
let owner = p.elements.get(&port).and_then(|e| e.parent);
|
||||
if let (Some(performer), Some(owner)) = (performer, owner) {
|
||||
if performer != owner {
|
||||
error(
|
||||
out,
|
||||
r.id,
|
||||
"exchange_allocation_performer",
|
||||
field,
|
||||
"Boundary port owner must perform the corresponding work-exchange endpoint.",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warning(
|
||||
out,
|
||||
r.id,
|
||||
"incomplete_exchange_allocation",
|
||||
field,
|
||||
"Allocate work endpoints to the boundary-port participants.",
|
||||
);
|
||||
}
|
||||
}
|
||||
for item in work.body.items() {
|
||||
if !boundary.body.items().contains(item) {
|
||||
error(
|
||||
out,
|
||||
r.id,
|
||||
"exchange_allocation_items",
|
||||
"items",
|
||||
"The boundary exchange must carry every item of the allocated work exchange.",
|
||||
);
|
||||
}
|
||||
}
|
||||
crate::ports::validate_exchange_allocation(p, r, out);
|
||||
}
|
||||
|
||||
pub fn effective_performer(p: &Project, work: Id) -> Option<Id> {
|
||||
@@ -2534,59 +2401,6 @@ fn validate_relation_context(p: &Project, r: &Relation, out: &mut Vec<Issue>) {
|
||||
"A boundary exchange connects different participants.",
|
||||
);
|
||||
}
|
||||
let ports: Vec<_> = match &r.body {
|
||||
RelationBody::BoundaryExchange { source, target, .. } => vec![*source, *target],
|
||||
RelationBody::FunctionalExchange {
|
||||
source_port,
|
||||
target_port,
|
||||
..
|
||||
} => [*source_port, *target_port].into_iter().flatten().collect(),
|
||||
_ => vec![],
|
||||
};
|
||||
for port in ports {
|
||||
let mut interfaces = BTreeSet::new();
|
||||
if let Some(SharedBody::Port {
|
||||
interface: Some(id),
|
||||
..
|
||||
}) = p.elements.get(&port).and_then(Element::shared)
|
||||
{
|
||||
interfaces.insert(*id);
|
||||
}
|
||||
interfaces.extend(
|
||||
p.relations
|
||||
.values()
|
||||
.filter(|rel| {
|
||||
matches!(
|
||||
rel.kind(),
|
||||
RelationKind::InterfaceProvision | RelationKind::InterfaceRequirement
|
||||
) && rel.source() == port
|
||||
})
|
||||
.map(Relation::target),
|
||||
);
|
||||
if !interfaces.is_empty() {
|
||||
let allowed: BTreeSet<_> = interfaces
|
||||
.iter()
|
||||
.filter_map(|id| p.elements.get(id))
|
||||
.filter_map(|e| match e.shared() {
|
||||
Some(SharedBody::Interface { items }) => Some(items),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.copied()
|
||||
.collect();
|
||||
for item in r.body.items() {
|
||||
if !allowed.contains(item) {
|
||||
error(
|
||||
out,
|
||||
r.id,
|
||||
"port_interface_items",
|
||||
"items",
|
||||
"The exchange carries an item outside a port's declared interface contract.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches!(
|
||||
r.kind(),
|
||||
RelationKind::Availability | RelationKind::StateAction
|
||||
|
||||
Reference in New Issue
Block a user