Files
ARCADIA-Studio/crates/oa-core/src/semantic/mutate.rs
T
Codex e2ecfea69b
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Checkpoint source 7 workflows and in-progress diagram authoring redesign
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.
2026-09-10 10:06:55 -07:00

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(&current_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(())
}