Checkpoint source 7 workflows and in-progress diagram authoring redesign
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.
This commit is contained in:
Codex
2026-09-10 10:06:55 -07:00
parent 4b6b5fc197
commit e2ecfea69b
162 changed files with 17595 additions and 926 deletions
@@ -104,6 +104,17 @@ pub(super) fn path(project: &Project, facet: &FacetSelector, layer: Layer) -> Op
Family::Source | Family::Target if !project.relations.contains_key(&id) => return None,
_ => {}
}
if project
.relations
.get(&id)
.is_some_and(|r| r.kind() == RelationKind::ContextualLink)
{
return match family(facet)? {
Family::Source => Some("/body/ContextualLink/source/occurrence".into()),
Family::Target => Some("/body/ContextualLink/target/occurrence".into()),
_ => None,
};
}
scalar::binding(project, id, &slot)
}
pub(super) fn label(facet: &FacetSelector) -> &'static str {
@@ -562,9 +562,14 @@ fn absent_attachment_payloads_migrate_v1_and_v2_and_source5_upgrade_requires_exa
] {
if value["version"] == 1 {
value.as_object_mut().unwrap().remove("attachments");
value.as_object_mut().unwrap().remove("operation_omission");
} else {
for row in value["rows"].as_array_mut().unwrap() {
row["draft"].as_object_mut().unwrap().remove("attachments");
row["draft"]
.as_object_mut()
.unwrap()
.remove("operation_omission");
}
}
let restored = State::from_recovery(&f.p, value.clone()).unwrap();
+74 -4
View File
@@ -668,12 +668,82 @@ impl State {
.cloned();
let direction = self.direction;
let base = &self.base;
let (source_layer, target_layer) = if direction == TransitionDirection::OaToSa {
("Operational", "System")
} else {
("System", "Operational")
};
let mut header = column![
text(format!("Review semantics · {} included rows", self.included_addresses().len())).size(22),
row![pick_list(available, self.add_trace.clone(), move |choice| message(Event::ChooseTrace(choice))).placeholder("Choose another trace to add or reopen").width(Fill).text_size(12).padding(7), button(text("Open row").size(12)).padding(7).style(crate::style::nav).on_press(message(Event::OpenTrace)), button(text(if direction == TransitionDirection::OaToSa { "OA → SA" } else { "SA → OA" }).size(12)).padding(7).style(crate::style::nav).on_press(message(Event::Direction(if direction == TransitionDirection::OaToSa { TransitionDirection::SaToOa } else { TransitionDirection::OaToSa })))].spacing(7),
row![pick_list(choices, selected, move |choice| message(Event::Focus(RowAddress::from_trace(&base.traces[&choice.id], direction)))).placeholder("Choose a trace / review history").width(Fill).text_size(12).padding(7), checkbox(self.active_address.is_some_and(|address| self.included.contains(&address))).label("Include this row").on_toggle_maybe(self.active_address.map(|address| move |included| message(Event::Include(address, included))))].spacing(10),
text("Switching rows retains every draft. Only included rows contribute to the one combined preview.").size(10).color(crate::style::MUTED),
text("Review counterpart changes").size(22),
text("Compare existing linked OA and SA objects, then choose which values to copy. This is for updating counterparts after editing; creating a counterpart is a separate action.").size(12),
row![
text(format!("Copy selected changes: {source_layer} → {target_layer}")).size(13),
button(text("Reverse direction").size(12)).padding(7).style(crate::style::nav).on_press(message(Event::Direction(if direction == TransitionDirection::OaToSa { TransitionDirection::SaToOa } else { TransitionDirection::OaToSa }))),
].spacing(10),
].spacing(6);
if available.len() > 1 || self.active_address.is_none() {
header = header.push(
row![
pick_list(available, self.add_trace.clone(), move |choice| message(
Event::ChooseTrace(choice)
))
.placeholder("Choose a linked object to review…")
.width(Fill)
.text_size(12)
.padding(7),
button(text("Review object").size(12))
.padding(7)
.style(crate::style::nav)
.on_press(message(Event::OpenTrace)),
]
.spacing(7),
);
}
let active: iced::Element<'a, Message> = if choices.len() > 1 {
pick_list(choices, selected, move |choice| {
message(Event::Focus(RowAddress::from_trace(
&base.traces[&choice.id],
direction,
)))
})
.placeholder("Choose an object already in this review")
.width(Fill)
.text_size(12)
.padding(7)
.into()
} else if let Some(address) = self.active_address {
text(format!(
"{} → {}",
super::view::choice(base, address.source).label,
super::view::choice(base, address.target).label
))
.size(13)
.width(Fill)
.into()
} else {
text("Select a linked object above to compare its values.")
.size(13)
.width(Fill)
.into()
};
header = header.push(
row![
active,
checkbox(
self.active_address
.is_some_and(|address| self.included.contains(&address))
)
.label("Include this object")
.on_toggle_maybe(
self.active_address
.map(|address| move |included| message(Event::Include(address, included)))
),
]
.spacing(10),
);
if self.included_addresses().len() > 1 {
header = header.push(text(format!("{} objects included. Apply saves their selected decisions together; switching objects keeps your drafts.", self.included_addresses().len())).size(11).color(crate::style::MUTED));
}
if let Some(notice) = &self.notice {
header = header.push(text(notice).size(11));
}
@@ -6,6 +6,20 @@ use super::*;
mod diagnostic_tests;
type Slot = (Id, FacetSelector);
fn context_leaf_path(project: &Project, slot: &Slot) -> Option<&'static str> {
if project.relations.get(&slot.0)?.kind() != RelationKind::ContextualLink {
return None;
}
match slot.1 {
FacetSelector::Relation(RelationFacet::Source) => {
Some("/body/ContextualLink/source/occurrence")
}
FacetSelector::Relation(RelationFacet::Target) => {
Some("/body/ContextualLink/target/occurrence")
}
_ => None,
}
}
struct Intent {
value: Value,
row: RowAddress,
@@ -47,6 +61,13 @@ fn canonical(facet: &FacetSelector, layer: Layer) -> Option<FacetSelector> {
})
}
fn observe(project: &Project, slot: &Slot) -> Result<Value, String> {
if let Some(path) = context_leaf_path(project, slot) {
let value = record_value(project, slot.0)?
.pointer(path)
.cloned()
.ok_or("Qualified endpoint leaf is absent.")?;
return Ok(serde_json::json!({"Value":value}));
}
if let Some((_, facet)) = artifacts::parts(&slot.1) {
return Ok(artifacts::observation(
project.artifact_links.get(&slot.0),
@@ -297,10 +318,9 @@ fn write_slot(project: &mut Project, slot: &Slot, observation: &Value) -> Result
serde_json::to_value(order).expect("Order");
} else {
let path = scalar::binding(project, slot.0, &slot.1)
.or_else(|| context_leaf_path(project, slot).map(str::to_owned))
.ok_or("A selected property was retired by another row's constructor proposal.")?;
*value
.pointer_mut(&path)
.ok_or("Selected property is no longer available.")? = supplied;
crate::forms::set_shape_value(&mut value, &path, supplied)?;
}
install(project, slot.0, value)
}
@@ -325,6 +345,22 @@ pub(super) fn request(state: &State) -> Result<SemanticTransitionRequest, String
})
.collect();
let mut request = SemanticTransitionRequest::new(transition);
let mut omissions = BTreeMap::new();
for address in &addresses {
if let Some(raw) = state
.row(*address)
.and_then(|row| row.operation_omission.as_ref())
&& omissions
.insert(address.source, raw.clone())
.is_some_and(|prior| prior != *raw)
{
return Err("Rows reviewing the same operation disagree on the explicit omission rationale. Retain one consistent decision before Apply.".into());
}
}
request.transition.operation_omissions = omissions
.into_iter()
.map(|(source, rationale)| OperationOmission { source, rationale })
.collect();
request.references = references.clone();
request.accepted_impacts = state.accepted_impacts.clone();
let mut slots = BTreeMap::new();
@@ -435,6 +471,32 @@ pub(super) fn request(state: &State) -> Result<SemanticTransitionRequest, String
observe(&proposed, &slot)?
};
agree(state, &mut slots, slot.clone(), observed, address, facet)?;
if matches!(
facet,
FacetSelector::Relation(
RelationFacet::ContextSource | RelationFacet::ContextTarget
)
) {
let leaf = (
address.target,
FacetSelector::Relation(
if matches!(facet, FacetSelector::Relation(RelationFacet::ContextSource)) {
RelationFacet::Source
} else {
RelationFacet::Target
},
),
);
let observed = observe(
if *decision == SemanticDecision::KeepTarget {
base
} else {
&proposed
},
&leaf,
)?;
agree(state, &mut slots, leaf, observed, address, facet)?;
}
let first_equal = row.can_acknowledge_constructor(base, facet)
&& (*decision != SemanticDecision::Candidate
|| observe(base, &slot)? == observe(&proposed, &slot)?);
@@ -463,6 +463,7 @@ fn occurrence_and_event_candidate_variants_cover_closed_families_without_touchin
lifeline: None,
},
OccurrenceBody::Exchange {
operation: None,
exchange: None,
sender: None,
receiver: None,
@@ -519,6 +520,7 @@ fn occurrence_and_event_candidate_variants_cover_closed_families_without_touchin
body: OccurrenceBody::Action { .. },
..
} => occurrence(OccurrenceBody::Exchange {
operation: None,
exchange: None,
sender: None,
receiver: None,
@@ -725,6 +727,7 @@ fn port_group_companions_typed_owner_context_and_direction_restrictions_are_expl
#[test]
fn outer_and_nested_constructor_candidates_require_explicit_resolution_and_retain_both_drafts() {
let original = occurrence(OccurrenceBody::Exchange {
operation: None,
exchange: None,
sender: None,
receiver: None,
@@ -1319,6 +1322,7 @@ fn closed_data_and_event_take_composition_matches_core_across_every_kind_pair_an
fn exchange_constructor_takes_qualified_annotation_addresses_without_remapping_expression_text() {
for reverse in [false, true] {
let message = occurrence(OccurrenceBody::Exchange {
operation: None,
exchange: None,
sender: None,
receiver: None,
@@ -137,6 +137,7 @@ fn fixture(binding: bool) -> Fixture {
});
locals.push(leaf);
OccurrenceBody::Exchange {
operation: None,
exchange: Some(exchange),
sender: None,
receiver: None,
@@ -14,6 +14,7 @@ fn fixture(oa_kind: MessageKind, sa_kind: MessageKind) -> (Project, Id, Id, Id)
.unwrap();
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
operation: None,
exchange: None,
sender: None,
receiver: None,
@@ -358,6 +359,7 @@ fn message_group_reply_and_reference_take_require_explicit_call_mapping_both_dir
.unwrap();
*p.elements.get_mut(&call).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
operation: None,
exchange: None,
sender: None,
receiver: None,
@@ -35,6 +35,8 @@ pub(super) struct Saved {
attachments: attachments::Saved,
#[serde(default)]
artifacts: artifacts::Saved,
#[serde(default)]
operation_omission: Option<String>,
reference_oa: Option<Id>,
reference_sa: Option<Id>,
reference_search: String,
@@ -68,6 +70,8 @@ struct RowPayload {
attachments: attachments::Saved,
#[serde(default)]
artifacts: artifacts::Saved,
#[serde(default)]
operation_omission: Option<String>,
reference_oa: Option<Id>,
reference_sa: Option<Id>,
reference_search: String,
@@ -120,6 +124,7 @@ impl RowPayload {
orders: saved.orders,
attachments: saved.attachments,
artifacts: saved.artifacts,
operation_omission: saved.operation_omission,
reference_oa: saved.reference_oa,
reference_sa: saved.reference_sa,
reference_search: saved.reference_search,
@@ -160,6 +165,7 @@ impl RowPayload {
orders: self.orders,
attachments: self.attachments,
artifacts: self.artifacts,
operation_omission: self.operation_omission,
reference_oa: self.reference_oa,
reference_sa: self.reference_sa,
reference_search: self.reference_search,
@@ -234,6 +240,7 @@ impl ReviewRow {
.collect(),
attachments: self.attachments.recovery_snapshot(),
artifacts: self.artifacts.recovery_snapshot(),
operation_omission: self.operation_omission.clone(),
reference_oa: self.reference_oa.as_ref().map(|c| c.id),
orders: self
.order_drafts
@@ -378,6 +385,7 @@ impl ReviewRow {
return Err("Recovered artifact drafts need an exact owner trace.".into());
}
state.reference_sa = saved.reference_sa.map(|id| view::choice(project, id));
state.operation_omission = saved.operation_omission;
state.reference_search = saved.reference_search;
state.accepted_impacts = saved.accepted_impacts;
// Invalid numbers and incomplete mappings are recoverable authored input.
@@ -414,7 +422,7 @@ fn batch_limits(value: &Value) -> Result<(), String> {
}
impl State {
/// A source-5 upgrade may reconstruct the same accepted records with a new
/// A source-5/6 upgrade may reconstruct the same accepted records with a new
/// schema fingerprint. The caller supplies the independently verified legacy
/// base; every saved row is still decoded and replanned through strict restore.
pub(crate) fn from_recovery_after_source5_upgrade(
@@ -423,13 +431,13 @@ impl State {
value: Value,
) -> Result<Self, String> {
batch_limits(&value)?;
crate::recovery::validate_pre7_intent(&value)?;
let value = match value.get("version").and_then(Value::as_u64) {
Some(1) => {
let mut saved: Saved = serde_json::from_value(value).map_err(|e| e.to_string())?;
if saved.base_identity != project.id || saved.base_fingerprint != legacy_base {
return Err(
"Semantic source-5 recovery does not match the verified legacy base."
.into(),
"Semantic legacy recovery does not match the verified legacy base.".into(),
);
}
saved.base_fingerprint = project_fingerprint(project);
@@ -441,8 +449,7 @@ impl State {
serde_json::from_value(value).map_err(|e| e.to_string())?;
if saved.base_identity != project.id || saved.base_fingerprint != legacy_base {
return Err(
"Semantic source-5 recovery does not match the verified legacy base."
.into(),
"Semantic legacy recovery does not match the verified legacy base.".into(),
);
}
saved.base_fingerprint = project_fingerprint(project);
+18 -4
View File
@@ -11,6 +11,7 @@ pub struct ReviewRow {
pub selected: Option<FacetSelector>,
pub decisions: BTreeMap<FacetSelector, SemanticDecision>,
pub references: BTreeMap<Id, Id>,
pub operation_omission: Option<String>,
pub resets: BTreeSet<FacetSelector>,
pub staged: BTreeSet<FacetSelector>,
pub presence: BTreeMap<FacetSelector, bool>,
@@ -73,6 +74,7 @@ impl ReviewRow {
selected: None,
decisions: BTreeMap::new(),
references: BTreeMap::new(),
operation_omission: None,
resets: BTreeSet::new(),
staged: BTreeSet::new(),
presence: BTreeMap::new(),
@@ -133,6 +135,7 @@ impl ReviewRow {
self.selected = None;
self.accepted_impacts.clear();
self.references.clear();
self.operation_omission = None;
self.draft = self
.target(project)
.and_then(|id| Draft::from_project(project, id));
@@ -163,6 +166,15 @@ impl ReviewRow {
pub fn update(&mut self, project: &Project, event: RowEvent) {
let mut replan = true;
let result = match event {
RowEvent::OperationOmission(enabled) => {
self.operation_omission =
enabled.then(|| self.operation_omission.clone().unwrap_or_default());
Ok(())
}
RowEvent::OperationRationale(raw) => {
self.operation_omission = Some(raw);
Ok(())
}
#[cfg(test)]
RowEvent::Trace(choice) => {
if !project
@@ -734,9 +746,7 @@ impl ReviewRow {
let fact = capture_facet(project, trace.oa, trace.sa, facet)?;
let value = scalar::source_value(&fact, self.direction.source_layer(), &map)?;
let mut supplied = original.clone();
*supplied
.pointer_mut(&roots[0])
.ok_or("Source property is unavailable.")? = value;
crate::forms::set_shape_value(&mut supplied, &roots[0], value)?;
supplied
}
};
@@ -744,11 +754,15 @@ impl ReviewRow {
let value = supplied
.pointer(&path)
.cloned()
.or_else(|| {
(matches!(facet, FacetSelector::Occurrence(OccurrenceFacet::Operation)))
.then_some(Value::Null)
})
.ok_or("Candidate property is no longer available.")?;
if scalar::grouped(facet) && *decision == SemanticDecision::Candidate {
group_candidates.push((path.clone(), value.clone()));
}
*final_value.pointer_mut(&path).ok_or("Final target property is unavailable; resolve the overlapping constructor decision.")? = value;
crate::forms::set_shape_value(&mut final_value, &path, value)?;
}
}
if !form_context {
@@ -16,7 +16,7 @@ fn enum_tag(value: &Value) -> Option<&str> {
}
impl Own {
fn get(project: &Project, id: Id) -> Option<Self> {
let (whole, element) = if let Some(e) = project.elements.get(&id) {
let (mut whole, element) = if let Some(e) = project.elements.get(&id) {
(serde_json::to_value(e).ok()?, true)
} else {
(
@@ -24,6 +24,35 @@ impl Own {
false,
)
};
if project.elements.get(&id).is_some_and(|e| {
e.layer() == Layer::Sa
&& matches!(
e.shared(),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
message_kind: MessageKind::Call | MessageKind::Asynchronous,
..
},
..
})
)
}) && validation::ancestor_of_kind(project, id, Kind::Scenario)
.and_then(|id| project.elements.get(&id))
.is_some_and(|e| {
matches!(
e.shared(),
Some(SharedBody::Scenario {
viewpoint: ScenarioViewpoint::BoundaryInterface,
..
})
)
})
&& let Some(fields) = whole
.pointer_mut("/body/Sa/Shared/Occurrence/body/Exchange")
.and_then(Value::as_object_mut)
{
fields.entry("operation").or_insert(Value::Null);
}
let mut prefix = vec!["body".to_string()];
let mut value = &whole["body"];
if element {
@@ -114,6 +143,9 @@ fn path(own: &Own, facet: &FacetSelector) -> Option<Vec<String>> {
F::Occurrence(OccurrenceFacet::ReplyTo) => {
("Occurrence", &["body", "Exchange", "reply_to"])
}
F::Occurrence(OccurrenceFacet::Operation) => {
("Occurrence", &["body", "Exchange", "operation"])
}
F::Occurrence(OccurrenceFacet::Action) => ("Occurrence", &["body", "Action", "action"]),
F::Occurrence(OccurrenceFacet::ActionLifeline) => {
("Occurrence", &["body", "Action", "lifeline"])
@@ -142,8 +174,12 @@ fn path(own: &Own, facet: &FacetSelector) -> Option<Vec<String>> {
F::State(StateFacet::Exit) => ("State", &["exit"]),
F::State(StateFacet::DoActivity) => ("State", &["do_activity"]),
F::Constraint(ConstraintFacet::Expression) => ("Constraint", &["expression"]),
F::Relation(RelationFacet::Source) => ("*", &["source"]),
F::Relation(RelationFacet::Target) => ("*", &["target"]),
F::Relation(RelationFacet::Source) if own.kind != "ContextualLink" => ("*", &["source"]),
F::Relation(RelationFacet::Target) if own.kind != "ContextualLink" => ("*", &["target"]),
F::Relation(RelationFacet::ContextOwner) => ("ContextualLink", &["owner"]),
F::Relation(RelationFacet::ContextSource) => ("ContextualLink", &["source"]),
F::Relation(RelationFacet::ContextTarget) => ("ContextualLink", &["target"]),
F::Relation(RelationFacet::ContextPurpose) => ("ContextualLink", &["purpose"]),
F::Relation(RelationFacet::AllocationOrder) => ("InterfaceItemAllocation", &["order"]),
F::Relation(RelationFacet::Transmission) => ("InterfaceItemAllocation", &["transmission"]),
F::Relation(RelationFacet::Acquisition) => ("InterfaceItemAllocation", &["acquisition"]),
@@ -0,0 +1,350 @@
//! Source7 desktop composition falsifiers, using the public planner as final oracle.
use super::*;
fn operation_fixture() -> Project {
persistence::decode(include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
)))
.unwrap()
}
fn operation_id(p: &Project, n: u32) -> Id {
let alias = format!("operation_probe_{n}");
p.elements
.values()
.find(|e| e.alias == alias)
.map(|e| e.id)
.or_else(|| {
p.relations
.values()
.find(|r| r.alias == alias)
.map(|r| r.id)
})
.unwrap()
}
fn send(state: &mut State, p: &Project, event: RowEvent) {
state.update(p, state.address_event(event));
}
fn occurrence(p: &mut Project, layer: Layer, owner: Id, name: &str, body: OccurrenceBody) -> Id {
let mut element = p
.make_element(Kind::Occurrence, layer, name, Some(owner))
.unwrap();
*element.shared_mut().unwrap() = SharedBody::Occurrence {
precondition: String::new(),
postcondition: String::new(),
body,
};
let id = element.id;
*p = execute_batch(p, &[Command::CreateElement(element)]).unwrap();
id
}
#[test]
fn contextual_incident_leaf_keep_allows_path_change_but_conflicts_with_changed_leaf_both_directions()
{
let mut p = Project::empty();
let mut layer_ids = Vec::new();
for layer in [Layer::Oa, Layer::Sa] {
let kind = if layer == Layer::Oa {
Kind::Process
} else {
Kind::FunctionalChain
};
let owner = p.add_element(kind, layer, "Caller", None).unwrap();
let definition = p.add_element(kind, layer, "Reusable checks", None).unwrap();
let a = occurrence(
&mut p,
layer,
definition,
"First check",
OccurrenceBody::Control {
kind: ControlKind::Choice,
},
);
let b = occurrence(
&mut p,
layer,
definition,
"Second check",
OccurrenceBody::Control {
kind: ControlKind::Choice,
},
);
let r1 = occurrence(
&mut p,
layer,
owner,
"First use",
OccurrenceBody::SequenceReference {
sequence: Some(definition),
},
);
let r2 = occurrence(
&mut p,
layer,
owner,
"Second use",
OccurrenceBody::SequenceReference {
sequence: Some(definition),
},
);
let id = Id::new_v4();
let relation = Relation {
id,
alias: p.fresh_alias("caller_constraint", "relation", id),
name: "Caller constraint".into(),
description: String::new(),
body: RelationBody::ContextualLink {
owner,
source: OccurrenceEndpoint {
references: vec![r1],
occurrence: a,
},
target: OccurrenceEndpoint {
references: vec![r2],
occurrence: b,
},
purpose: ContextualLinkPurpose::Precedence {
guard: String::new(),
},
},
};
p = execute_batch(&p, &[Command::CreateRelation(relation)]).unwrap();
layer_ids.push([owner, definition, a, b, r1, r2, id]);
}
let attached_trace = tests::add_trace(
&mut p,
layer_ids[0][2],
layer_ids[1][2],
"Incident leaf review",
);
let direct_trace = tests::add_trace(
&mut p,
layer_ids[0][6],
layer_ids[1][6],
"Qualified path review",
);
let incident = FacetSelector::Incident {
keys: KeyPair {
oa: Some(layer_ids[0][6]),
sa: Some(layer_ids[1][6]),
},
end: IncidentEnd::Source,
};
let direct = FacetSelector::Relation(RelationFacet::ContextSource);
for reverse in [false, true] {
let target = usize::from(!reverse);
let mut state = State::new(&p, attached_trace).unwrap();
if reverse {
state.update(&p, Event::Direction(TransitionDirection::SaToOa));
}
send(&mut state, &p, RowEvent::EditPair(incident.clone()));
send(
&mut state,
&p,
RowEvent::Attachment(attachments::Event::Stage),
);
send(
&mut state,
&p,
RowEvent::Decide(incident.clone(), Some(SemanticDecision::KeepTarget)),
);
state.update(&p, Event::ChooseTrace(view::choice(&p, direct_trace)));
state.update(&p, Event::OpenTrace);
for (&oa, &sa) in layer_ids[0].iter().zip(&layer_ids[1]).take(7) {
state.references.insert(oa, sa);
}
state.refresh(&p);
send(
&mut state,
&p,
RowEvent::Decide(direct.clone(), Some(SemanticDecision::Candidate)),
);
send(&mut state, &p, RowEvent::Select(direct.clone()));
let endpoint = OccurrenceEndpoint {
references: vec![layer_ids[target][5]],
occurrence: layer_ids[target][2],
};
send(
&mut state,
&p,
RowEvent::Shape(
"/body/ContextualLink/source".into(),
serde_json::to_value(&endpoint).unwrap(),
),
);
assert!(state.can_apply(), "{:?}", state.error);
let expected = state.proposed.as_ref().unwrap().clone();
assert!(
matches!(&expected.relations[&layer_ids[target][6]].body, RelationBody::ContextualLink { source, .. } if *source == endpoint)
);
assert_eq!(
execute_batch(&p, &[state.command(&p).unwrap()]).unwrap(),
expected
);
let saved = state.recovery_snapshot().unwrap();
let mut restored = State::from_recovery(&p, saved.clone()).unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), saved);
assert!(restored.can_apply());
let conflicting = OccurrenceEndpoint {
references: vec![layer_ids[target][4]],
occurrence: layer_ids[target][3],
};
send(
&mut restored,
&p,
RowEvent::Shape(
"/body/ContextualLink/source".into(),
serde_json::to_value(conflicting).unwrap(),
),
);
assert!(!restored.can_apply());
assert!(
restored
.error
.as_deref()
.unwrap_or_default()
.contains("Independent row proposals disagree"),
"{:?}",
restored.error
);
assert!(restored.command(&p).is_err());
}
}
#[test]
fn semantic_operation_virtual_none_candidate_uses_exact_allocation_and_preserves_accepted_bytes() {
let mut p = operation_fixture();
let call = p
.elements
.values()
.find(|e| e.alias == "operation_probe_43")
.unwrap()
.id;
let allocation = p
.relations
.values()
.find(|r| r.alias == "operation_probe_30")
.unwrap()
.id;
let owner = p
.add_element(Kind::Scenario, Layer::Oa, "Operational conversation", None)
.unwrap();
let oa = occurrence(
&mut p,
Layer::Oa,
owner,
"Operational request",
OccurrenceBody::Exchange {
exchange: None,
sender: None,
receiver: None,
message_kind: MessageKind::Call,
reply_to: None,
operation: None,
values: vec![],
},
);
let trace = tests::add_trace(&mut p, oa, call, "Service rationale");
let before = serde_json::to_value(&p).unwrap();
let facet = FacetSelector::Occurrence(OccurrenceFacet::Operation);
let path = scalar::binding(&p, call, &facet).unwrap();
assert!(
serde_json::to_value(&p.elements[&call])
.unwrap()
.pointer(&path)
.is_none()
);
let mut state = State::new(&p, trace).unwrap();
send(
&mut state,
&p,
RowEvent::Decide(facet.clone(), Some(SemanticDecision::Candidate)),
);
send(&mut state, &p, RowEvent::Select(facet));
send(
&mut state,
&p,
RowEvent::Shape(path, serde_json::json!({"allocation":allocation})),
);
assert!(state.can_apply(), "{:?}", state.error);
let actual = state.proposed.as_ref().unwrap();
assert!(
matches!(oa_core::operations::resolve(actual, call).unwrap(), oa_core::operations::OperationResolution::Selected(value) if value.allocation == allocation)
);
assert_eq!(serde_json::to_value(&p).unwrap(), before);
let saved = state.recovery_snapshot().unwrap();
let restored = State::from_recovery(&p, saved.clone()).unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), saved);
assert_eq!(restored.proposed, state.proposed);
}
#[test]
fn reverse_operation_omission_gui_requires_raw_rationale_and_matches_one_core_batch_after_recovery()
{
use crate::workflow::{Event as W, Workflow};
let p = operation_fixture();
let call = operation_id(&p, 43);
let mut message = p.elements[&call].clone();
let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { operation, .. },
..
}) = message.shared_mut()
else {
panic!()
};
*operation = Some(OperationUse {
allocation: Some(operation_id(&p, 30)),
});
let p = execute_batch(&p, &[Command::EditElement(call, message)]).unwrap();
let mut workflow = Workflow::transition(&p, operation_id(&p, 40)).unwrap();
assert!(workflow.update(&p, W::Apply).is_err());
workflow
.update(&p, W::OperationOmission(call, true))
.unwrap();
workflow
.update(&p, W::OperationRationale(call, " \n ".into()))
.unwrap();
let saved = workflow.recovery_snapshot().unwrap();
let mut workflow = Workflow::from_recovery(&p, saved.clone()).unwrap();
assert_eq!(workflow.recovery_snapshot().unwrap(), saved);
assert!(workflow.update(&p, W::Apply).is_err());
for (n, kind) in [(10, Kind::Actor), (11, Kind::Entity)] {
workflow
.update(&p, W::MappingKind(operation_id(&p, n), kind))
.unwrap();
}
let rationale = " The operational conversation abstracts the technical service signature. ";
workflow
.update(&p, W::OperationRationale(call, rationale.into()))
.unwrap();
let Workflow::Transition(draft) = &workflow else {
panic!()
};
assert_eq!(draft.request.operation_omissions[0].rationale, rationale);
assert!(
draft.plan.conflicts.is_empty(),
"{:?}",
draft.plan.conflicts
);
let expected = execute_batch(&p, &draft.plan.commands).unwrap();
let target = draft
.plan
.changes
.iter()
.find(|c| c.source == call)
.unwrap()
.target;
let actual = workflow.update(&p, W::Apply).unwrap().unwrap();
assert_eq!(
actual, expected,
"Behavior root adds no implicit architectural placements"
);
assert!(matches!(
oa_core::operations::resolve(&actual, target).unwrap(),
oa_core::operations::OperationResolution::Ordinary
));
assert_eq!(actual.elements[&call], p.elements[&call]);
}
@@ -919,6 +919,7 @@ fn inherited_annotation_impact_requires_exact_acceptance_and_edits_clear_approva
.unwrap();
*p.elements.get_mut(&message).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
operation: None,
exchange: Some(exchange),
sender: None,
receiver: None,
+52 -26
View File
@@ -278,32 +278,22 @@ impl ReviewRow {
let mut content = column![
header,
row![
action("Properties", RowEvent::Tab(Tab::Facts)),
action("Members / rows", RowEvent::Tab(Tab::Members)),
action("Attachments", RowEvent::Tab(Tab::Attachments)),
action("Artifact links", RowEvent::Tab(Tab::Artifacts)),
action(
format!(
"History ({}) · {} resets",
trace
.and_then(|t| t.baseline.as_ref())
.and_then(|b| b.semantic.as_ref())
.map_or(0, |b| b.facets.len()),
self.resets.len()
),
RowEvent::Tab(Tab::History)
),
action(
format!("Reference pairs ({})", self.references.len()),
RowEvent::Tab(Tab::References)
),
action(
format!(
"Additional changes ({})",
self.plan.as_ref().map_or(0, |p| p.impacts.len())
),
RowEvent::Tab(Tab::Impacts)
),
text("Review area").size(12),
pick_list(
vec![
Tab::Facts,
Tab::Members,
Tab::Attachments,
Tab::Artifacts,
Tab::History,
Tab::References,
Tab::Impacts,
],
Some(self.tab),
|tab| event(RowEvent::Tab(tab)),
)
.text_size(12)
.padding(7),
Space::new().width(Fill),
action("Refresh preview", RowEvent::Preview)
]
@@ -311,6 +301,42 @@ impl ReviewRow {
.wrap(),
]
.spacing(8);
let impacts = self.plan.as_ref().map_or(0, |plan| plan.impacts.len());
if impacts > 0 && self.tab != Tab::Impacts {
content = content.push(action(
format!("View {impacts} additional changes"),
RowEvent::Tab(Tab::Impacts),
));
}
if self.direction == TransitionDirection::SaToOa
&& trace.is_some_and(|trace| {
matches!(
project.elements.get(&trace.sa).and_then(|e| e.shared()),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
operation: Some(_),
message_kind: MessageKind::Call | MessageKind::Asynchronous,
..
},
..
})
)
})
{
content = content.push(
checkbox(self.operation_omission.is_some())
.label("Explicitly omit the technical operation in the OA counterpart")
.on_toggle(|enabled| event(RowEvent::OperationOmission(enabled))),
);
if let Some(raw) = &self.operation_omission {
content = content.push(
text_input("Required engineering rationale", raw)
.on_input(|raw| event(RowEvent::OperationRationale(raw)))
.padding(8)
.size(12),
);
}
}
let body = match self.tab {
Tab::Facts => self.facts_view(project),
Tab::References => self.references_view(project),