Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
617 lines
22 KiB
Rust
617 lines
22 KiB
Rust
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<Id>,
|
|
) -> Result<(), String> {
|
|
if list.iter().collect::<BTreeSet<_>>().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<T>(pair: &KeyPair<T>, layer: Layer) -> Option<&T> {
|
|
match layer {
|
|
Layer::Oa => pair.oa.as_ref(),
|
|
Layer::Sa => pair.sa.as_ref(),
|
|
}
|
|
}
|
|
pub(crate) fn value<T>(review: &Reviewed<T>, 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<Id, Id>,
|
|
direction: TransitionDirection,
|
|
) -> Result<BTreeMap<Id, Id>, 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<Id, Id>,
|
|
) -> 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<Id, Id>,
|
|
source: Layer,
|
|
) -> Result<FacetReview, String> {
|
|
// 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::<usize>()
|
|
.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<Id, Id>) -> Result<(), String> {
|
|
fn pair(keys: &KeyPair<Id>, map: &BTreeMap<Id, Id>) -> 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::<Vec<_>>()
|
|
== 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<T: PartialEq>(
|
|
rows: &mut Vec<T>,
|
|
row: Option<T>,
|
|
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(())
|
|
}
|