//! Throwaway R3 experiment. Nothing in oa-core depends on this crate. //! A carrier stores independently reviewed facets; it is NOT a past valid model. use oa_core::*; use serde::{Deserialize, Serialize}; pub mod typed_reviews; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum OwnRecord { Element { parent: Option, body: ElementBody, }, Relation { body: RelationBody, }, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct MemberPair { pub oa: Option, pub sa: Option, } /// Deliberately finite proof subset, not the final exhaustive R3 catalog. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum Facet { IntegerMinimum, IntegerMaximum, DataUnit, FieldMinimum, StateGuard, Trigger(MemberPair), Step(MemberPair), StepsOrder, /// Definition AND default form one explicit constructor replacement group. DataConstructor, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct SemanticBaseline { pub oa: OwnRecord, pub sa: OwnRecord, pub reviewed: Vec, pub orders: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum OrderedCollection { Steps, } /// Order overlaps membership in the live Vec, so it needs a separate review value. /// No observation IDs, history versions, or generic value representation. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct OrderCheckpoint { pub collection: OrderedCollection, pub oa: Vec, pub sa: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Decision { TakeSource, KeepTarget, Candidate, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Resolution { pub facet: Facet, pub decision: Decision, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Review { pub source: Id, pub target: Id, pub candidate: Option, pub resolutions: Vec, } pub fn own(p: &Project, id: Id) -> OwnRecord { if let Some(e) = p.elements.get(&id) { OwnRecord::Element { parent: e.parent, body: e.body.clone(), } } else { OwnRecord::Relation { body: p.relations[&id].body.clone(), } } } fn shared(record: &OwnRecord) -> Result<&SharedBody, String> { match record { OwnRecord::Element { body, .. } => body.shared().ok_or("Not a shared body".into()), _ => Err("Not an element".into()), } } fn shared_mut(record: &mut OwnRecord) -> Result<&mut SharedBody, String> { match record { OwnRecord::Element { body, .. } => body.shared_mut().ok_or("Not a shared body".into()), _ => Err("Not an element".into()), } } fn list(record: &OwnRecord, trigger: bool) -> Result<&Vec, String> { match (record, trigger) { ( OwnRecord::Relation { body: RelationBody::StateTransition { triggers, .. }, }, true, ) => Ok(triggers), (OwnRecord::Element { body, .. }, false) => { body.steps().ok_or("Not an ordered sequence".into()) } _ => Err("Wrong member selector".into()), } } fn list_mut(record: &mut OwnRecord, trigger: bool) -> Result<&mut Vec, String> { match (record, trigger) { ( OwnRecord::Relation { body: RelationBody::StateTransition { triggers, .. }, }, true, ) => Ok(triggers), (OwnRecord::Element { body, .. }, false) => { body.steps_mut().ok_or("Not an ordered sequence".into()) } _ => Err("Wrong member selector".into()), } } /// Copy same-coordinate facet; mapped reference proposals use copy_member below. fn copy(dst: &mut OwnRecord, src: &OwnRecord, facet: &Facet, oa: bool) -> Result<(), String> { match facet { Facet::IntegerMinimum | Facet::IntegerMaximum => { let SharedBody::DataType { definition: DataTypeDefinition::Integer { minimum, maximum }, .. } = shared(src)? else { return Err("Explicit constructor decision required".into()); }; let (minimum, maximum) = (*minimum, *maximum); let SharedBody::DataType { definition: DataTypeDefinition::Integer { minimum: dst_min, maximum: dst_max, }, .. } = shared_mut(dst)? else { return Err("Explicit constructor decision required".into()); }; if *facet == Facet::IntegerMinimum { *dst_min = minimum; } else { *dst_max = maximum; } } Facet::DataUnit => { let SharedBody::DataType { unit, .. } = shared(src)? else { return Err("Wrong facet".into()); }; let unit = unit.clone(); let SharedBody::DataType { unit: dst_unit, .. } = shared_mut(dst)? else { return Err("Wrong facet".into()); }; *dst_unit = unit; } Facet::FieldMinimum => { let SharedBody::DataField { minimum, .. } = shared(src)? else { return Err("Wrong facet".into()); }; let minimum = *minimum; let SharedBody::DataField { minimum: dst_min, .. } = shared_mut(dst)? else { return Err("Wrong facet".into()); }; *dst_min = minimum; } Facet::StateGuard => { let OwnRecord::Relation { body: RelationBody::StateTransition { guard, .. }, } = src else { return Err("Wrong facet".into()); }; let OwnRecord::Relation { body: RelationBody::StateTransition { guard: dst_guard, .. }, } = dst else { return Err("Wrong facet".into()); }; *dst_guard = guard.clone(); } Facet::Trigger(pair) | Facet::Step(pair) => { let id = if oa { pair.oa } else { pair.sa }; copy_member(dst, src, matches!(facet, Facet::Trigger(_)), id, id)?; } Facet::StepsOrder => *list_mut(dst, false)? = list(src, false)?.clone(), Facet::DataConstructor => { let SharedBody::DataType { definition, default, .. } = shared(src)? else { return Err("Wrong constructor scope".into()); }; let (definition, default) = (definition.clone(), default.clone()); let SharedBody::DataType { definition: dst_definition, default: dst_default, .. } = shared_mut(dst)? else { return Err("Wrong constructor scope".into()); }; *dst_definition = definition; *dst_default = default; } } Ok(()) } fn copy_member( dst: &mut OwnRecord, src: &OwnRecord, trigger: bool, src_id: Option, dst_id: Option, ) -> Result<(), String> { if src_id.is_none() && dst_id.is_none() { return Err("Empty member identity".into()); } let present = src_id.is_some_and(|id| list(src, trigger).is_ok_and(|items| items.contains(&id))); let Some(dst_id) = dst_id else { return if present { Err("Missing counterpart mapping".into()) } else { Ok(()) }; }; let items = list_mut(dst, trigger)?; if present && !items.contains(&dst_id) { items.push(dst_id); } if !present { items.retain(|id| *id != dst_id); } Ok(()) } fn equal(a: &OwnRecord, b: &OwnRecord, facet: &Facet, oa: bool) -> Result { let mut projected = a.clone(); copy(&mut projected, b, facet, oa)?; Ok(projected == *a) } pub fn reviewed_changed( base: &SemanticBaseline, now: &OwnRecord, facet: &Facet, oa: bool, ) -> Result, String> { if !base.reviewed.contains(facet) { return Ok(None); } if *facet == Facet::StepsOrder { let checkpoint = base .orders .iter() .find(|o| o.collection == OrderedCollection::Steps) .ok_or("Missing order checkpoint")?; return Ok(Some( list(now, false)? != if oa { &checkpoint.oa } else { &checkpoint.sa }, )); } equal(if oa { &base.oa } else { &base.sa }, now, facet, oa).map(|same| Some(!same)) } /// One validated core candidate, then baseline assignment. No partial commit. /// OA→SA only in this bounded experiment. pub fn apply( p: &Project, base: &SemanticBaseline, review: &Review, ) -> Result<(Project, SemanticBaseline), String> { let source = own(p, review.source); let current = own(p, review.target); let mut target = current.clone(); let mut acknowledged = base.clone(); let mut seen = Vec::new(); if review .resolutions .iter() .any(|r| r.facet == Facet::DataConstructor) && review .resolutions .iter() .any(|r| matches!(r.facet, Facet::IntegerMinimum | Facet::IntegerMaximum)) { return Err("Constructor and descendant facets overlap".into()); } for resolution in &review.resolutions { if seen.contains(&resolution.facet) { return Err("Duplicate facet".into()); } seen.push(resolution.facet.clone()); match resolution.decision { Decision::KeepTarget => {} Decision::TakeSource => match &resolution.facet { Facet::Trigger(pair) | Facet::Step(pair) => copy_member( &mut target, &source, matches!(resolution.facet, Facet::Trigger(_)), pair.oa, pair.sa, )?, Facet::StepsOrder => { return Err("This probe requires an explicit final order".into()); } facet => copy(&mut target, &source, facet, false)?, }, Decision::Candidate => { let candidate = review.candidate.as_ref().ok_or("Missing candidate")?; if resolution.facet == Facet::StepsOrder { let mut expected = list(&target, false)?.clone(); let mut proposed = list(candidate, false)?.clone(); expected.sort(); proposed.sort(); if expected != proposed || proposed.windows(2).any(|p| p[0] == p[1]) { return Err( "Final order must contain every surviving target member exactly once" .into(), ); } } copy(&mut target, candidate, &resolution.facet, false)?; } } if resolution.facet == Facet::DataConstructor { acknowledged.reviewed.retain(|f| { !matches!( f, Facet::IntegerMinimum | Facet::IntegerMaximum | Facet::DataConstructor ) }); } if resolution.facet == Facet::StepsOrder { acknowledged .orders .retain(|o| o.collection != OrderedCollection::Steps); acknowledged.orders.push(OrderCheckpoint { collection: OrderedCollection::Steps, oa: list(&source, false)?.clone(), sa: list(&target, false)?.clone(), }); } else { copy(&mut acknowledged.oa, &source, &resolution.facet, true)?; copy(&mut acknowledged.sa, &target, &resolution.facet, false)?; } if !acknowledged.reviewed.contains(&resolution.facet) { acknowledged.reviewed.push(resolution.facet.clone()); } } // Explicit candidate cannot smuggle changes to any unreviewed facet. if let Some(candidate) = &review.candidate { if *candidate != target { return Err("Candidate changes an unselected facet".into()); } } let command = match target { OwnRecord::Element { parent, body } => { let mut e = p.elements[&review.target].clone(); e.parent = parent; e.body = body; Command::EditElement(review.target, e) } OwnRecord::Relation { body } => { let mut r = p.relations[&review.target].clone(); r.body = body; Command::EditRelation(review.target, r) } }; let prepared = prepare_command(p, &Command::Batch(vec![command])).map_err(|e| e.to_string())?; Ok((prepared.project, acknowledged)) } #[cfg(test)] mod tests { use super::*; pub(crate) fn add( p: &mut Project, kind: Kind, layer: Layer, name: &str, parent: Option, ) -> Id { p.add_element(kind, layer, name, parent).unwrap() } pub(crate) fn set(p: &mut Project, id: Id, body: SharedBody) { *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = body; } pub(crate) fn valid(p: &Project) { let errors: Vec<_> = validation::validate(p) .into_iter() .filter(|i| i.severity == validation::Severity::Error) .collect(); assert!(errors.is_empty(), "{errors:?}"); } fn baseline(p: &Project, oa: Id, sa: Id, reviewed: Vec) -> SemanticBaseline { let orders = if reviewed.contains(&Facet::StepsOrder) { vec![OrderCheckpoint { collection: OrderedCollection::Steps, oa: list(&own(p, oa), false).unwrap().clone(), sa: list(&own(p, sa), false).unwrap().clone(), }] } else { vec![] }; SemanticBaseline { oa: own(p, oa), sa: own(p, sa), reviewed, orders, } } fn resolution(facet: Facet, decision: Decision) -> Resolution { Resolution { facet, decision } } fn roundtrip(p: &Project, base: &SemanticBaseline) { let json = serde_json::to_string(base).unwrap(); assert_eq!( serde_json::from_str::(&json).unwrap(), *base ); assert_eq!(language::parse(&language::format(p)).unwrap(), *p); assert_eq!( serde_json::from_str::(&serde_json::to_string(p).unwrap()).unwrap(), *p ); } fn evidence(name: &str, p: &Project, base: &SemanticBaseline) { let folder = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/r3-probe/evidence"); std::fs::create_dir_all(&folder).unwrap(); std::fs::write( folder.join(format!("{name}-baseline.json")), serde_json::to_string_pretty(base).unwrap(), ) .unwrap(); std::fs::write( folder.join(format!("{name}-candidate.arcadia")), language::format(p), ) .unwrap(); } pub(crate) fn integer(minimum: i64, maximum: i64, unit: &str) -> SharedBody { SharedBody::DataType { definition: DataTypeDefinition::Integer { minimum: Some(minimum), maximum: Some(maximum), }, default: None, unit: unit.into(), } } #[test] fn bounded_data_partial_keep_take_and_constructor_atomicity() { let mut p = Project::empty(); let oa = add(&mut p, Kind::DataType, Layer::Oa, "OA bounded", None); let sa = add(&mut p, Kind::DataType, Layer::Sa, "SA bounded", None); set(&mut p, oa, integer(0, 100, "m")); set(&mut p, sa, integer(0, 100, "ft")); let base = baseline( &p, oa, sa, vec![ Facet::IntegerMinimum, Facet::IntegerMaximum, Facet::DataUnit, ], ); set(&mut p, oa, integer(120, 200, "cm")); let review = Review { source: oa, target: sa, candidate: None, resolutions: vec![ resolution(Facet::IntegerMinimum, Decision::KeepTarget), resolution(Facet::DataUnit, Decision::KeepTarget), ], }; let (kept, mixed) = apply(&p, &base, &review).unwrap(); assert_eq!(kept, p); // OA baseline now min=120,max=100. This is intentional: fields were reviewed independently. assert!(matches!( shared(&mixed.oa).unwrap(), SharedBody::DataType { definition: DataTypeDefinition::Integer { minimum: Some(120), maximum: Some(100) }, .. } )); assert_eq!( reviewed_changed(&mixed, &own(&p, oa), &Facet::IntegerMinimum, true).unwrap(), Some(false) ); assert_eq!( reviewed_changed(&mixed, &own(&p, oa), &Facet::IntegerMaximum, true).unwrap(), Some(true) ); let take = Review { source: oa, target: sa, candidate: None, resolutions: vec![resolution(Facet::IntegerMaximum, Decision::TakeSource)], }; let (p, merged) = apply(&p, &mixed, &take).unwrap(); assert!( matches!(shared(&own(&p, sa)).unwrap(), SharedBody::DataType { definition: DataTypeDefinition::Integer { minimum: Some(0), maximum: Some(200) }, unit, .. } if unit == "ft") ); let (again, same) = apply(&p, &merged, &take).unwrap(); assert_eq!(again, p); assert_eq!(same, merged); roundtrip(&p, &mixed); evidence("mixed-range", &p, &mixed); let mut fields = p; let owner_oa = add(&mut fields, Kind::DataType, Layer::Oa, "OA record", None); let owner_sa = add(&mut fields, Kind::DataType, Layer::Sa, "SA record", None); for id in [owner_oa, owner_sa] { set( &mut fields, id, SharedBody::DataType { definition: DataTypeDefinition::Record { supertype: None }, default: None, unit: String::new(), }, ); } let field_oa = add( &mut fields, Kind::DataField, Layer::Oa, "OA field", Some(owner_oa), ); let field_sa = add( &mut fields, Kind::DataField, Layer::Sa, "SA field", Some(owner_sa), ); for (id, max) in [(field_oa, 5), (field_sa, 9)] { set( &mut fields, id, SharedBody::DataField { type_ref: TypeRef::Named(if id == field_oa { oa } else { sa }), minimum: 0, maximum: Some(max), unit: String::new(), direction: ParameterDirection::Unspecified, storage: FieldStorage::Value, default: None, }, ); } let field_base = baseline(&fields, field_oa, field_sa, vec![Facet::FieldMinimum]); if let Some(SharedBody::DataField { minimum, .. }) = fields.elements.get_mut(&field_oa).unwrap().shared_mut() { *minimum = 2; } let (fields, field_base) = apply( &fields, &field_base, &Review { source: field_oa, target: field_sa, candidate: None, resolutions: vec![resolution(Facet::FieldMinimum, Decision::TakeSource)], }, ) .unwrap(); assert!(matches!( fields.elements[&field_sa].shared(), Some(SharedBody::DataField { minimum: 2, maximum: Some(9), .. }) )); roundtrip(&fields, &field_base); let mut switched = fields.clone(); let constructor_base = baseline(&switched, owner_oa, owner_sa, vec![]); // Remove only the source's child; the target-only field remains real independent work. switched = prepare_command(&switched, &Command::Delete(field_oa)) .unwrap() .project; set(&mut switched, owner_oa, integer(0, 10, "")); valid(&switched); let before = switched.clone(); let before_base = constructor_base.clone(); let result = apply( &switched, &constructor_base, &Review { source: owner_oa, target: owner_sa, candidate: None, resolutions: vec![resolution(Facet::DataConstructor, Decision::TakeSource)], }, ); assert!( result.is_err(), "target Record child blocks primitive constructor switch" ); assert_eq!(switched, before); assert_eq!(constructor_base, before_base); let forbidden = apply( &switched, &constructor_base, &Review { source: owner_oa, target: owner_sa, candidate: None, resolutions: vec![resolution(Facet::IntegerMinimum, Decision::TakeSource)], }, ); assert!(forbidden.unwrap_err().contains("constructor")); println!( "bounded data: mixed historical min/max permitted; unselected drift retained; target max/unit preserved; constructor candidate rejected atomically" ); } pub(crate) fn state_side(p: &mut Project, layer: Layer, prefix: &str) -> (Id, Id, Id) { let owner = add( p, if layer == Layer::Oa { Kind::Entity } else { Kind::System }, layer, &format!("{prefix} owner"), None, ); let machine = add( p, Kind::StateMachine, layer, &format!("{prefix} machine"), Some(owner), ); let region = add( p, Kind::Region, layer, &format!("{prefix} region"), Some(machine), ); let a = add(p, Kind::State, layer, &format!("{prefix} A"), Some(region)); let b = add(p, Kind::State, layer, &format!("{prefix} B"), Some(region)); let old = add(p, Kind::Event, layer, &format!("{prefix} old event"), None); let new = add(p, Kind::Event, layer, &format!("{prefix} new event"), None); let relation = p.relate( RelationKind::StateTransition, &format!("{prefix} transition"), a, b, ); if let RelationBody::StateTransition { triggers, guard, .. } = &mut p.relations.get_mut(&relation).unwrap().body { *triggers = vec![old]; *guard = "ready".into(); } (relation, old, new) } #[test] fn state_guard_and_trigger_delta_retain_deleted_historical_identity() { let mut p = Project::empty(); let (oa, old_oa, new_oa) = state_side(&mut p, Layer::Oa, "OA"); let (sa, old_sa, new_sa) = state_side(&mut p, Layer::Sa, "SA"); let extra = add(&mut p, Kind::Event, Layer::Sa, "Target only", None); let old = MemberPair { oa: Some(old_oa), sa: Some(old_sa), }; let new = MemberPair { oa: Some(new_oa), sa: Some(new_sa), }; let base = baseline( &p, oa, sa, vec![Facet::StateGuard, Facet::Trigger(old), Facet::Trigger(new)], ); p = prepare_command(&p, &Command::Delete(old_oa)) .unwrap() .project; if let RelationBody::StateTransition { triggers, guard, .. } = &mut p.relations.get_mut(&oa).unwrap().body { *triggers = vec![new_oa]; *guard = "armed".into(); } if let RelationBody::StateTransition { triggers, guard, .. } = &mut p.relations.get_mut(&sa).unwrap().body { triggers.push(extra); *guard = "local inhibit clear".into(); } valid(&p); assert!(!p.elements.contains_key(&old_oa)); assert!(list(&base.oa, true).unwrap().contains(&old_oa)); // Current source uses live @alias semantics. A historical body needs a // distinct source context, not transplantation into a live declaration. let mut historical_as_live = p.clone(); if let OwnRecord::Relation { body } = &base.oa { historical_as_live.relations.get_mut(&oa).unwrap().body = body.clone(); } let rejected = language::parse(&language::format(&historical_as_live)); assert!( rejected.is_err(), "deleted historical UUID is not a live alias" ); let review = Review { source: oa, target: sa, candidate: None, resolutions: vec![ resolution(Facet::StateGuard, Decision::KeepTarget), resolution(Facet::Trigger(old), Decision::TakeSource), resolution(Facet::Trigger(new), Decision::TakeSource), ], }; let (p, acknowledged) = apply(&p, &base, &review).unwrap(); assert_eq!(list(&own(&p, sa), true).unwrap(), &vec![extra, new_sa]); assert!(p.elements.contains_key(&old_sa)); assert!( matches!(&p.relations[&sa].body, RelationBody::StateTransition { guard, .. } if guard == "local inhibit clear") ); roundtrip(&p, &base); roundtrip(&p, &acknowledged); evidence("deleted-trigger", &p, &base); let (again, same) = apply(&p, &acknowledged, &review).unwrap(); assert_eq!(again, p); assert_eq!(same, acknowledged); println!( "state: old source Event deleted; historical member pair still removes its target link, preserves target object and extra trigger, keeps guard" ); } pub(crate) fn sequence(p: &mut Project, layer: Layer, prefix: &str) -> (Id, Id, Id) { let sequence = add( p, if layer == Layer::Oa { Kind::Process } else { Kind::FunctionalChain }, layer, prefix, None, ); let a = add( p, Kind::Occurrence, layer, &format!("{prefix} A"), Some(sequence), ); let b = add( p, Kind::Occurrence, layer, &format!("{prefix} B"), Some(sequence), ); for id in [a, b] { set( p, id, SharedBody::Occurrence { body: OccurrenceBody::Control { kind: ControlKind::Choice, }, precondition: String::new(), postcondition: String::new(), }, ); } *p.elements.get_mut(&sequence).unwrap().steps_mut().unwrap() = vec![a, b]; (sequence, a, b) } #[test] fn ordered_steps_preserve_target_only_members_and_reject_hidden_edits() { let mut p = Project::empty(); let (oa, oa_a, oa_b) = sequence(&mut p, Layer::Oa, "OA flow"); let (sa, sa_a, sa_b) = sequence(&mut p, Layer::Sa, "SA flow"); let base = baseline(&p, oa, sa, vec![Facet::StepsOrder]); // Counterexample: a bare carrier loses pending placement review after // independently acknowledging append membership. The order checkpoint // keeps the exact older order and therefore retains the drift. let oa_c = Id::new_v4(); let sa_c = Id::new_v4(); let mut source_with_append = own(&p, oa); list_mut(&mut source_with_append, false).unwrap().push(oa_c); let mut membership_only = base.clone(); copy( &mut membership_only.oa, &source_with_append, &Facet::Step(MemberPair { oa: Some(oa_c), sa: Some(sa_c), }), true, ) .unwrap(); assert_eq!( membership_only.oa, source_with_append, "bare carrier would falsely report no order drift" ); assert_eq!( reviewed_changed( &membership_only, &source_with_append, &Facet::StepsOrder, true ) .unwrap(), Some(true) ); let extra = add( &mut p, Kind::Occurrence, Layer::Sa, "Target only step", Some(sa), ); set( &mut p, extra, SharedBody::Occurrence { body: OccurrenceBody::Control { kind: ControlKind::Merge, }, precondition: String::new(), postcondition: String::new(), }, ); *p.elements.get_mut(&sa).unwrap().steps_mut().unwrap() = vec![sa_a, extra, sa_b]; *p.elements.get_mut(&oa).unwrap().steps_mut().unwrap() = vec![oa_b, oa_a]; valid(&p); let take = Review { source: oa, target: sa, candidate: None, resolutions: vec![resolution(Facet::StepsOrder, Decision::TakeSource)], }; assert!( apply(&p, &base, &take) .unwrap_err() .contains("explicit final order") ); let mut candidate = own(&p, sa); *list_mut(&mut candidate, false).unwrap() = vec![sa_b, sa_a]; let mut review = Review { candidate: Some(candidate), resolutions: vec![resolution(Facet::StepsOrder, Decision::Candidate)], ..take }; assert!( apply(&p, &base, &review) .unwrap_err() .contains("every surviving") ); *list_mut(review.candidate.as_mut().unwrap(), false).unwrap() = vec![sa_b, extra, sa_a]; let target_only = p.elements[&extra].clone(); let (p, acknowledged) = apply(&p, &base, &review).unwrap(); assert_eq!(list(&own(&p, sa), false).unwrap(), &vec![sa_b, extra, sa_a]); assert_eq!(p.elements[&extra], target_only); roundtrip(&p, &acknowledged); evidence("target-only-step", &p, &acknowledged); let mut duplicate = review.clone(); duplicate.resolutions.push(duplicate.resolutions[0].clone()); assert!( apply(&p, &acknowledged, &duplicate) .unwrap_err() .contains("Duplicate") ); let mut hidden = review; if let Some(OwnRecord::Element { parent, .. }) = &mut hidden.candidate { *parent = Some(extra); } assert!( apply(&p, &acknowledged, &hidden) .unwrap_err() .contains("unselected") ); println!( "steps: ambiguous source reorder demands explicit candidate; missing target-only member and unselected parent edits reject" ); } }