From d5650309a93f3d590a41f8a8f0322c6500568f69 Mon Sep 17 00:00:00 2001 From: Codex-9e8r6t Date: Fri, 11 Sep 2026 09:11:50 -0700 Subject: [PATCH] Preserve exact GUI5657 contextual prerelease input bytes --- .gitattributes | 13 + crates/oa-desktop/src/behavior/creation.rs | 1504 +- .../src/behavior/scenario/workspace.rs | 1620 +- .../src/behavior/topology/contextual.rs | 1574 +- crates/oa-desktop/src/guide.rs | 2736 +- .../src/semantic_review/artifacts_view.rs | 294 +- .../src/semantic_review/attachments_view.rs | 158 +- .../oa-desktop/src/semantic_review/keyed.rs | 700 +- .../src/semantic_review/keyed_rows.rs | 690 +- .../src/semantic_review/keyed_tests.rs | 1584 +- .../oa-desktop/src/semantic_review/ordered.rs | 508 +- docs/openapi.json | 25674 ++++++++-------- 12 files changed, 18534 insertions(+), 18521 deletions(-) diff --git a/.gitattributes b/.gitattributes index f419945e..972ca763 100644 --- a/.gitattributes +++ b/.gitattributes @@ -65,3 +65,16 @@ harness/experiments/contextual-source-machine-v2-20260911/**/*.arcadia whitespac harness/experiments/contextual-source-reopen-20260911/launch-method.ps1 whitespace=-blank-at-eof,cr-at-eol # Raw compact.exe output includes CR-CR-LF and incidental trailing spaces. harness/experiments/wsl-capacity-20260911/*.log -whitespace + +# Preserve exact frozen GUI5657 contextual milestone input bytes. +crates/oa-desktop/src/behavior/creation.rs -text +crates/oa-desktop/src/behavior/scenario/workspace.rs -text +crates/oa-desktop/src/behavior/topology/contextual.rs -text +crates/oa-desktop/src/guide.rs -text +crates/oa-desktop/src/semantic_review/artifacts_view.rs -text +crates/oa-desktop/src/semantic_review/attachments_view.rs -text +crates/oa-desktop/src/semantic_review/keyed_rows.rs -text +crates/oa-desktop/src/semantic_review/keyed_tests.rs -text +crates/oa-desktop/src/semantic_review/keyed.rs -text +crates/oa-desktop/src/semantic_review/ordered.rs -text +docs/openapi.json -text diff --git a/crates/oa-desktop/src/behavior/creation.rs b/crates/oa-desktop/src/behavior/creation.rs index d30281a4..a845dabb 100644 --- a/crates/oa-desktop/src/behavior/creation.rs +++ b/crates/oa-desktop/src/behavior/creation.rs @@ -1,695 +1,695 @@ -//! Local behavior creation. A draft is not a model object: only its validated -//! batch may cross Document::execute, and acknowledgement follows success. -use iced::{ - Element as UiElement, Fill, - widget::{button, checkbox, column, container, row, scrollable, text, text_input}, -}; -use oa_core::*; -use serde::{Deserialize, Serialize}; - -const PAGE_SIZE: usize = 6; -pub const NAME_INPUT: &str = "behavior-creation-name"; - -// A focused text input consumes Escape. Preserve the application's modal -// priority by emitting its ordinary Escape message from this narrow bridge. -pub fn subscription(active: bool) -> iced::Subscription<()> { - if active { - iced::event::listen_with(captured_escape) - } else { - iced::Subscription::none() - } -} - -fn captured_escape( - event: iced::Event, - status: iced::event::Status, - _: iced::window::Id, -) -> Option<()> { - (status == iced::event::Status::Captured - && matches!( - event, - iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { - key: iced::keyboard::Key::Named(iced::keyboard::key::Named::Escape), - .. - }) - )) - .then_some(()) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum Family { - Sequence, - Scenario, - Machine, -} -impl Family { - fn kind(self, layer: Layer) -> Kind { - match (self, layer) { - (Self::Sequence, Layer::Oa) => Kind::Process, - (Self::Sequence, Layer::Sa) => Kind::FunctionalChain, - (Self::Scenario, _) => Kind::Scenario, - (Self::Machine, _) => Kind::StateMachine, - } - } - fn title(self, layer: Layer) -> &'static str { - match (self, layer) { - (Self::Sequence, Layer::Oa) => "Operational process", - (Self::Sequence, Layer::Sa) => "Functional chain", - (Self::Scenario, _) => "Scenario", - (Self::Machine, _) => "State machine", - } - } -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Picker { - Owner, - Context, -} -impl Picker { - fn index(self) -> usize { - match self { - Self::Owner => 0, - Self::Context => 1, - } - } -} -#[derive(Debug, Clone)] -pub enum Event { - Source(super::source_draft::Event), - Open(Layer), - Choose(Family), - Name(String), - Owner(Id), - Viewpoint(ScenarioViewpoint), - Context(Option), - ContextOptions(bool), - FirstView(bool), - Search(Picker, String), - Page(Picker, usize), - Cancel, - Submit, -} -#[derive(Clone, Default, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -struct Browser { - query: String, - page: usize, -} -#[derive(Clone, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -struct Draft { - #[serde(default)] - source: super::source_draft::State, - #[serde(default = "Id::new_v4")] - invocation: Id, - - version: u32, - document: Id, - layer: Layer, - family: Option, - name: String, - owner: Option, - viewpoint: Option, - context: Option, - #[serde(default)] - context_open: bool, - first_view: bool, - element: Id, - diagram: Id, - placement: Id, - browsers: [Browser; 2], -} -#[derive(Default)] -pub struct State { - draft: Option, -} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Created { - pub behavior: Id, - pub layer: Layer, - pub diagram: Option, -} - -impl State { - pub fn source_active(&self) -> bool { - self.draft.as_ref().is_some_and(|d| d.source.engaged()) - } - - pub fn source_open(&self) -> bool { - self.draft.as_ref().is_some_and(|d| d.source.is_open()) - } - pub fn source_back(&mut self) -> bool { - self.draft.as_mut().is_some_and(|d| d.source.back()) - } - - pub fn has_pending(&self) -> bool { - self.draft.is_some() - } - pub fn pending_layer(&self) -> Option { - self.draft.as_ref().map(|d| d.layer) - } - /// Capture this before successful acknowledgement; never select these IDs - /// on mere planning or a rejected source/model transaction. - pub fn created(&self) -> Option { - let d = self.draft.as_ref()?; - d.family.map(|family| Created { - behavior: d.element, - layer: d.layer, - diagram: (family == Family::Machine && d.first_view).then_some(d.diagram), - }) - } - /// Match before execution, then apply this target only after success. - /// Identity alone is insufficient: changed bodies or trailing operations - /// must not inherit the prepared creation's selection/opening contract. - pub fn created_by(&self, p: &Project, command: &Command) -> Option { - let target = self.created()?; - (self.prepare_pending(p).ok().as_ref() == Some(command)).then_some(target) - } - pub fn recovery_snapshot(&self) -> Result, String> { - self.draft - .as_ref() - .map(serde_json::to_value) - .transpose() - .map_err(|e| e.to_string()) - } - pub fn from_recovery(value: serde_json::Value) -> Result { - let draft: Draft = serde_json::from_value(value).map_err(|e| e.to_string())?; - if draft.version != 1 { - return Err("Unsupported behavior creation draft version.".into()); - } - // Raw values and stale references remain editable; planning validates - // against the current document rather than silently fixing the draft. - Ok(Self { draft: Some(draft) }) - } - pub fn acknowledge_commit(&mut self) { - self.draft = None; - } - pub fn prepare_pending(&self, p: &Project) -> Result { - self.draft - .as_ref() - .ok_or("Choose New to start a behavior.")? - .command(p) - } - pub fn update(&mut self, p: &Project, event: Event) -> Result, String> { - if let Event::Source(event) = event { - if self - .draft - .as_mut() - .ok_or("Start the consumer form first.")? - .source - .dismiss(&event)? - { - return Ok(None); - } - let d = self - .draft - .as_ref() - .ok_or("Start the consumer form first.")?; - if d.family != Some(Family::Machine) { - return Err("This reference does not support staged source creation.".into()); - } - if d.document != p.id { - return Err("This behavior draft belongs to another document.".into()); - } - let mut next = d.clone(); - if let Some(id) = next.source.update( - p, - super::source_draft::Scope::Machine { layer: d.layer }, - d.invocation, - event, - )? { - next.owner = Some(id); - let preview = next - .source - .preview(p, next.owner)? - .ok_or("New owner preview is missing.")?; - if !eligible(&preview, next.layer, Picker::Owner, id) { - return Err("This participant cannot own the machine.".into()); - } - } - self.draft = Some(next); - return Ok(None); - } - if self.source_open() && !matches!(&event, Event::Cancel) { - return Err("Use the new source or go Back before editing the parent form.".into()); - } - - match event { - Event::Cancel => { - self.draft = None; - return Ok(None); - } - Event::Open(layer) => { - if self.has_pending() { - return Err("Create or cancel the pending behavior first.".into()); - } - self.draft = Some(Draft { - source: Default::default(), - invocation: Id::new_v4(), - version: 1, - document: p.id, - layer, - family: None, - name: String::new(), - owner: None, - viewpoint: None, - context: None, - context_open: false, - first_view: false, - element: Id::new_v4(), - diagram: Id::new_v4(), - placement: Id::new_v4(), - browsers: Default::default(), - }); - return Ok(None); - } - Event::Submit => return self.prepare_pending(p).map(Some), - _ => {} - } - let d = self - .draft - .as_mut() - .ok_or("Choose New to start a behavior.")?; - if d.document != p.id { - return Err("This draft belongs to another document. Cancel it to start here.".into()); - } - match event { - Event::Choose(family) => { - if d.family.is_some_and(|old| old != family) { - return Err( - "Cancel this draft before choosing a different behavior family.".into(), - ); - } - d.family = Some(family); - } - Event::Name(name) => d.name = name, - Event::Owner(id) if d.family == Some(Family::Machine) => { - if !eligible(p, d.layer, Picker::Owner, id) { - return Err("Choose an existing participant in this layer.".into()); - } - d.owner = Some(id); - } - Event::Viewpoint(viewpoint) if d.family == Some(Family::Scenario) => { - if !viewpoints(d.layer).contains(&viewpoint) { - return Err("Choose a scenario viewpoint from this layer.".into()); - } - d.viewpoint = Some(viewpoint); - } - Event::Context(id) if d.family == Some(Family::Scenario) => { - if id.is_some_and(|id| !eligible(p, d.layer, Picker::Context, id)) { - return Err("Choose an existing same-layer scenario context.".into()); - } - d.context = id; - } - Event::ContextOptions(open) if d.family == Some(Family::Scenario) => { - d.context_open = open; - } - Event::FirstView(value) if d.family == Some(Family::Machine) => d.first_view = value, - Event::Search(picker, query) => d.browsers[picker.index()] = Browser { query, page: 0 }, - Event::Page(picker, page) => d.browsers[picker.index()].page = page, - Event::Cancel | Event::Open(_) | Event::Submit => unreachable!(), - _ => return Err("That field does not apply to this behavior family.".into()), - } - Ok(None) - } -} - -fn viewpoints(layer: Layer) -> [ScenarioViewpoint; 2] { - match layer { - Layer::Oa => [ - ScenarioViewpoint::OperationalActivity, - ScenarioViewpoint::ParticipantInteraction, - ], - Layer::Sa => [ - ScenarioViewpoint::Functional, - ScenarioViewpoint::BoundaryInterface, - ], - } -} -fn viewpoint_title(viewpoint: ScenarioViewpoint) -> &'static str { - match viewpoint { - ScenarioViewpoint::OperationalActivity => "Activities · operational exchanges", - ScenarioViewpoint::ParticipantInteraction => "Participants · interactions", - ScenarioViewpoint::Functional => "Functions · functional exchanges", - ScenarioViewpoint::BoundaryInterface => "System boundary · interface exchanges", - ScenarioViewpoint::MixedLegacy => "Legacy mixed viewpoint", - } -} -fn eligible(p: &Project, layer: Layer, picker: Picker, id: Id) -> bool { - p.elements.get(&id).is_some_and(|e| { - e.layer() == layer - && match picker { - Picker::Owner => validation::parent_permits(e.kind(), Kind::StateMachine), - Picker::Context => { - e.kind().is_sequence() - || e.kind().is_work() - || e.kind().is_performer() - || matches!( - e.kind(), - Kind::Capability - | Kind::SystemCapability - | Kind::OperationalMission - | Kind::Mission - ) - } - } - }) -} -fn choices<'a>(p: &'a Project, layer: Layer, picker: Picker, query: &str) -> Vec<&'a Element> { - let query = query.trim().to_lowercase(); - let mut matches: Vec<_> = p - .elements - .values() - .filter(|e| eligible(p, layer, picker, e.id)) - .filter(|e| query.is_empty() || reference_label(p, e.id).to_lowercase().contains(&query)) - .collect(); - matches.sort_by(|a, b| { - a.name - .to_lowercase() - .cmp(&b.name.to_lowercase()) - .then(a.id.cmp(&b.id)) - }); - matches -} - -fn reference_context(p: &Project, e: &Element) -> String { - let mut names = Vec::new(); - let mut parent = e.parent; - let mut seen = std::collections::BTreeSet::new(); - while let Some(id) = parent { - if !seen.insert(id) { - break; - } - let Some(ancestor) = p.elements.get(&id) else { - break; - }; - names.push(ancestor.name.as_str()); - parent = ancestor.parent; - } - names.reverse(); - let location = if names.is_empty() { - format!("{} root", e.layer()) - } else { - format!("{} / {}", e.layer(), names.join(" / ")) - }; - format!("{} · {} · {location}", e.kind(), e.alias) -} -pub(super) fn reference_label(p: &Project, id: Id) -> String { - p.elements - .get(&id) - .map(|e| format!("{} · {}", e.name, reference_context(p, e))) - .unwrap_or_else(|| "Missing element — choose a replacement".into()) -} -impl Draft { - fn command(&self, p: &Project) -> Result { - self.source.check_scope( - super::source_draft::Scope::Machine { layer: self.layer }, - self.owner, - )?; - let preview = self.source.preview(p, self.owner)?; - let command = self.consumer_command(preview.as_ref().unwrap_or(p))?; - self.source.combine(p, self.owner, command) - } - fn consumer_command(&self, p: &Project) -> Result { - if self.document != p.id { - return Err("This draft belongs to another document.".into()); - } - let family = self - .family - .ok_or("Choose the behavior you want to create.")?; - if self.name.trim().is_empty() { - return Err("Enter the behavior's name before creating it.".into()); - } - if [self.element, self.diagram, self.placement] - .into_iter() - .any(|id| p.contains(id)) - || self.element == self.diagram - || self.element == self.placement - || self.diagram == self.placement - { - return Err( - "A reserved creation identity is already in use. Cancel and start a fresh draft." - .into(), - ); - } - let kind = family.kind(self.layer); - let parent = if family == Family::Machine { - Some( - self.owner - .filter(|id| eligible(p, self.layer, Picker::Owner, *id)) - .ok_or("Choose the participant that owns this state machine.")?, - ) - } else { - if self.owner.is_some() { - return Err("Processes and scenarios belong at the layer root; context is a separate reference.".into()); - } - None - }; - if family != Family::Machine && self.first_view { - return Err("A state view can only accompany a state machine.".into()); - } - let mut body = ElementBody::new(kind, self.layer) - .ok_or("This behavior is unavailable in this layer.")?; - if family == Family::Scenario { - let viewpoint = self - .viewpoint - .filter(|v| viewpoints(self.layer).contains(v)) - .ok_or("Choose the scenario's viewpoint explicitly.")?; - if self - .context - .is_some_and(|id| !eligible(p, self.layer, Picker::Context, id)) - { - return Err( - "The selected scenario context is missing or no longer compatible.".into(), - ); - } - body = ElementBody::shared_in( - self.layer, - SharedBody::Scenario { - viewpoint, - context: self.context, - precondition: String::new(), - postcondition: String::new(), - steps: Vec::new(), - }, - ); - } else if self.viewpoint.is_some() || self.context.is_some() { - return Err( - "Scenario viewpoint and context cannot be applied to another behavior family." - .into(), - ); - } - let element = Element { - id: self.element, - alias: p.fresh_alias(&self.name, "behavior", self.element), - name: self.name.clone(), - description: String::new(), - parent, - body, - }; - let mut commands = vec![Command::CreateElement(element)]; - if self.first_view { - let working = execute_batch(p, &commands).map_err(|e| e.to_string())?; - // Reuse the state editor's geometry contract. A new machine has one - // root placement; reserve both presentation IDs across retries. - let plan = super::state_edit::create_view(&working, self.element) - .map_err(|e| e.to_string())?; - let Command::Batch(mut view_commands) = plan.command else { - return Err("Unexpected state-view plan.".into()); - }; - if view_commands.len() != 2 { - return Err("Unexpected additional operation in the first state-view plan.".into()); - } - let Some(Command::Layout(layout)) = view_commands.pop() else { - return Err("State-view plan must finish with its layout operation.".into()); - }; - let Some(Command::CreateDiagram(mut diagram)) = view_commands.pop() else { - return Err("State-view plan is missing its diagram.".into()); - }; - if diagram.id != layout || diagram.nodes.len() != 1 { - return Err("A first state view must contain only its new machine.".into()); - } - let mut node = diagram - .nodes - .into_values() - .next() - .ok_or("State-view root is missing.")?; - if node.element != self.element { - return Err("State-view root differs from the new machine.".into()); - } - diagram.id = self.diagram; - diagram.alias = working.fresh_alias(&diagram.name, "state_view", self.diagram); - node.id = self.placement; - node.alias = working.fresh_alias( - &format!("{} placement", self.name), - "placement", - self.placement, - ); - diagram.nodes = [(node.id, node)].into_iter().collect(); - commands.push(Command::CreateDiagram(diagram)); - commands.push(Command::Layout(self.diagram)); - } - let command = Command::Batch(commands); - execute_batch(p, std::slice::from_ref(&command)).map_err(|e| e.to_string())?; - Ok(command) - } -} - -impl State { - /// Parent maps these local events into the existing guarded Behavior route. - /// The card's fields scroll; Create/Cancel stay outside that scroll region. - pub fn view<'a>(&'a self, p: &'a Project) -> Option> { - self.view_with(p) - } - fn view_with<'a, R: iced::advanced::text::Renderer + 'a>( - &'a self, - p: &'a Project, - ) -> Option> { - let d = self.draft.as_ref()?; - if let Some(view) = d.source.view(p) { - return Some( - column![ - text(format!( - "State machine name: {}", - crate::views::short(&d.name, 60) - )) - .size(12), - view.map(Event::Source) - ] - .spacing(6) - .height(Fill) - .into(), - ); - } - let mut fields = column![].spacing(10); - let title = if let Some(family) = d.family { - fields = fields.push( - text_input("Exact behavior name", &d.name) - .id(NAME_INPUT) - .on_input(Event::Name) - .on_submit(Event::Submit) - .padding(8), - ); - match family { - Family::Sequence => { - fields = fields.push( - text(format!( - "Owned by the {} layer root. Add work and exchange contributions after creation.", - d.layer - )) - .size(12), - ); - } - Family::Scenario => { - fields = fields.push( - text(format!( - "Owned by the {} layer root. Choose what the lifelines represent:", - d.layer - )) - .size(12), - ); - for viewpoint in viewpoints(d.layer) { - fields = fields.push( - button(text(viewpoint_title(viewpoint)).size(13)) - .style(if d.viewpoint == Some(viewpoint) { - button::primary - } else { - button::secondary - }) - .on_press(Event::Viewpoint(viewpoint)) - .padding(7), - ); - } - let context_label = if d.context_open { - "Hide optional context".into() - } else if let Some(id) = d.context { - format!("Context: {} · Change…", reference_label(p, id)) - } else { - "Add optional context…".into() - }; - fields = fields.push( - button(text(context_label).size(12)) - .on_press(Event::ContextOptions(!d.context_open)) - .padding(6), - ); - if d.context_open { - fields = fields.push(picker_view(p, d, Picker::Context)); - } - } - Family::Machine => { - if let Some(label) = d.source.label(d.owner) { - fields = fields.push(text(label).size(13)).push(text("Create and open commits the new owner and machine together, including the first view only when selected.").size(12)); - } - fields = fields.push(picker_view(p, d, Picker::Owner)); - fields = fields.push( - button("Create new owner…") - .on_press(Event::Source(super::source_draft::Event::Open( - d.invocation, - ))) - .padding(7), - ); - fields = fields.push( - checkbox(d.first_view) - .label("Create the first state view") - .on_toggle(Event::FirstView), - ); - fields = fields.push(text("The machine starts without regions or states. Add those explicitly in its workspace.").size(12)); - } - } - format!("New {}", family.title(d.layer)) - } else { - for family in [Family::Sequence, Family::Scenario, Family::Machine] { - let purpose = match family { - Family::Sequence => "A path through work and exchanges", - Family::Scenario => "Ordered interactions from an explicit viewpoint", - Family::Machine => "States and modes of an explicitly chosen participant", - }; - fields = fields.push( - button( - column![text(family.title(d.layer)).size(15), text(purpose).size(12)] - .spacing(3), - ) - .on_press(Event::Choose(family)) - .padding(10) - .width(Fill), - ); - } - format!("New behavior · {}", d.layer) - }; - let mut actions = - row![button("Cancel · Esc").on_press(Event::Cancel).padding(8)].spacing(8); - if d.family.is_some() { - actions = actions.push(button("Create and open").on_press(Event::Submit).padding(8)); - } - Some( - container( - column![ - text(title).size(20), - scrollable(fields) - .id("behavior-creation-fields") - .height(Fill), - container(actions).id("behavior-creation-actions") - ] - .spacing(12), - ) - .padding(18) - .max_width(680) - .height(Fill) - .style(crate::style::card) - .into(), - ) - } -} -fn picker_view<'a, R: iced::advanced::text::Renderer + 'a>( - p: &'a Project, - d: &'a Draft, - picker: Picker, -) -> UiElement<'a, Event, iced::Theme, R> { - let browser = &d.browsers[picker.index()]; - let options = choices(p, d.layer, picker, &browser.query); - let page = browser - .page - .min(options.len().saturating_sub(1) / PAGE_SIZE); +//! Local behavior creation. A draft is not a model object: only its validated +//! batch may cross Document::execute, and acknowledgement follows success. +use iced::{ + Element as UiElement, Fill, + widget::{button, checkbox, column, container, row, scrollable, text, text_input}, +}; +use oa_core::*; +use serde::{Deserialize, Serialize}; + +const PAGE_SIZE: usize = 6; +pub const NAME_INPUT: &str = "behavior-creation-name"; + +// A focused text input consumes Escape. Preserve the application's modal +// priority by emitting its ordinary Escape message from this narrow bridge. +pub fn subscription(active: bool) -> iced::Subscription<()> { + if active { + iced::event::listen_with(captured_escape) + } else { + iced::Subscription::none() + } +} + +fn captured_escape( + event: iced::Event, + status: iced::event::Status, + _: iced::window::Id, +) -> Option<()> { + (status == iced::event::Status::Captured + && matches!( + event, + iced::Event::Keyboard(iced::keyboard::Event::KeyPressed { + key: iced::keyboard::Key::Named(iced::keyboard::key::Named::Escape), + .. + }) + )) + .then_some(()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Family { + Sequence, + Scenario, + Machine, +} +impl Family { + fn kind(self, layer: Layer) -> Kind { + match (self, layer) { + (Self::Sequence, Layer::Oa) => Kind::Process, + (Self::Sequence, Layer::Sa) => Kind::FunctionalChain, + (Self::Scenario, _) => Kind::Scenario, + (Self::Machine, _) => Kind::StateMachine, + } + } + fn title(self, layer: Layer) -> &'static str { + match (self, layer) { + (Self::Sequence, Layer::Oa) => "Operational process", + (Self::Sequence, Layer::Sa) => "Functional chain", + (Self::Scenario, _) => "Scenario", + (Self::Machine, _) => "State machine", + } + } +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Picker { + Owner, + Context, +} +impl Picker { + fn index(self) -> usize { + match self { + Self::Owner => 0, + Self::Context => 1, + } + } +} +#[derive(Debug, Clone)] +pub enum Event { + Source(super::source_draft::Event), + Open(Layer), + Choose(Family), + Name(String), + Owner(Id), + Viewpoint(ScenarioViewpoint), + Context(Option), + ContextOptions(bool), + FirstView(bool), + Search(Picker, String), + Page(Picker, usize), + Cancel, + Submit, +} +#[derive(Clone, Default, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct Browser { + query: String, + page: usize, +} +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct Draft { + #[serde(default)] + source: super::source_draft::State, + #[serde(default = "Id::new_v4")] + invocation: Id, + + version: u32, + document: Id, + layer: Layer, + family: Option, + name: String, + owner: Option, + viewpoint: Option, + context: Option, + #[serde(default)] + context_open: bool, + first_view: bool, + element: Id, + diagram: Id, + placement: Id, + browsers: [Browser; 2], +} +#[derive(Default)] +pub struct State { + draft: Option, +} +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Created { + pub behavior: Id, + pub layer: Layer, + pub diagram: Option, +} + +impl State { + pub fn source_active(&self) -> bool { + self.draft.as_ref().is_some_and(|d| d.source.engaged()) + } + + pub fn source_open(&self) -> bool { + self.draft.as_ref().is_some_and(|d| d.source.is_open()) + } + pub fn source_back(&mut self) -> bool { + self.draft.as_mut().is_some_and(|d| d.source.back()) + } + + pub fn has_pending(&self) -> bool { + self.draft.is_some() + } + pub fn pending_layer(&self) -> Option { + self.draft.as_ref().map(|d| d.layer) + } + /// Capture this before successful acknowledgement; never select these IDs + /// on mere planning or a rejected source/model transaction. + pub fn created(&self) -> Option { + let d = self.draft.as_ref()?; + d.family.map(|family| Created { + behavior: d.element, + layer: d.layer, + diagram: (family == Family::Machine && d.first_view).then_some(d.diagram), + }) + } + /// Match before execution, then apply this target only after success. + /// Identity alone is insufficient: changed bodies or trailing operations + /// must not inherit the prepared creation's selection/opening contract. + pub fn created_by(&self, p: &Project, command: &Command) -> Option { + let target = self.created()?; + (self.prepare_pending(p).ok().as_ref() == Some(command)).then_some(target) + } + pub fn recovery_snapshot(&self) -> Result, String> { + self.draft + .as_ref() + .map(serde_json::to_value) + .transpose() + .map_err(|e| e.to_string()) + } + pub fn from_recovery(value: serde_json::Value) -> Result { + let draft: Draft = serde_json::from_value(value).map_err(|e| e.to_string())?; + if draft.version != 1 { + return Err("Unsupported behavior creation draft version.".into()); + } + // Raw values and stale references remain editable; planning validates + // against the current document rather than silently fixing the draft. + Ok(Self { draft: Some(draft) }) + } + pub fn acknowledge_commit(&mut self) { + self.draft = None; + } + pub fn prepare_pending(&self, p: &Project) -> Result { + self.draft + .as_ref() + .ok_or("Choose New to start a behavior.")? + .command(p) + } + pub fn update(&mut self, p: &Project, event: Event) -> Result, String> { + if let Event::Source(event) = event { + if self + .draft + .as_mut() + .ok_or("Start the consumer form first.")? + .source + .dismiss(&event)? + { + return Ok(None); + } + let d = self + .draft + .as_ref() + .ok_or("Start the consumer form first.")?; + if d.family != Some(Family::Machine) { + return Err("This reference does not support staged source creation.".into()); + } + if d.document != p.id { + return Err("This behavior draft belongs to another document.".into()); + } + let mut next = d.clone(); + if let Some(id) = next.source.update( + p, + super::source_draft::Scope::Machine { layer: d.layer }, + d.invocation, + event, + )? { + next.owner = Some(id); + let preview = next + .source + .preview(p, next.owner)? + .ok_or("New owner preview is missing.")?; + if !eligible(&preview, next.layer, Picker::Owner, id) { + return Err("This participant cannot own the machine.".into()); + } + } + self.draft = Some(next); + return Ok(None); + } + if self.source_open() && !matches!(&event, Event::Cancel) { + return Err("Use the new source or go Back before editing the parent form.".into()); + } + + match event { + Event::Cancel => { + self.draft = None; + return Ok(None); + } + Event::Open(layer) => { + if self.has_pending() { + return Err("Create or cancel the pending behavior first.".into()); + } + self.draft = Some(Draft { + source: Default::default(), + invocation: Id::new_v4(), + version: 1, + document: p.id, + layer, + family: None, + name: String::new(), + owner: None, + viewpoint: None, + context: None, + context_open: false, + first_view: false, + element: Id::new_v4(), + diagram: Id::new_v4(), + placement: Id::new_v4(), + browsers: Default::default(), + }); + return Ok(None); + } + Event::Submit => return self.prepare_pending(p).map(Some), + _ => {} + } + let d = self + .draft + .as_mut() + .ok_or("Choose New to start a behavior.")?; + if d.document != p.id { + return Err("This draft belongs to another document. Cancel it to start here.".into()); + } + match event { + Event::Choose(family) => { + if d.family.is_some_and(|old| old != family) { + return Err( + "Cancel this draft before choosing a different behavior family.".into(), + ); + } + d.family = Some(family); + } + Event::Name(name) => d.name = name, + Event::Owner(id) if d.family == Some(Family::Machine) => { + if !eligible(p, d.layer, Picker::Owner, id) { + return Err("Choose an existing participant in this layer.".into()); + } + d.owner = Some(id); + } + Event::Viewpoint(viewpoint) if d.family == Some(Family::Scenario) => { + if !viewpoints(d.layer).contains(&viewpoint) { + return Err("Choose a scenario viewpoint from this layer.".into()); + } + d.viewpoint = Some(viewpoint); + } + Event::Context(id) if d.family == Some(Family::Scenario) => { + if id.is_some_and(|id| !eligible(p, d.layer, Picker::Context, id)) { + return Err("Choose an existing same-layer scenario context.".into()); + } + d.context = id; + } + Event::ContextOptions(open) if d.family == Some(Family::Scenario) => { + d.context_open = open; + } + Event::FirstView(value) if d.family == Some(Family::Machine) => d.first_view = value, + Event::Search(picker, query) => d.browsers[picker.index()] = Browser { query, page: 0 }, + Event::Page(picker, page) => d.browsers[picker.index()].page = page, + Event::Cancel | Event::Open(_) | Event::Submit => unreachable!(), + _ => return Err("That field does not apply to this behavior family.".into()), + } + Ok(None) + } +} + +fn viewpoints(layer: Layer) -> [ScenarioViewpoint; 2] { + match layer { + Layer::Oa => [ + ScenarioViewpoint::OperationalActivity, + ScenarioViewpoint::ParticipantInteraction, + ], + Layer::Sa => [ + ScenarioViewpoint::Functional, + ScenarioViewpoint::BoundaryInterface, + ], + } +} +fn viewpoint_title(viewpoint: ScenarioViewpoint) -> &'static str { + match viewpoint { + ScenarioViewpoint::OperationalActivity => "Activities · operational exchanges", + ScenarioViewpoint::ParticipantInteraction => "Participants · interactions", + ScenarioViewpoint::Functional => "Functions · functional exchanges", + ScenarioViewpoint::BoundaryInterface => "System boundary · interface exchanges", + ScenarioViewpoint::MixedLegacy => "Legacy mixed viewpoint", + } +} +fn eligible(p: &Project, layer: Layer, picker: Picker, id: Id) -> bool { + p.elements.get(&id).is_some_and(|e| { + e.layer() == layer + && match picker { + Picker::Owner => validation::parent_permits(e.kind(), Kind::StateMachine), + Picker::Context => { + e.kind().is_sequence() + || e.kind().is_work() + || e.kind().is_performer() + || matches!( + e.kind(), + Kind::Capability + | Kind::SystemCapability + | Kind::OperationalMission + | Kind::Mission + ) + } + } + }) +} +fn choices<'a>(p: &'a Project, layer: Layer, picker: Picker, query: &str) -> Vec<&'a Element> { + let query = query.trim().to_lowercase(); + let mut matches: Vec<_> = p + .elements + .values() + .filter(|e| eligible(p, layer, picker, e.id)) + .filter(|e| query.is_empty() || reference_label(p, e.id).to_lowercase().contains(&query)) + .collect(); + matches.sort_by(|a, b| { + a.name + .to_lowercase() + .cmp(&b.name.to_lowercase()) + .then(a.id.cmp(&b.id)) + }); + matches +} + +fn reference_context(p: &Project, e: &Element) -> String { + let mut names = Vec::new(); + let mut parent = e.parent; + let mut seen = std::collections::BTreeSet::new(); + while let Some(id) = parent { + if !seen.insert(id) { + break; + } + let Some(ancestor) = p.elements.get(&id) else { + break; + }; + names.push(ancestor.name.as_str()); + parent = ancestor.parent; + } + names.reverse(); + let location = if names.is_empty() { + format!("{} root", e.layer()) + } else { + format!("{} / {}", e.layer(), names.join(" / ")) + }; + format!("{} · {} · {location}", e.kind(), e.alias) +} +pub(super) fn reference_label(p: &Project, id: Id) -> String { + p.elements + .get(&id) + .map(|e| format!("{} · {}", e.name, reference_context(p, e))) + .unwrap_or_else(|| "Missing element — choose a replacement".into()) +} +impl Draft { + fn command(&self, p: &Project) -> Result { + self.source.check_scope( + super::source_draft::Scope::Machine { layer: self.layer }, + self.owner, + )?; + let preview = self.source.preview(p, self.owner)?; + let command = self.consumer_command(preview.as_ref().unwrap_or(p))?; + self.source.combine(p, self.owner, command) + } + fn consumer_command(&self, p: &Project) -> Result { + if self.document != p.id { + return Err("This draft belongs to another document.".into()); + } + let family = self + .family + .ok_or("Choose the behavior you want to create.")?; + if self.name.trim().is_empty() { + return Err("Enter the behavior's name before creating it.".into()); + } + if [self.element, self.diagram, self.placement] + .into_iter() + .any(|id| p.contains(id)) + || self.element == self.diagram + || self.element == self.placement + || self.diagram == self.placement + { + return Err( + "A reserved creation identity is already in use. Cancel and start a fresh draft." + .into(), + ); + } + let kind = family.kind(self.layer); + let parent = if family == Family::Machine { + Some( + self.owner + .filter(|id| eligible(p, self.layer, Picker::Owner, *id)) + .ok_or("Choose the participant that owns this state machine.")?, + ) + } else { + if self.owner.is_some() { + return Err("Processes and scenarios belong at the layer root; context is a separate reference.".into()); + } + None + }; + if family != Family::Machine && self.first_view { + return Err("A state view can only accompany a state machine.".into()); + } + let mut body = ElementBody::new(kind, self.layer) + .ok_or("This behavior is unavailable in this layer.")?; + if family == Family::Scenario { + let viewpoint = self + .viewpoint + .filter(|v| viewpoints(self.layer).contains(v)) + .ok_or("Choose the scenario's viewpoint explicitly.")?; + if self + .context + .is_some_and(|id| !eligible(p, self.layer, Picker::Context, id)) + { + return Err( + "The selected scenario context is missing or no longer compatible.".into(), + ); + } + body = ElementBody::shared_in( + self.layer, + SharedBody::Scenario { + viewpoint, + context: self.context, + precondition: String::new(), + postcondition: String::new(), + steps: Vec::new(), + }, + ); + } else if self.viewpoint.is_some() || self.context.is_some() { + return Err( + "Scenario viewpoint and context cannot be applied to another behavior family." + .into(), + ); + } + let element = Element { + id: self.element, + alias: p.fresh_alias(&self.name, "behavior", self.element), + name: self.name.clone(), + description: String::new(), + parent, + body, + }; + let mut commands = vec![Command::CreateElement(element)]; + if self.first_view { + let working = execute_batch(p, &commands).map_err(|e| e.to_string())?; + // Reuse the state editor's geometry contract. A new machine has one + // root placement; reserve both presentation IDs across retries. + let plan = super::state_edit::create_view(&working, self.element) + .map_err(|e| e.to_string())?; + let Command::Batch(mut view_commands) = plan.command else { + return Err("Unexpected state-view plan.".into()); + }; + if view_commands.len() != 2 { + return Err("Unexpected additional operation in the first state-view plan.".into()); + } + let Some(Command::Layout(layout)) = view_commands.pop() else { + return Err("State-view plan must finish with its layout operation.".into()); + }; + let Some(Command::CreateDiagram(mut diagram)) = view_commands.pop() else { + return Err("State-view plan is missing its diagram.".into()); + }; + if diagram.id != layout || diagram.nodes.len() != 1 { + return Err("A first state view must contain only its new machine.".into()); + } + let mut node = diagram + .nodes + .into_values() + .next() + .ok_or("State-view root is missing.")?; + if node.element != self.element { + return Err("State-view root differs from the new machine.".into()); + } + diagram.id = self.diagram; + diagram.alias = working.fresh_alias(&diagram.name, "state_view", self.diagram); + node.id = self.placement; + node.alias = working.fresh_alias( + &format!("{} placement", self.name), + "placement", + self.placement, + ); + diagram.nodes = [(node.id, node)].into_iter().collect(); + commands.push(Command::CreateDiagram(diagram)); + commands.push(Command::Layout(self.diagram)); + } + let command = Command::Batch(commands); + execute_batch(p, std::slice::from_ref(&command)).map_err(|e| e.to_string())?; + Ok(command) + } +} + +impl State { + /// Parent maps these local events into the existing guarded Behavior route. + /// The card's fields scroll; Create/Cancel stay outside that scroll region. + pub fn view<'a>(&'a self, p: &'a Project) -> Option> { + self.view_with(p) + } + fn view_with<'a, R: iced::advanced::text::Renderer + 'a>( + &'a self, + p: &'a Project, + ) -> Option> { + let d = self.draft.as_ref()?; + if let Some(view) = d.source.view(p) { + return Some( + column![ + text(format!( + "State machine name: {}", + crate::views::short(&d.name, 60) + )) + .size(12), + view.map(Event::Source) + ] + .spacing(6) + .height(Fill) + .into(), + ); + } + let mut fields = column![].spacing(10); + let title = if let Some(family) = d.family { + fields = fields.push( + text_input("Exact behavior name", &d.name) + .id(NAME_INPUT) + .on_input(Event::Name) + .on_submit(Event::Submit) + .padding(8), + ); + match family { + Family::Sequence => { + fields = fields.push( + text(format!( + "Owned by the {} layer root. Add work and exchange contributions after creation.", + d.layer + )) + .size(12), + ); + } + Family::Scenario => { + fields = fields.push( + text(format!( + "Owned by the {} layer root. Choose what the lifelines represent:", + d.layer + )) + .size(12), + ); + for viewpoint in viewpoints(d.layer) { + fields = fields.push( + button(text(viewpoint_title(viewpoint)).size(13)) + .style(if d.viewpoint == Some(viewpoint) { + button::primary + } else { + button::secondary + }) + .on_press(Event::Viewpoint(viewpoint)) + .padding(7), + ); + } + let context_label = if d.context_open { + "Hide optional context".into() + } else if let Some(id) = d.context { + format!("Context: {} · Change…", reference_label(p, id)) + } else { + "Add optional context…".into() + }; + fields = fields.push( + button(text(context_label).size(12)) + .on_press(Event::ContextOptions(!d.context_open)) + .padding(6), + ); + if d.context_open { + fields = fields.push(picker_view(p, d, Picker::Context)); + } + } + Family::Machine => { + if let Some(label) = d.source.label(d.owner) { + fields = fields.push(text(label).size(13)).push(text("Create and open commits the new owner and machine together, including the first view only when selected.").size(12)); + } + fields = fields.push(picker_view(p, d, Picker::Owner)); + fields = fields.push( + button("Create new owner…") + .on_press(Event::Source(super::source_draft::Event::Open( + d.invocation, + ))) + .padding(7), + ); + fields = fields.push( + checkbox(d.first_view) + .label("Create the first state view") + .on_toggle(Event::FirstView), + ); + fields = fields.push(text("The machine starts without regions or states. Add those explicitly in its workspace.").size(12)); + } + } + format!("New {}", family.title(d.layer)) + } else { + for family in [Family::Sequence, Family::Scenario, Family::Machine] { + let purpose = match family { + Family::Sequence => "A path through work and exchanges", + Family::Scenario => "Ordered interactions from an explicit viewpoint", + Family::Machine => "States and modes of an explicitly chosen participant", + }; + fields = fields.push( + button( + column![text(family.title(d.layer)).size(15), text(purpose).size(12)] + .spacing(3), + ) + .on_press(Event::Choose(family)) + .padding(10) + .width(Fill), + ); + } + format!("New behavior · {}", d.layer) + }; + let mut actions = + row![button("Cancel · Esc").on_press(Event::Cancel).padding(8)].spacing(8); + if d.family.is_some() { + actions = actions.push(button("Create and open").on_press(Event::Submit).padding(8)); + } + Some( + container( + column![ + text(title).size(20), + scrollable(fields) + .id("behavior-creation-fields") + .height(Fill), + container(actions).id("behavior-creation-actions") + ] + .spacing(12), + ) + .padding(18) + .max_width(680) + .height(Fill) + .style(crate::style::card) + .into(), + ) + } +} +fn picker_view<'a, R: iced::advanced::text::Renderer + 'a>( + p: &'a Project, + d: &'a Draft, + picker: Picker, +) -> UiElement<'a, Event, iced::Theme, R> { + let browser = &d.browsers[picker.index()]; + let options = choices(p, d.layer, picker, &browser.query); + let page = browser + .page + .min(options.len().saturating_sub(1) / PAGE_SIZE); let selected = match picker { Picker::Owner => d.owner, Picker::Context => d.context, @@ -698,69 +698,69 @@ fn picker_view<'a, R: iced::advanced::text::Renderer + 'a>( let title = match picker { Picker::Owner if staged_owner => "Or choose an existing participant", Picker::Owner => "Owned by participant", - Picker::Context => "Scenario context · optional", - }; - let mut out = column![ - text(title).size(13), - text_input("Search names, aliases or parent context", &browser.query) - .on_input(move |query| Event::Search(picker, query)) - .padding(6) - ] - .spacing(5); + Picker::Context => "Scenario context · optional", + }; + let mut out = column![ + text(title).size(13), + text_input("Search names, aliases or parent context", &browser.query) + .on_input(move |query| Event::Search(picker, query)) + .padding(6) + ] + .spacing(5); if let Some(id) = selected && !staged_owner { - out = out.push(text(format!("Selected: {}", reference_label(p, id))).size(12)); - } else if picker == Picker::Context { - out = out.push(text("No context selected").size(12)); - } - if options.is_empty() { + out = out.push(text(format!("Selected: {}", reference_label(p, id))).size(12)); + } else if picker == Picker::Context { + out = out.push(text("No context selected").size(12)); + } + if options.is_empty() { out = out.push(text(match picker { Picker::Owner if staged_owner => "No existing participants match this search. The new owner above remains selected.", Picker::Owner => "No matching participant. Refine the search or choose Create new owner.", - Picker::Context => "No matching context. Refine the search, or leave this optional reference empty.", - }).size(12)); - } - for e in options.iter().skip(page * PAGE_SIZE).take(PAGE_SIZE) { - let message = match picker { - Picker::Owner => Event::Owner(e.id), - Picker::Context => Event::Context(Some(e.id)), - }; - out = out.push( - container( - button( - column![ - text(&e.name).size(13), - text(reference_context(p, e)).size(11) - ] - .spacing(2), - ) - .style(if selected == Some(e.id) { - button::primary - } else { - button::secondary - }) - .on_press(message) - .padding(6) - .width(Fill), - ) - .id(format!("behavior-creation-candidate-{}", e.id)), - ); - } - let mut navigation = row![text(format!("{} matches", options.len())).size(11)].spacing(8); - if page > 0 { - navigation = navigation.push(button("Previous").on_press(Event::Page(picker, page - 1))); - } - if (page + 1) * PAGE_SIZE < options.len() { - navigation = navigation.push(button("Next").on_press(Event::Page(picker, page + 1))); - } - if picker == Picker::Context && selected.is_some() { - navigation = navigation.push(button("Clear context").on_press(Event::Context(None))); - } - out.push(navigation).into() -} - -#[cfg(test)] -mod app_tests; -#[cfg(test)] -mod tests; + Picker::Context => "No matching context. Refine the search, or leave this optional reference empty.", + }).size(12)); + } + for e in options.iter().skip(page * PAGE_SIZE).take(PAGE_SIZE) { + let message = match picker { + Picker::Owner => Event::Owner(e.id), + Picker::Context => Event::Context(Some(e.id)), + }; + out = out.push( + container( + button( + column![ + text(&e.name).size(13), + text(reference_context(p, e)).size(11) + ] + .spacing(2), + ) + .style(if selected == Some(e.id) { + button::primary + } else { + button::secondary + }) + .on_press(message) + .padding(6) + .width(Fill), + ) + .id(format!("behavior-creation-candidate-{}", e.id)), + ); + } + let mut navigation = row![text(format!("{} matches", options.len())).size(11)].spacing(8); + if page > 0 { + navigation = navigation.push(button("Previous").on_press(Event::Page(picker, page - 1))); + } + if (page + 1) * PAGE_SIZE < options.len() { + navigation = navigation.push(button("Next").on_press(Event::Page(picker, page + 1))); + } + if picker == Picker::Context && selected.is_some() { + navigation = navigation.push(button("Clear context").on_press(Event::Context(None))); + } + out.push(navigation).into() +} + +#[cfg(test)] +mod app_tests; +#[cfg(test)] +mod tests; diff --git a/crates/oa-desktop/src/behavior/scenario/workspace.rs b/crates/oa-desktop/src/behavior/scenario/workspace.rs index 1f63379d..7f5e18f6 100644 --- a/crates/oa-desktop/src/behavior/scenario/workspace.rs +++ b/crates/oa-desktop/src/behavior/scenario/workspace.rs @@ -1,810 +1,810 @@ -//! Scenario presentation. Layout/camera controls never construct model commands. -use super::super::scenario_scroll; -use super::*; -use iced::widget::{Space, column, sensor}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Shelf { - Tools, - Selection, -} - -#[derive(Clone, Default)] -pub(super) struct Presentation { - pub(super) identity: Option<(Id, Id)>, - pub(super) viewport: Option, - pub(super) focused: bool, - pub(super) shelf: Option, - pub(super) zoom: Option, - pub(super) fit: bool, -} -const MIN_SCALE: f32 = 0.05; -impl Presentation { - pub(super) fn viewport(&mut self, size: Size) -> bool { - if !size.width.is_finite() - || !size.height.is_finite() - || size.width <= 0. - || size.height <= 0. - || self.viewport == Some(size) - { - return false; - } - self.viewport = Some(size); - true - } - pub(super) fn zoom(&mut self, scale: Option) -> bool { - if scale.is_some_and(|value| !value.is_finite() || value <= 0.) { - return false; - } - let next = scale.map(|value| value.clamp(MIN_SCALE, 3.)); - let changed = self.zoom != next || self.fit != scale.is_none(); - self.zoom = next; - self.fit = scale.is_none(); - changed - } - fn fit_scale(&self, scene: Size) -> Option { - let viewport = self.viewport?; - // This is the measured inner Scrollable, never a window-height estimate. - // Leave room for the overlay scrollbars on both axes. - Some( - ((viewport.width - 16.).max(1.) / scene.width.max(1.)) - .min((viewport.height - 16.).max(1.) / scene.height.max(1.)), - ) - } - fn scale(&self, scene: Size) -> f32 { - if self.fit { - self.fit_scale(scene).unwrap_or(1.).clamp(MIN_SCALE, 1.) - } else { - self.zoom.unwrap_or(1.) - } - } - fn fit_limited(&self, scene: Size) -> bool { - self.fit && self.fit_scale(scene).is_some_and(|scale| scale < MIN_SCALE) - } -} - -pub fn view<'a>(app: &'a App, owner: Id, out: Column<'a, Message>) -> Element<'a, Message> { - let p = app.project(); - let state = &app.behavior.scenario; - let presentation = &state.presentation; - let pending = state - .draft - .as_ref() - .is_some_and(|draft| draft.owner == owner); - let context = app.scenario_context().expect("visible scenario context"); - let tool = state.canvas_tool(owner); - let clean = !app.behavior.has_pending() && !app.draft_dirty() && !app.source_dirty(); - let mut scene = Scene::sequence(p, owner, app.selected); - let scale = presentation.scale(scene.size); - let limited = presentation.fit_limited(scene.size); - scene.scale = scale; - scene.interaction = Some((context, tool, clean)); - // The outline is a sibling shelf, not an ancestor of this canvas. - scene.scroll_offset = state.scroll_offset(); - scene.pending_placement = state.pending_placement(owner); - let size = Size::new(scene.size.width * scale, scene.size.height * scale); - let canvas = canvas_pane( - context, - state.scroll_restore(context, ScrollArea::Canvas), - canvas::Canvas::new(scene) - .width(size.width) - .height(size.height) - .into(), - ); - let mut navigation = row![ - out.width(Fill), - button( - text(if presentation.focused { - "Restore panels" - } else { - "Focus canvas" - }) - .size(12) - ) - .padding(6) - .on_press(Message::ScenarioCanvas(CanvasEvent::Focus(context))) - ] - .spacing(6) - .align_y(iced::Center); - if state.can_back(p, context) { - navigation = navigation.push( - button(text("Back to caller").size(12)) - .padding(6) - .on_press_maybe( - clean.then_some(Message::ScenarioCanvas(CanvasEvent::Back(context))), - ), - ); - } - let mut header = column![ - navigation, - row![ - button(text("Tools").size(12)) - .padding(6) - .style(if presentation.shelf == Some(Shelf::Tools) { - button::primary - } else { - style::nav - }) - .on_press(Message::ScenarioCanvas(CanvasEvent::Shelf( - context, - Shelf::Tools - ))), - button(text("Selection").size(12)) - .padding(6) - .style(if presentation.shelf == Some(Shelf::Selection) { - button::primary - } else { - style::nav - }) - .on_press(Message::ScenarioCanvas(CanvasEvent::Shelf( - context, - Shelf::Selection - ))), - button(text("Fit overview").size(12)) - .padding(6) - .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom(context, None))), - button(text("−").size(12)) - .padding(6) - .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom( - context, - Some(scale / 1.25) - ))), - text(format!("{:.0}%", scale * 100.)).size(12), - button(text("+").size(12)) - .padding(6) - .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom( - context, - Some(scale * 1.25) - ))), - button(text("100%").size(12)) - .padding(6) - .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom( - context, - Some(1.) - ))) - ] - .spacing(6) - .align_y(iced::Center) - .wrap(), - text(if pending { - "Pending form · the highlighted sequence position is retained".to_owned() - } else if !p.children(owner).any(|e| e.kind() == Kind::Lifeline) { - "Start with a lifeline representing existing or explicitly new work / participants." - .into() - } else if !p.children(owner).any(|e| e.kind() == Kind::Occurrence) { - "Choose a first message, action or another tool; no occurrence is selected yet.".into() - } else { - let guidance = match tool { - CanvasTool::Message(_) => { - "Click a source position, then click or drag to a target · Esc cancels" - } - CanvasTool::Add(Tool::Action | Tool::Observation) => { - "Click a highlighted position · a lifeline click preselects that lifeline" - } - CanvasTool::Add(Tool::Reuse | Tool::Fragment) => { - "Click a highlighted sequence position to open its form" - } - _ => "Select a lifeline or occurrence · drag its ↕ handle to reorder", - }; - format!("{tool} · {guidance}") - }) - .size(11) - ] - .spacing(6); - if !pending { - if !p.children(owner).any(|e| e.kind() == Kind::Lifeline) { - header = header.push( - row![ - text(format!("No lifelines · {:?}", viewpoint(p, owner))).size(12), - button("Add first lifeline").on_press_maybe(clean.then_some({ - Message::ScenarioCanvas(CanvasEvent::First(context, Tool::Lifeline)) - })) - ] - .spacing(8), - ); - } else if !p.children(owner).any(|e| e.kind() == Kind::Occurrence) { - header = header.push( - row![ - text("No occurrences yet").size(12), - button("Add first message").on_press_maybe(clean.then_some({ - Message::ScenarioCanvas(CanvasEvent::First(context, Tool::Message)) - })), - button("Add first action").on_press_maybe(clean.then_some({ - Message::ScenarioCanvas(CanvasEvent::First(context, Tool::Action)) - })) - ] - .spacing(8), - ); - } - } - if limited { - header = header - .push(text("Minimum zoom reached · scroll to see the remaining scenario.").size(11)); - } - let details = match presentation.shelf { - Some(Shelf::Tools) => { - let choose = move |tool| Message::ScenarioCanvas(CanvasEvent::Choose(context, tool)); - let tool_picker: Element<'_, Message> = if clean { - pick_list( - vec![ - CanvasTool::Select, - CanvasTool::Message(MessageStyle::CallReply), - CanvasTool::Message(MessageStyle::Asynchronous), - CanvasTool::Message(MessageStyle::Call), - CanvasTool::Message(MessageStyle::Reply), - CanvasTool::Message(MessageStyle::Create), - CanvasTool::Message(MessageStyle::Delete), - CanvasTool::Message(MessageStyle::Timer), - CanvasTool::Add(Tool::Action), - CanvasTool::Add(Tool::Observation), - CanvasTool::Add(Tool::Reuse), - CanvasTool::Add(Tool::Fragment), - ], - Some(tool), - choose, - ) - .text_size(12) - .padding(6) - .width(Fill) - .into() - } else { - button(text(format!("{tool} · finish pending edits to change tools")).size(12)) - .padding(6) - .width(Fill) - .into() - }; - column![ - tool_picker, - row![ - button(text("+ Lifeline").size(12)) - .padding(6) - .on_press_maybe( - clean.then_some(event(Event::Start(owner, Tool::Lifeline))) - ), - button(text("Scenario properties").size(12)) - .padding(6) - .on_press_maybe(clean.then_some(Message::EditProperties(owner))), - ] - .spacing(6) - .wrap(), - text(match viewpoint(p, owner) { - ScenarioViewpoint::OperationalActivity => - "Activity scenario · lifelines represent activities", - ScenarioViewpoint::Functional => - "Functional scenario · lifelines represent system functions", - ScenarioViewpoint::ParticipantInteraction => - "Participant scenario · entities, actors or roles", - ScenarioViewpoint::BoundaryInterface => - "Interface scenario · system and external actors", - ScenarioViewpoint::MixedLegacy => "Mixed scenario · work or participants", - }) - .size(11), - ] - .spacing(6) - } - _ => { - let mut details = - column![text(format!("Selected: {}", p.named(app.selected))).size(12)].spacing(6); - let mut navigation = row![].spacing(6); - if referenced_scenario(p, owner, app.selected).is_some() { - navigation = navigation.push( - button(text("Open definition").size(12)) - .padding(6) - .on_press_maybe(clean.then_some(Message::ScenarioCanvas( - CanvasEvent::OpenUse(context, app.selected), - ))), - ); - } - navigation = navigation.push( - button(text("Properties").size(12)) - .padding(6) - .on_press_maybe(clean.then_some(Message::EditProperties(app.selected))), - ); - details = details.push(navigation.wrap()); - if lines(p, owner).is_empty() && p.elements[&owner].steps().is_none_or(Vec::is_empty) { - details.push( - text("Use Tools to add lifelines, then draw messages or insert occurrences.") - .size(12), - ) - } else { - sequence_rows(p, context, owner, details, clean) - } - } - }; - let details = details_pane( - context, - state.scroll_restore(context, ScrollArea::Page), - details.into(), - presentation.shelf.is_some(), - pending, - ); - let mut form_panel = editor_panel(Space::new().height(0).into(), Space::new().height(0).into()); - if let Some(d) = app - .behavior - .scenario - .draft - .as_ref() - .filter(|d| d.owner == owner) - { - if let Some(child) = d.source.view(p) { - form_panel = column![ - text(format!( - "Consumer name: {}", - crate::views::short(&d.name, 60) - )) - .size(12), - child.map(|e| event(Event::Source(e))) - ] - .spacing(6) - .height(Fill) - .into(); - } else { - let mut form = column![ - text(d.tool.label()).size(16), - text_input("Name (defaults to selected source)", &d.name) - .on_input(|s| event(Event::Name(s))) - .padding(9) - .size(12) - ] - .spacing(9); - if d.tool != Tool::Lifeline { - form = form.push(label(format!( - "Insert: {}", - slot_label( - p, - Slot { - parent: d.parent, - before: d.before - } - ) - ))); - if d.before.is_some() { - form = form.push( - button("Append to this sequence instead") - .on_press(event(Event::AppendInstead)), - ); - } - form = form.push(picker( - parents(p, owner), - Some(d.parent), - "Destination sequence / operand…", - Event::Parent, - )); - } - if matches!( - d.tool, - Tool::Lifeline | Tool::Reuse | Tool::Action | Tool::Observation - ) { - form = form.push(picker( - sources(p, d), - d.target, - "Choose a named source…", - Event::Target, - )); - } - if d.tool == Tool::Lifeline { - if let Some(title) = d.source.label(d.target) { - form = form.push(label(title)).push(label( - "Create commits one new source and this lifeline together.", - )); - } - if viewpoint(p, owner) != ScenarioViewpoint::MixedLegacy { - form = form.push(button("Create new source…").on_press(event(Event::Source( - super::super::source_draft::Event::Open(d.invocation), - )))); - } else { - form=form.push(label("New source creation requires a strict viewpoint. Existing sources remain available.")); - } - } - if d.tool == Tool::Message { - form = form.push( - pick_list( - vec![ - MessageStyle::Asynchronous, - MessageStyle::Call, - MessageStyle::CallReply, - MessageStyle::Reply, - MessageStyle::Create, - MessageStyle::Delete, - MessageStyle::Timer, - ], - Some(d.style), - |style| event(Event::Style(style)), - ) - .width(Fill) - .padding(9) - .text_size(12), - ); - if d.style == MessageStyle::Reply { - form = form.push(bounded_picker( - app.behavior.scenario.call_choices(p, context, d), - d, - d.call, - "Choose the earlier call…", - Event::Call, - )); - if d.call.is_some() { - form = form.push(label( - "Reply reuses the call exchange in reverse and retains its correlation.", - )); - } - } else if d.style != MessageStyle::Timer { - form = form.push(bounded_picker( - exchanges(p, d), - d, - d.exchange, - "Choose the exchange…", - Event::Exchange, - )); - form = form.push(label( - "Apply checks that the message is valid at this insertion position.", - )); - } - form = form.push(picker( - d.line_choices(p, true), - d.sender, - "Sending lifeline…", - Event::Sender, - )); - if d.style != MessageStyle::Timer { - form = form.push(picker( - d.line_choices(p, false), - d.receiver, - "Receiving lifeline…", - Event::Receiver, - )); - } - if d.style == MessageStyle::Timer { - form = form.push( - text_input("Duration / deadline, e.g. 30 s", &d.expression) - .on_input(|s| event(Event::Expression(s))) - .padding(9) - .size(12), - ); - } - if p.layer_of(owner) == Some(Layer::Sa) - && viewpoint(p, owner) == ScenarioViewpoint::BoundaryInterface - && matches!( - d.style, - MessageStyle::Call | MessageStyle::CallReply | MessageStyle::Asynchronous - ) - { - form = form.push( - iced::widget::checkbox(d.operation.is_some()) - .label("Invoke a specific interface operation") - .on_toggle(|enabled| event(Event::OperationIntent(enabled))), - ); - if let Some(operation) = &d.operation { - form = form - .push(bounded_picker( - crate::data_values::operation_choices(p, &d.proposed_message(p)), - d, - operation.allocation, - "Choose the exact interface operation allocation…", - Event::Operation, - )) - .push( - button("Keep operation unresolved") - .on_press(event(Event::ClearOperation)), - ); - if let Some(id) = operation.allocation { - form = form.push(label(crate::data_values::operation_identity(p, id))); - } - } - } - if let Some(description) = - crate::data_values::operation_label(p, &d.proposed_message(p)) - { - form = form.push(label(description)); - } - match d.signature(p) { - Ok(Some(exchange)) => form = form.push(value_panel(p, d, &exchange)), - Err(problem) => { - form = form.push(label(problem)); - for (key, expression) in d.authored_values() { - form = form - .push( - text(format!( - "Retained value · {} / {}", - key.path - .iter() - .map(|id| p.named(*id)) - .collect::>() - .join(" → "), - p.named(key.field) - )) - .size(11), - ) - .push( - text_input("Value expression", expression) - .on_input({ - let key = key.clone(); - move |raw| event(Event::Value(key.clone(), raw)) - }) - .padding(8) - .size(12), - ); - } - } - Ok(None) => {} - } - if d.style == MessageStyle::CallReply { - form=form.push(label("Creates the call and reversed, correlated reply together in one undoable operation.")); - } - } - if matches!(d.tool, Tool::Action | Tool::Observation) { - form = form.push(picker( - lines(p, owner), - d.sender, - "Observed lifeline…", - Event::Sender, - )); - } - if d.tool == Tool::Reuse - && let Some(target) = d.target - { - for line in lines(p, target) { - let referenced = line.id; - let compatible: Vec<_> = lines(p, owner) - .into_iter() - .filter(|c| { - compatible_binding(p, referenced, c.id) - && !d - .bindings - .iter() - .any(|(r, l)| *r != referenced && *l == c.id) - }) - .collect(); - if compatible.is_empty() { - form = form.push(label(format!( - "Add a compatible local lifeline before binding '{}'.", - p.named(referenced) - ))); - } - form = form - .push(label(format!("Bind {}", p.named(referenced)))) - .push(picker( - compatible, - d.bindings.get(&referenced).copied(), - "Compatible local lifeline…", - move |c| Event::Binding(referenced, c), - )); - } - form = form.push(label( - "Bindings are explicit, one-to-one and refer to existing lifelines.", - )); - } - if d.tool == Tool::Fragment { - form = form.push( - pick_list( - vec![ - Operator(FragmentOperator::Alternative), - Operator(FragmentOperator::Parallel), - Operator(FragmentOperator::Loop), - Operator(FragmentOperator::Optional), - ], - Some(Operator(d.operator)), - |o| event(Event::Operator(o)), - ) - .padding(9) - .text_size(12) - .width(Fill), - ); - if d.operator != FragmentOperator::Parallel { - form = form.push( - text_input("Condition / loop guard", &d.expression) - .on_input(|s| event(Event::Expression(s))) - .padding(9) - .size(12), - ); - } - if d.operator == FragmentOperator::Alternative { - form = form.push( - text_input("Second alternative guard", &d.second_guard) - .on_input(|s| event(Event::SecondGuard(s))) - .padding(9) - .size(12), - ); - } - form=form.push(label("Creates the fragment with its operands. Choose an operand as destination when adding messages or actions.")); - } - let readiness = d.command(p); - if let Err(error) = &readiness { - form = form.push(label(error.clone())); - } - let footer = row![ - button("Create") - .padding(9) - .on_press_maybe(readiness.is_ok().then_some(event(Event::Submit))), - action("Cancel", event(Event::Cancel)) - ] - .spacing(7); - form_panel = editor_panel(footer.into(), form.into()); - } - } - workspace_layout(header.into(), details, canvas, form_panel, pending) -} - -fn sequence_action<'a>( - title: &str, - message: Message, - enabled: bool, -) -> iced::widget::Button<'a, Message> { - button(text(title.to_owned()).size(12)) - .padding(8) - .style(style::nav) - .on_press_maybe(enabled.then_some(message)) -} - -// Permanent header/shelf/body and canvas/form slots. Zero-sized disclosures -// must remain in the tree: Column::push otherwise drops them and remounts siblings. -fn workspace_layout<'a, R: iced::advanced::text::Renderer + 'a>( - header: iced::Element<'a, Message, Theme, R>, - details: iced::Element<'a, Message, Theme, R>, - canvas: iced::Element<'a, Message, Theme, R>, - form: iced::Element<'a, Message, Theme, R>, - pending: bool, -) -> iced::Element<'a, Message, Theme, R> { - let panes = Column::from_vec(vec![ - container(canvas).width(Fill).height(Fill).into(), - container(form) - .width(Fill) - .height(if pending { - Fill - } else { - iced::Length::Fixed(0.) - }) - .clip(true) - .into(), - ]) - .spacing(if pending { 6 } else { 0 }) - .width(Fill) - .height(Fill); - Column::from_vec(vec![header, details, panes.into()]) - .spacing(6) - .padding(8) - .width(Fill) - .height(Fill) - .into() -} - -fn details_pane<'a, R: iced::advanced::text::Renderer + 'a>( - context: Context, - restore: Option, - details: iced::Element<'a, Message, Theme, R>, - open: bool, - pending: bool, -) -> iced::Element<'a, Message, Theme, R> { - scenario_scroll::observe( - context, - ScrollArea::Page, - restore, - scrollable(details) - .id("scenario-details") - .width(Fill) - .height(if open { - if pending { 72. } else { 100. } - } else { - 0. - }), - ) -} - -fn canvas_pane<'a, R: iced::advanced::text::Renderer + 'a>( - context: Context, - restore: Option, - content: iced::Element<'a, Message, Theme, R>, -) -> iced::Element<'a, Message, Theme, R> { - container( - sensor(scenario_scroll::observe( - context, - ScrollArea::Canvas, - restore, - scrollable(content) - .id("scenario-canvas") - .direction(scrollable::Direction::Both { - vertical: scrollable::Scrollbar::default(), - horizontal: scrollable::Scrollbar::default(), - }) - .width(Fill) - .height(Fill), - )) - // A new owner/document needs its first measurement even at the same size. - .key((context.document, context.scenario)) - .on_show(move |size| Message::ScenarioCanvas(CanvasEvent::Viewport(context, size))) - .on_resize(move |size| Message::ScenarioCanvas(CanvasEvent::Viewport(context, size))), - ) - .padding(8) - .width(Fill) - .height(Fill) - .style(style::card) - .into() -} - -fn editor_panel<'a, R: iced::advanced::text::Renderer + 'a>( - actions: iced::Element<'a, Message, Theme, R>, - fields: iced::Element<'a, Message, Theme, R>, -) -> iced::Element<'a, Message, Theme, R> { - container( - column![ - container(actions).id("scenario-actions").width(Fill), - scrollable(fields) - .id("scenario-form") - .width(Fill) - .height(Fill), - ] - .spacing(6), - ) - .padding(10) - .width(Fill) - .height(Fill) - .style(style::card) - .into() -} - -#[cfg(all(test, debug_assertions))] -mod tests; - -fn sequence_rows<'a>( - p: &'a Project, - context: Context, - owner: Id, - mut out: Column<'a, Message>, - enabled: bool, -) -> Column<'a, Message> { - if let Some(SharedBody::Occurrence { - body: OccurrenceBody::Fragment { branches, .. }, - .. - }) = p.elements.get(&owner).and_then(|e| e.shared()) - { - for branch in branches { - out = out.push(sequence_action( - &format!("Operand: {}", p.named(*branch)), - Message::Select(*branch), - enabled, - )); - out = sequence_rows(p, context, *branch, out, enabled); - } - } - if let Some(steps) = p.elements.get(&owner).and_then(|e| e.steps()) { - for (i, id) in steps.iter().enumerate() { - out = out.push( - row![ - sequence_action( - &format!("{}. {}", i + 1, p.named(*id)), - Message::Select(*id), - enabled - ) - .width(Fill), - button("↑") - .padding(8) - .on_press_maybe((enabled && i > 0).then_some(Message::Command( - Command::MoveStep(owner, i, i.saturating_sub(1)) - ))), - button("↓").padding(8).on_press_maybe( - (enabled && i + 1 < steps.len()) - .then_some(Message::Command(Command::MoveStep(owner, i, i + 1))) - ) - ] - .spacing(6), - ); - if let Some(SharedBody::Occurrence { - body: - OccurrenceBody::ScenarioUse { - scenario: Some(target), - .. - }, - .. - }) = p.elements[id].shared() - { - out = out.push(sequence_action( - &format!("Open {} →", p.named(*target)), - Message::ScenarioCanvas(CanvasEvent::OpenUse(context, *id)), - enabled, - )); - } - out = sequence_rows(p, context, *id, out, enabled); - } - } - out -} +//! Scenario presentation. Layout/camera controls never construct model commands. +use super::super::scenario_scroll; +use super::*; +use iced::widget::{Space, column, sensor}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Shelf { + Tools, + Selection, +} + +#[derive(Clone, Default)] +pub(super) struct Presentation { + pub(super) identity: Option<(Id, Id)>, + pub(super) viewport: Option, + pub(super) focused: bool, + pub(super) shelf: Option, + pub(super) zoom: Option, + pub(super) fit: bool, +} +const MIN_SCALE: f32 = 0.05; +impl Presentation { + pub(super) fn viewport(&mut self, size: Size) -> bool { + if !size.width.is_finite() + || !size.height.is_finite() + || size.width <= 0. + || size.height <= 0. + || self.viewport == Some(size) + { + return false; + } + self.viewport = Some(size); + true + } + pub(super) fn zoom(&mut self, scale: Option) -> bool { + if scale.is_some_and(|value| !value.is_finite() || value <= 0.) { + return false; + } + let next = scale.map(|value| value.clamp(MIN_SCALE, 3.)); + let changed = self.zoom != next || self.fit != scale.is_none(); + self.zoom = next; + self.fit = scale.is_none(); + changed + } + fn fit_scale(&self, scene: Size) -> Option { + let viewport = self.viewport?; + // This is the measured inner Scrollable, never a window-height estimate. + // Leave room for the overlay scrollbars on both axes. + Some( + ((viewport.width - 16.).max(1.) / scene.width.max(1.)) + .min((viewport.height - 16.).max(1.) / scene.height.max(1.)), + ) + } + fn scale(&self, scene: Size) -> f32 { + if self.fit { + self.fit_scale(scene).unwrap_or(1.).clamp(MIN_SCALE, 1.) + } else { + self.zoom.unwrap_or(1.) + } + } + fn fit_limited(&self, scene: Size) -> bool { + self.fit && self.fit_scale(scene).is_some_and(|scale| scale < MIN_SCALE) + } +} + +pub fn view<'a>(app: &'a App, owner: Id, out: Column<'a, Message>) -> Element<'a, Message> { + let p = app.project(); + let state = &app.behavior.scenario; + let presentation = &state.presentation; + let pending = state + .draft + .as_ref() + .is_some_and(|draft| draft.owner == owner); + let context = app.scenario_context().expect("visible scenario context"); + let tool = state.canvas_tool(owner); + let clean = !app.behavior.has_pending() && !app.draft_dirty() && !app.source_dirty(); + let mut scene = Scene::sequence(p, owner, app.selected); + let scale = presentation.scale(scene.size); + let limited = presentation.fit_limited(scene.size); + scene.scale = scale; + scene.interaction = Some((context, tool, clean)); + // The outline is a sibling shelf, not an ancestor of this canvas. + scene.scroll_offset = state.scroll_offset(); + scene.pending_placement = state.pending_placement(owner); + let size = Size::new(scene.size.width * scale, scene.size.height * scale); + let canvas = canvas_pane( + context, + state.scroll_restore(context, ScrollArea::Canvas), + canvas::Canvas::new(scene) + .width(size.width) + .height(size.height) + .into(), + ); + let mut navigation = row![ + out.width(Fill), + button( + text(if presentation.focused { + "Restore panels" + } else { + "Focus canvas" + }) + .size(12) + ) + .padding(6) + .on_press(Message::ScenarioCanvas(CanvasEvent::Focus(context))) + ] + .spacing(6) + .align_y(iced::Center); + if state.can_back(p, context) { + navigation = navigation.push( + button(text("Back to caller").size(12)) + .padding(6) + .on_press_maybe( + clean.then_some(Message::ScenarioCanvas(CanvasEvent::Back(context))), + ), + ); + } + let mut header = column![ + navigation, + row![ + button(text("Tools").size(12)) + .padding(6) + .style(if presentation.shelf == Some(Shelf::Tools) { + button::primary + } else { + style::nav + }) + .on_press(Message::ScenarioCanvas(CanvasEvent::Shelf( + context, + Shelf::Tools + ))), + button(text("Selection").size(12)) + .padding(6) + .style(if presentation.shelf == Some(Shelf::Selection) { + button::primary + } else { + style::nav + }) + .on_press(Message::ScenarioCanvas(CanvasEvent::Shelf( + context, + Shelf::Selection + ))), + button(text("Fit overview").size(12)) + .padding(6) + .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom(context, None))), + button(text("−").size(12)) + .padding(6) + .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom( + context, + Some(scale / 1.25) + ))), + text(format!("{:.0}%", scale * 100.)).size(12), + button(text("+").size(12)) + .padding(6) + .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom( + context, + Some(scale * 1.25) + ))), + button(text("100%").size(12)) + .padding(6) + .on_press(Message::ScenarioCanvas(CanvasEvent::Zoom( + context, + Some(1.) + ))) + ] + .spacing(6) + .align_y(iced::Center) + .wrap(), + text(if pending { + "Pending form · the highlighted sequence position is retained".to_owned() + } else if !p.children(owner).any(|e| e.kind() == Kind::Lifeline) { + "Start with a lifeline representing existing or explicitly new work / participants." + .into() + } else if !p.children(owner).any(|e| e.kind() == Kind::Occurrence) { + "Choose a first message, action or another tool; no occurrence is selected yet.".into() + } else { + let guidance = match tool { + CanvasTool::Message(_) => { + "Click a source position, then click or drag to a target · Esc cancels" + } + CanvasTool::Add(Tool::Action | Tool::Observation) => { + "Click a highlighted position · a lifeline click preselects that lifeline" + } + CanvasTool::Add(Tool::Reuse | Tool::Fragment) => { + "Click a highlighted sequence position to open its form" + } + _ => "Select a lifeline or occurrence · drag its ↕ handle to reorder", + }; + format!("{tool} · {guidance}") + }) + .size(11) + ] + .spacing(6); + if !pending { + if !p.children(owner).any(|e| e.kind() == Kind::Lifeline) { + header = header.push( + row![ + text(format!("No lifelines · {:?}", viewpoint(p, owner))).size(12), + button("Add first lifeline").on_press_maybe(clean.then_some({ + Message::ScenarioCanvas(CanvasEvent::First(context, Tool::Lifeline)) + })) + ] + .spacing(8), + ); + } else if !p.children(owner).any(|e| e.kind() == Kind::Occurrence) { + header = header.push( + row![ + text("No occurrences yet").size(12), + button("Add first message").on_press_maybe(clean.then_some({ + Message::ScenarioCanvas(CanvasEvent::First(context, Tool::Message)) + })), + button("Add first action").on_press_maybe(clean.then_some({ + Message::ScenarioCanvas(CanvasEvent::First(context, Tool::Action)) + })) + ] + .spacing(8), + ); + } + } + if limited { + header = header + .push(text("Minimum zoom reached · scroll to see the remaining scenario.").size(11)); + } + let details = match presentation.shelf { + Some(Shelf::Tools) => { + let choose = move |tool| Message::ScenarioCanvas(CanvasEvent::Choose(context, tool)); + let tool_picker: Element<'_, Message> = if clean { + pick_list( + vec![ + CanvasTool::Select, + CanvasTool::Message(MessageStyle::CallReply), + CanvasTool::Message(MessageStyle::Asynchronous), + CanvasTool::Message(MessageStyle::Call), + CanvasTool::Message(MessageStyle::Reply), + CanvasTool::Message(MessageStyle::Create), + CanvasTool::Message(MessageStyle::Delete), + CanvasTool::Message(MessageStyle::Timer), + CanvasTool::Add(Tool::Action), + CanvasTool::Add(Tool::Observation), + CanvasTool::Add(Tool::Reuse), + CanvasTool::Add(Tool::Fragment), + ], + Some(tool), + choose, + ) + .text_size(12) + .padding(6) + .width(Fill) + .into() + } else { + button(text(format!("{tool} · finish pending edits to change tools")).size(12)) + .padding(6) + .width(Fill) + .into() + }; + column![ + tool_picker, + row![ + button(text("+ Lifeline").size(12)) + .padding(6) + .on_press_maybe( + clean.then_some(event(Event::Start(owner, Tool::Lifeline))) + ), + button(text("Scenario properties").size(12)) + .padding(6) + .on_press_maybe(clean.then_some(Message::EditProperties(owner))), + ] + .spacing(6) + .wrap(), + text(match viewpoint(p, owner) { + ScenarioViewpoint::OperationalActivity => + "Activity scenario · lifelines represent activities", + ScenarioViewpoint::Functional => + "Functional scenario · lifelines represent system functions", + ScenarioViewpoint::ParticipantInteraction => + "Participant scenario · entities, actors or roles", + ScenarioViewpoint::BoundaryInterface => + "Interface scenario · system and external actors", + ScenarioViewpoint::MixedLegacy => "Mixed scenario · work or participants", + }) + .size(11), + ] + .spacing(6) + } + _ => { + let mut details = + column![text(format!("Selected: {}", p.named(app.selected))).size(12)].spacing(6); + let mut navigation = row![].spacing(6); + if referenced_scenario(p, owner, app.selected).is_some() { + navigation = navigation.push( + button(text("Open definition").size(12)) + .padding(6) + .on_press_maybe(clean.then_some(Message::ScenarioCanvas( + CanvasEvent::OpenUse(context, app.selected), + ))), + ); + } + navigation = navigation.push( + button(text("Properties").size(12)) + .padding(6) + .on_press_maybe(clean.then_some(Message::EditProperties(app.selected))), + ); + details = details.push(navigation.wrap()); + if lines(p, owner).is_empty() && p.elements[&owner].steps().is_none_or(Vec::is_empty) { + details.push( + text("Use Tools to add lifelines, then draw messages or insert occurrences.") + .size(12), + ) + } else { + sequence_rows(p, context, owner, details, clean) + } + } + }; + let details = details_pane( + context, + state.scroll_restore(context, ScrollArea::Page), + details.into(), + presentation.shelf.is_some(), + pending, + ); + let mut form_panel = editor_panel(Space::new().height(0).into(), Space::new().height(0).into()); + if let Some(d) = app + .behavior + .scenario + .draft + .as_ref() + .filter(|d| d.owner == owner) + { + if let Some(child) = d.source.view(p) { + form_panel = column![ + text(format!( + "Consumer name: {}", + crate::views::short(&d.name, 60) + )) + .size(12), + child.map(|e| event(Event::Source(e))) + ] + .spacing(6) + .height(Fill) + .into(); + } else { + let mut form = column![ + text(d.tool.label()).size(16), + text_input("Name (defaults to selected source)", &d.name) + .on_input(|s| event(Event::Name(s))) + .padding(9) + .size(12) + ] + .spacing(9); + if d.tool != Tool::Lifeline { + form = form.push(label(format!( + "Insert: {}", + slot_label( + p, + Slot { + parent: d.parent, + before: d.before + } + ) + ))); + if d.before.is_some() { + form = form.push( + button("Append to this sequence instead") + .on_press(event(Event::AppendInstead)), + ); + } + form = form.push(picker( + parents(p, owner), + Some(d.parent), + "Destination sequence / operand…", + Event::Parent, + )); + } + if matches!( + d.tool, + Tool::Lifeline | Tool::Reuse | Tool::Action | Tool::Observation + ) { + form = form.push(picker( + sources(p, d), + d.target, + "Choose a named source…", + Event::Target, + )); + } + if d.tool == Tool::Lifeline { + if let Some(title) = d.source.label(d.target) { + form = form.push(label(title)).push(label( + "Create commits one new source and this lifeline together.", + )); + } + if viewpoint(p, owner) != ScenarioViewpoint::MixedLegacy { + form = form.push(button("Create new source…").on_press(event(Event::Source( + super::super::source_draft::Event::Open(d.invocation), + )))); + } else { + form=form.push(label("New source creation requires a strict viewpoint. Existing sources remain available.")); + } + } + if d.tool == Tool::Message { + form = form.push( + pick_list( + vec![ + MessageStyle::Asynchronous, + MessageStyle::Call, + MessageStyle::CallReply, + MessageStyle::Reply, + MessageStyle::Create, + MessageStyle::Delete, + MessageStyle::Timer, + ], + Some(d.style), + |style| event(Event::Style(style)), + ) + .width(Fill) + .padding(9) + .text_size(12), + ); + if d.style == MessageStyle::Reply { + form = form.push(bounded_picker( + app.behavior.scenario.call_choices(p, context, d), + d, + d.call, + "Choose the earlier call…", + Event::Call, + )); + if d.call.is_some() { + form = form.push(label( + "Reply reuses the call exchange in reverse and retains its correlation.", + )); + } + } else if d.style != MessageStyle::Timer { + form = form.push(bounded_picker( + exchanges(p, d), + d, + d.exchange, + "Choose the exchange…", + Event::Exchange, + )); + form = form.push(label( + "Apply checks that the message is valid at this insertion position.", + )); + } + form = form.push(picker( + d.line_choices(p, true), + d.sender, + "Sending lifeline…", + Event::Sender, + )); + if d.style != MessageStyle::Timer { + form = form.push(picker( + d.line_choices(p, false), + d.receiver, + "Receiving lifeline…", + Event::Receiver, + )); + } + if d.style == MessageStyle::Timer { + form = form.push( + text_input("Duration / deadline, e.g. 30 s", &d.expression) + .on_input(|s| event(Event::Expression(s))) + .padding(9) + .size(12), + ); + } + if p.layer_of(owner) == Some(Layer::Sa) + && viewpoint(p, owner) == ScenarioViewpoint::BoundaryInterface + && matches!( + d.style, + MessageStyle::Call | MessageStyle::CallReply | MessageStyle::Asynchronous + ) + { + form = form.push( + iced::widget::checkbox(d.operation.is_some()) + .label("Invoke a specific interface operation") + .on_toggle(|enabled| event(Event::OperationIntent(enabled))), + ); + if let Some(operation) = &d.operation { + form = form + .push(bounded_picker( + crate::data_values::operation_choices(p, &d.proposed_message(p)), + d, + operation.allocation, + "Choose the exact interface operation allocation…", + Event::Operation, + )) + .push( + button("Keep operation unresolved") + .on_press(event(Event::ClearOperation)), + ); + if let Some(id) = operation.allocation { + form = form.push(label(crate::data_values::operation_identity(p, id))); + } + } + } + if let Some(description) = + crate::data_values::operation_label(p, &d.proposed_message(p)) + { + form = form.push(label(description)); + } + match d.signature(p) { + Ok(Some(exchange)) => form = form.push(value_panel(p, d, &exchange)), + Err(problem) => { + form = form.push(label(problem)); + for (key, expression) in d.authored_values() { + form = form + .push( + text(format!( + "Retained value · {} / {}", + key.path + .iter() + .map(|id| p.named(*id)) + .collect::>() + .join(" → "), + p.named(key.field) + )) + .size(11), + ) + .push( + text_input("Value expression", expression) + .on_input({ + let key = key.clone(); + move |raw| event(Event::Value(key.clone(), raw)) + }) + .padding(8) + .size(12), + ); + } + } + Ok(None) => {} + } + if d.style == MessageStyle::CallReply { + form=form.push(label("Creates the call and reversed, correlated reply together in one undoable operation.")); + } + } + if matches!(d.tool, Tool::Action | Tool::Observation) { + form = form.push(picker( + lines(p, owner), + d.sender, + "Observed lifeline…", + Event::Sender, + )); + } + if d.tool == Tool::Reuse + && let Some(target) = d.target + { + for line in lines(p, target) { + let referenced = line.id; + let compatible: Vec<_> = lines(p, owner) + .into_iter() + .filter(|c| { + compatible_binding(p, referenced, c.id) + && !d + .bindings + .iter() + .any(|(r, l)| *r != referenced && *l == c.id) + }) + .collect(); + if compatible.is_empty() { + form = form.push(label(format!( + "Add a compatible local lifeline before binding '{}'.", + p.named(referenced) + ))); + } + form = form + .push(label(format!("Bind {}", p.named(referenced)))) + .push(picker( + compatible, + d.bindings.get(&referenced).copied(), + "Compatible local lifeline…", + move |c| Event::Binding(referenced, c), + )); + } + form = form.push(label( + "Bindings are explicit, one-to-one and refer to existing lifelines.", + )); + } + if d.tool == Tool::Fragment { + form = form.push( + pick_list( + vec![ + Operator(FragmentOperator::Alternative), + Operator(FragmentOperator::Parallel), + Operator(FragmentOperator::Loop), + Operator(FragmentOperator::Optional), + ], + Some(Operator(d.operator)), + |o| event(Event::Operator(o)), + ) + .padding(9) + .text_size(12) + .width(Fill), + ); + if d.operator != FragmentOperator::Parallel { + form = form.push( + text_input("Condition / loop guard", &d.expression) + .on_input(|s| event(Event::Expression(s))) + .padding(9) + .size(12), + ); + } + if d.operator == FragmentOperator::Alternative { + form = form.push( + text_input("Second alternative guard", &d.second_guard) + .on_input(|s| event(Event::SecondGuard(s))) + .padding(9) + .size(12), + ); + } + form=form.push(label("Creates the fragment with its operands. Choose an operand as destination when adding messages or actions.")); + } + let readiness = d.command(p); + if let Err(error) = &readiness { + form = form.push(label(error.clone())); + } + let footer = row![ + button("Create") + .padding(9) + .on_press_maybe(readiness.is_ok().then_some(event(Event::Submit))), + action("Cancel", event(Event::Cancel)) + ] + .spacing(7); + form_panel = editor_panel(footer.into(), form.into()); + } + } + workspace_layout(header.into(), details, canvas, form_panel, pending) +} + +fn sequence_action<'a>( + title: &str, + message: Message, + enabled: bool, +) -> iced::widget::Button<'a, Message> { + button(text(title.to_owned()).size(12)) + .padding(8) + .style(style::nav) + .on_press_maybe(enabled.then_some(message)) +} + +// Permanent header/shelf/body and canvas/form slots. Zero-sized disclosures +// must remain in the tree: Column::push otherwise drops them and remounts siblings. +fn workspace_layout<'a, R: iced::advanced::text::Renderer + 'a>( + header: iced::Element<'a, Message, Theme, R>, + details: iced::Element<'a, Message, Theme, R>, + canvas: iced::Element<'a, Message, Theme, R>, + form: iced::Element<'a, Message, Theme, R>, + pending: bool, +) -> iced::Element<'a, Message, Theme, R> { + let panes = Column::from_vec(vec![ + container(canvas).width(Fill).height(Fill).into(), + container(form) + .width(Fill) + .height(if pending { + Fill + } else { + iced::Length::Fixed(0.) + }) + .clip(true) + .into(), + ]) + .spacing(if pending { 6 } else { 0 }) + .width(Fill) + .height(Fill); + Column::from_vec(vec![header, details, panes.into()]) + .spacing(6) + .padding(8) + .width(Fill) + .height(Fill) + .into() +} + +fn details_pane<'a, R: iced::advanced::text::Renderer + 'a>( + context: Context, + restore: Option, + details: iced::Element<'a, Message, Theme, R>, + open: bool, + pending: bool, +) -> iced::Element<'a, Message, Theme, R> { + scenario_scroll::observe( + context, + ScrollArea::Page, + restore, + scrollable(details) + .id("scenario-details") + .width(Fill) + .height(if open { + if pending { 72. } else { 100. } + } else { + 0. + }), + ) +} + +fn canvas_pane<'a, R: iced::advanced::text::Renderer + 'a>( + context: Context, + restore: Option, + content: iced::Element<'a, Message, Theme, R>, +) -> iced::Element<'a, Message, Theme, R> { + container( + sensor(scenario_scroll::observe( + context, + ScrollArea::Canvas, + restore, + scrollable(content) + .id("scenario-canvas") + .direction(scrollable::Direction::Both { + vertical: scrollable::Scrollbar::default(), + horizontal: scrollable::Scrollbar::default(), + }) + .width(Fill) + .height(Fill), + )) + // A new owner/document needs its first measurement even at the same size. + .key((context.document, context.scenario)) + .on_show(move |size| Message::ScenarioCanvas(CanvasEvent::Viewport(context, size))) + .on_resize(move |size| Message::ScenarioCanvas(CanvasEvent::Viewport(context, size))), + ) + .padding(8) + .width(Fill) + .height(Fill) + .style(style::card) + .into() +} + +fn editor_panel<'a, R: iced::advanced::text::Renderer + 'a>( + actions: iced::Element<'a, Message, Theme, R>, + fields: iced::Element<'a, Message, Theme, R>, +) -> iced::Element<'a, Message, Theme, R> { + container( + column![ + container(actions).id("scenario-actions").width(Fill), + scrollable(fields) + .id("scenario-form") + .width(Fill) + .height(Fill), + ] + .spacing(6), + ) + .padding(10) + .width(Fill) + .height(Fill) + .style(style::card) + .into() +} + +#[cfg(all(test, debug_assertions))] +mod tests; + +fn sequence_rows<'a>( + p: &'a Project, + context: Context, + owner: Id, + mut out: Column<'a, Message>, + enabled: bool, +) -> Column<'a, Message> { + if let Some(SharedBody::Occurrence { + body: OccurrenceBody::Fragment { branches, .. }, + .. + }) = p.elements.get(&owner).and_then(|e| e.shared()) + { + for branch in branches { + out = out.push(sequence_action( + &format!("Operand: {}", p.named(*branch)), + Message::Select(*branch), + enabled, + )); + out = sequence_rows(p, context, *branch, out, enabled); + } + } + if let Some(steps) = p.elements.get(&owner).and_then(|e| e.steps()) { + for (i, id) in steps.iter().enumerate() { + out = out.push( + row![ + sequence_action( + &format!("{}. {}", i + 1, p.named(*id)), + Message::Select(*id), + enabled + ) + .width(Fill), + button("↑") + .padding(8) + .on_press_maybe((enabled && i > 0).then_some(Message::Command( + Command::MoveStep(owner, i, i.saturating_sub(1)) + ))), + button("↓").padding(8).on_press_maybe( + (enabled && i + 1 < steps.len()) + .then_some(Message::Command(Command::MoveStep(owner, i, i + 1))) + ) + ] + .spacing(6), + ); + if let Some(SharedBody::Occurrence { + body: + OccurrenceBody::ScenarioUse { + scenario: Some(target), + .. + }, + .. + }) = p.elements[id].shared() + { + out = out.push(sequence_action( + &format!("Open {} →", p.named(*target)), + Message::ScenarioCanvas(CanvasEvent::OpenUse(context, *id)), + enabled, + )); + } + out = sequence_rows(p, context, *id, out, enabled); + } + } + out +} diff --git a/crates/oa-desktop/src/behavior/topology/contextual.rs b/crates/oa-desktop/src/behavior/topology/contextual.rs index d7396f31..8bf8c059 100644 --- a/crates/oa-desktop/src/behavior/topology/contextual.rs +++ b/crates/oa-desktop/src/behavior/topology/contextual.rs @@ -1,787 +1,787 @@ -//! Lazy qualified authoring over the core address resolver. A folded reference -//! never replaces its retained endpoint with the reference or a same-named leaf. -use super::*; -use iced::widget::column; -use oa_core::topology_context::TopologyIndex; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] -pub enum Purpose { - #[default] - Precedence, - Exchange, - CommonWork, -} -impl std::fmt::Display for Purpose { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::Precedence => "Precedence", - Self::Exchange => "Exchange assembly", - Self::CommonWork => "Common work incidence", - }) - } -} -#[derive(Debug, Clone)] -pub enum Event { - Side(bool), - Pick(OccurrenceEndpoint), - Expand(OccurrenceEndpoint), - Up, - Root, - Purpose(Purpose), - Evidence(Choice), - Guard(String), - Search(String), - Page(usize), -} -#[derive(Clone, Default, serde::Serialize, serde::Deserialize)] -#[serde(deny_unknown_fields)] -pub(super) struct Draft { - edit: Option, - source: Option, - target: Option, - target_side: bool, - purpose: Purpose, - evidence: Option, - guard: String, - search: String, - page: usize, - #[serde(default)] - scope: Vec, -} -impl Draft { - pub(super) fn seed_source(&mut self, endpoint: OccurrenceEndpoint) { - if self.edit.is_none() { - self.source = Some(endpoint); - } - } - pub(super) fn new(p: &Project, owner: Id, selected: Option) -> Self { - if let Some(r) = selected.and_then(|id| p.relations.get(&id)) - && let RelationBody::ContextualLink { - owner: caller, - source, - target, - purpose, - } = &r.body - && *caller == owner - { - let (purpose, evidence, guard) = match purpose { - ContextualLinkPurpose::Precedence { guard } => { - (Purpose::Precedence, None, guard.clone()) - } - ContextualLinkPurpose::Exchange { exchange } => { - (Purpose::Exchange, Some(*exchange), String::new()) - } - ContextualLinkPurpose::CommonWork { work } => { - (Purpose::CommonWork, Some(*work), String::new()) - } - }; - return Self { - edit: Some(r.id), - source: Some(source.clone()), - target: Some(target.clone()), - purpose, - evidence, - guard, - ..Self::default() - }; - } - Self { - source: selected - .filter(|id| is_contribution(p, owner, *id)) - .map(|occurrence| OccurrenceEndpoint { - references: vec![], - occurrence, - }), - ..Self::default() - } - } - pub(super) fn update(&mut self, event: Event) { - match event { - Event::Side(target) => self.target_side = target, - Event::Pick(endpoint) => { - if self.target_side { - self.target = Some(endpoint); - } else { - self.source = Some(endpoint); - } - } - Event::Expand(endpoint) => { - self.scope = endpoint.references; - self.scope.push(endpoint.occurrence); - self.search.clear(); - self.page = 0; - } - Event::Up => { - self.scope.pop(); - self.search.clear(); - self.page = 0; - } - Event::Root => { - self.scope.clear(); - self.search.clear(); - self.page = 0; - } - Event::Purpose(purpose) => self.purpose = purpose, - Event::Evidence(choice) => self.evidence = Some(choice.id), - Event::Guard(raw) => self.guard = raw, - Event::Search(raw) => { - self.search = raw; - self.page = 0; - } - Event::Page(page) => self.page = page, - } - } - fn purpose(&self) -> Result { - Ok(match self.purpose { - Purpose::Precedence => ContextualLinkPurpose::Precedence { - guard: self.guard.clone(), - }, - Purpose::Exchange => ContextualLinkPurpose::Exchange { - exchange: self - .evidence - .ok_or("Choose the exact exchange used for assembly.")?, - }, - Purpose::CommonWork => ContextualLinkPurpose::CommonWork { - work: self.evidence.ok_or("Choose the shared work identity.")?, - }, - }) - } - pub(super) fn command(&self, p: &Project, owner: Id, name: &str) -> Result { - let source = self - .source - .as_ref() - .ok_or("Choose the qualified source contribution.")?; - let target = self - .target - .as_ref() - .ok_or("Choose the qualified target contribution.")?; - let purpose = self.purpose()?; - TopologyIndex::new(p) - .validate_link(owner, source, target, &purpose) - .map_err(|e| e.to_string())?; - let mut relation = if let Some(id) = self.edit { - p.relations.get(&id).filter(|r| matches!(&r.body, RelationBody::ContextualLink { owner: caller, .. } if *caller==owner)).ok_or("This caller-owned link no longer exists.")?.clone() - } else { - let id = Id::new_v4(); - Relation { - id, - alias: p.fresh_alias("contextual_link", "relation", id), - name: self.purpose.to_string(), - description: String::new(), - body: RelationBody::ContextualLink { - owner, - source: source.clone(), - target: target.clone(), - purpose: purpose.clone(), - }, - } - }; - if !name.trim().is_empty() { - relation.name = name.to_owned(); - } - relation.body = RelationBody::ContextualLink { - owner, - source: source.clone(), - target: target.clone(), - purpose, - }; - let command = if self.edit.is_some() { - Command::EditRelation(relation.id, relation) - } else { - Command::CreateRelation(relation) - }; - execute_batch(p, std::slice::from_ref(&command)).map_err(|e| e.to_string())?; - Ok(command) - } -} - -pub(crate) fn address_label(p: &Project, owner: Id, endpoint: &OccurrenceEndpoint) -> String { - std::iter::once(&owner) - .chain(endpoint.references.iter()) - .chain(std::iter::once(&endpoint.occurrence)) - .map(|id| { - p.elements - .get(id) - .map(|e| format!("{} (@{})", e.name, e.alias)) - .unwrap_or_else(|| format!("Unavailable {id}")) - }) - .collect::>() - .join(" → ") -} -struct AddressRow { - endpoint: OccurrenceEndpoint, - reference: bool, -} -fn rows(p: &Project, owner: Id, scope: &[Id], search: &str) -> Result, String> { - let index = TopologyIndex::new(p); - let sequence = index - .resolve_scope(owner, scope) - .map_err(|e| e.to_string())?; - let query = search.trim().to_lowercase(); - Ok(index - .contributions(sequence) - .into_iter() - .filter_map(|occurrence| { - let endpoint = OccurrenceEndpoint { - references: scope.to_vec(), - occurrence, - }; - if !address_label(p, owner, &endpoint) - .to_lowercase() - .contains(&query) - { - return None; - } - let reference = matches!( - p.elements.get(&occurrence).and_then(|e| e.shared()), - Some(SharedBody::Occurrence { - body: OccurrenceBody::SequenceReference { sequence: Some(_) }, - .. - }) - ); - Some(AddressRow { - endpoint, - reference, - }) - }) - .collect()) -} -fn event(value: Event) -> Message { - super::event(super::Event::Contextual(value)) -} - -pub(super) fn preview( - index: &TopologyIndex<'_>, - p: &Project, - owner: Id, - reference: Id, - expanded: &BTreeSet, - limit: usize, -) -> Vec { - let children = |endpoint: &OccurrenceEndpoint| { - if index.resolve(owner, endpoint).is_err() { - return vec![]; - } - let sequence = p - .elements - .get(&endpoint.occurrence) - .and_then(|e| e.shared()) - .and_then(|body| match body { - SharedBody::Occurrence { - body: OccurrenceBody::SequenceReference { sequence }, - .. - } => *sequence, - _ => None, - }); - let Some(sequence) = sequence else { - return vec![]; - }; - let available: BTreeSet<_> = index.contributions(sequence).into_iter().collect(); - let mut references = endpoint.references.clone(); - references.push(endpoint.occurrence); - p.elements - .get(&sequence) - .and_then(|e| e.steps()) - .into_iter() - .flatten() - .filter(|id| available.contains(id)) - .map(|occurrence| OccurrenceEndpoint { - references: references.clone(), - occurrence: *occurrence, - }) - .collect::>() - }; - let root = OccurrenceEndpoint { - references: vec![], - occurrence: reference, - }; - let mut pending = children(&root); - pending.reverse(); - let mut visible = vec![]; - while let Some(endpoint) = pending.pop() { - if visible.len() == limit { - break; - } - if index.resolve(owner, &endpoint).is_err() { - continue; - } - if expanded.contains(&endpoint) { - pending.extend(children(&endpoint).into_iter().rev()); - } - visible.push(endpoint); - } - visible -} -pub(super) fn view<'a>(p: &'a Project, owner: Id, d: &'a Draft) -> Element<'a, Message> { - let mut out = column![ - text("Choose each endpoint in its caller context. Enter a reference to browse all its contributions; repeated uses stay distinct. Browsing preserves both selected endpoints.").size(12), - row![button("Choose source").style(if !d.target_side { button::primary } else { style::nav }).on_press(event(Event::Side(false))), button("Choose target").style(if d.target_side { button::primary } else { style::nav }).on_press(event(Event::Side(true)))].spacing(6), - text(format!("Source: {}", d.source.as_ref().map(|a| address_label(p,owner,a)).unwrap_or("Choose a contribution".into()))).size(12), - text(format!("Target: {}", d.target.as_ref().map(|a| address_label(p,owner,a)).unwrap_or("Choose a contribution".into()))).size(12), - pick_list(vec![Purpose::Precedence,Purpose::Exchange,Purpose::CommonWork],Some(d.purpose),|p| event(Event::Purpose(p))).width(Fill).text_size(12).padding(8) - ].spacing(7); - match d.purpose { - Purpose::Precedence => { - out = out.push( - text_input("Precedence guard (optional)", &d.guard) - .on_input(|v| event(Event::Guard(v))) - .padding(8) - .size(12), - ) - } - purpose => { - let choices = if purpose == Purpose::Exchange { - targets(p, owner, Tool::Exchange) - } else { - targets(p, owner, Tool::Work) - }; - let selected = d.evidence.map(|id| Choice { - id, - label: p.named(id).to_owned(), - }); - out = out - .push( - pick_list(choices, selected, |c| event(Event::Evidence(c))) - .placeholder("Choose exact engineering identity…") - .width(Fill) - .text_size(12) - .padding(8), - ) - .push( - text("Assembly incidence does not imply execution order or fuse executions.") - .size(11), - ); - } - } - out = out.push( - text_input("Search all contributions in this scope…", &d.search) - .on_input(|v| event(Event::Search(v))) - .padding(7) - .size(12), - ); - let scope_label = std::iter::once(&owner) - .chain(d.scope.iter()) - .map(|id| { - p.elements - .get(id) - .map(|e| format!("{} (@{})", e.name, e.alias)) - .unwrap_or_else(|| format!("Unavailable {id}")) - }) - .collect::>() - .join(" → "); - out = out - .push(text(format!("Browsing: {scope_label}")).size(12)) - .push( - row![ - button("Root").on_press_maybe((!d.scope.is_empty()).then(|| event(Event::Root))), - button("Up one reference") - .on_press_maybe((!d.scope.is_empty()).then(|| event(Event::Up))) - ] - .spacing(6), - ); - let rows = match rows(p, owner, &d.scope, &d.search) { - Ok(rows) => rows, - Err(error) => return out.push(text(error).size(12)).into(), - }; - let range = super::view::choice_page(rows.len(), d.page); - let page = range.start / 24; - out = out.push( - row![ - button("Previous").on_press_maybe((page > 0).then(|| event(Event::Page(page - 1)))), - text(format!( - "{} matching contributions · page {}", - rows.len(), - page + 1 - )) - .size(11), - button("Next") - .on_press_maybe((range.end < rows.len()).then(|| event(Event::Page(page + 1)))) - ] - .spacing(6), - ); - for r in &rows[range] { - let mut line = row![ - button(text(address_label(p, owner, &r.endpoint)).size(12)) - .width(Fill) - .on_press(event(Event::Pick(r.endpoint.clone()))) - ] - .spacing(5); - if r.reference { - line = line - .push(button("Enter reference").on_press(event(Event::Expand(r.endpoint.clone())))); - } - out = out.push(line); - } - out.into() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::document::Document; - use oa_core::source_project::ProjectLimits; - - fn fixture(layer: Layer) -> (Project, Id, Id, Id, Id, Id) { - let f = super::super::direct_tests::fixture(layer); - let mut p = f.p; - let sequence = p - .make_element( - if layer == Layer::Oa { - Kind::Process - } else { - Kind::FunctionalChain - }, - layer, - "Reusable check", - None, - ) - .unwrap(); - let sequence_id = sequence.id; - p = execute_batch(&p, &[Command::CreateElement(sequence)]).unwrap(); - let leaf = occurrence( - &p, - sequence_id, - "Verify pressure", - OccurrenceBody::Action { - action: Some(f.work[0]), - lifeline: None, - }, - ); - let leaf_id = leaf.id; - p = execute_batch(&p, &[Command::CreateElement(leaf)]).unwrap(); - let a = occurrence( - &p, - f.owner, - "First check", - OccurrenceBody::SequenceReference { - sequence: Some(sequence_id), - }, - ); - let b = occurrence( - &p, - f.owner, - "Second check", - OccurrenceBody::SequenceReference { - sequence: Some(sequence_id), - }, - ); - let (a_id, b_id) = (a.id, b.id); - p = execute_batch(&p, &[Command::CreateElement(a), Command::CreateElement(b)]).unwrap(); - (p, f.owner, a_id, b_id, leaf_id, f.work[0]) - } - #[test] - fn qualified_link_gui_repeated_sources_remain_distinct_and_apply_has_exact_source_history() { - for layer in [Layer::Oa, Layer::Sa] { - let (p, owner, a, b, leaf, work) = fixture(layer); - let mut state = State::default(); - state - .update( - &p, - super::super::Event::Start(owner, Tool::Contextual, None), - ) - .unwrap(); - let source = OccurrenceEndpoint { - references: vec![a], - occurrence: leaf, - }; - let target = OccurrenceEndpoint { - references: vec![b], - occurrence: leaf, - }; - for event in [ - Event::Pick(source.clone()), - Event::Side(true), - Event::Pick(target.clone()), - Event::Purpose(Purpose::CommonWork), - Event::Evidence(Choice { - id: work, - label: String::new(), - }), - ] { - state - .update(&p, super::super::Event::Contextual(event)) - .unwrap(); - } - let raw = state.recovery_snapshot().unwrap().unwrap(); - let restored = State::from_recovery(raw.clone()).unwrap(); - assert_eq!(restored.recovery_snapshot().unwrap(), Some(raw)); - let command = restored.prepare_pending(&p).unwrap(); - let Command::CreateRelation(record) = &command else { - panic!("one caller-owned record") - }; - assert_eq!( - record.body, - RelationBody::ContextualLink { - owner, - source: source.clone(), - target: target.clone(), - purpose: ContextualLinkPurpose::CommonWork { work } - } - ); - let expected = execute_batch(&p, std::slice::from_ref(&command)).unwrap(); - let mut doc = Document::from_project(p.clone(), ProjectLimits::default()).unwrap(); - let before = doc.bundle().clone(); - doc.execute(command.clone()).unwrap(); - assert_eq!(doc.project(), &expected); - assert_eq!( - doc.project().elements, - p.elements, - "no copied work or source occurrences" - ); - let after = doc.bundle().clone(); - let reopened = Document::from_bundle(after.clone(), ProjectLimits::default()).unwrap(); - assert_eq!(reopened.project(), &expected); - assert!(doc.undo()); - assert_eq!(doc.bundle(), &before); - assert!(doc.redo()); - assert_eq!(doc.bundle(), &after); - assert_ne!( - address_label(&p, owner, &source), - address_label(&p, owner, &target) - ); - assert!( - Graph::new(&expected, owner, record.id) - .edges - .iter() - .any(|edge| edge.id == record.id && edge.incidence) - ); - } - } - #[test] - fn qualified_link_gui_rejected_endpoint_keeps_exact_raw_intent_after_recovery() { - let (p, owner, a, _, leaf, _) = fixture(Layer::Oa); - let mut state = State::default(); - state - .update( - &p, - super::super::Event::Start(owner, Tool::Contextual, None), - ) - .unwrap(); - for event in [ - Event::Pick(OccurrenceEndpoint { - references: vec![a], - occurrence: leaf, - }), - Event::Side(true), - Event::Pick(OccurrenceEndpoint { - references: vec![Id::new_v4()], - occurrence: leaf, - }), - Event::Guard(" pressure > && ".into()), - ] { - state - .update(&p, super::super::Event::Contextual(event)) - .unwrap(); - } - let before = state.recovery_snapshot().unwrap(); - assert!(state.prepare_pending(&p).is_err()); - assert_eq!(state.recovery_snapshot().unwrap(), before); - let restored = State::from_recovery(before.clone().unwrap()).unwrap(); - assert!(restored.prepare_pending(&p).is_err()); - assert_eq!(restored.recovery_snapshot().unwrap(), before); - } - #[test] - fn qualified_link_gui_deleted_transient_selection_does_not_seed_or_hide_retained_authored_endpoint() - { - let (p, owner, reference, _, leaf, _) = fixture(Layer::Oa); - let endpoint = OccurrenceEndpoint { - references: vec![reference], - occurrence: leaf, - }; - let mut state = State::default(); - state.select_context(oa_core::topology_context::ContextAddress { - owner, - endpoint: endpoint.clone(), - }); - state - .update( - &p, - super::super::Event::Start(owner, Tool::Contextual, None), - ) - .unwrap(); - let before = state.recovery_snapshot().unwrap(); - let deleted = execute_batch(&p, &[Command::Delete(leaf)]).unwrap(); - assert!(state.qualified_selection(&deleted, owner).is_none()); - assert_eq!( - state.recovery_snapshot().unwrap(), - before, - "Authored endpoint is retained for explicit repair" - ); - assert!(state.prepare_pending(&deleted).is_err()); - state.remember(owner, reference); - assert_eq!(state.back(&deleted), Some((owner, reference))); - assert!(state.selected_context.is_none()); - state.draft = None; - state - .update( - &deleted, - super::super::Event::Start(owner, Tool::Contextual, None), - ) - .unwrap(); - assert!( - state - .draft - .as_ref() - .unwrap() - .contextual - .as_ref() - .unwrap() - .source - .is_none() - ); - } - #[test] - fn qualified_link_gui_scope_paging_and_search_reach_tail_beyond_512_without_losing_endpoints() { - let (p, owner, reference, _, leaf, _) = fixture(Layer::Oa); - let sequence = p.elements[&leaf].parent.unwrap(); - let mut commands = Vec::new(); - let mut tails = Vec::new(); - for (scope_index, scope) in [owner, sequence].into_iter().enumerate() { - for n in 0..520 { - let mut record = occurrence( - &p, - scope, - &format!("Contribution {n:04}"), - OccurrenceBody::Control { - kind: ControlKind::Choice, - }, - ); - record.alias = format!("scope_{scope_index}_contribution_{n:04}"); - if n == 519 { - tails.push(record.id); - } - commands.push(Command::CreateElement(record)); - } - } - let p = execute_batch(&p, &commands).unwrap(); - let all = rows(&p, owner, &[], "").unwrap(); - assert!(all.len() > 512); - let last_page = super::super::view::choice_page(all.len(), usize::MAX); - assert!( - all[last_page] - .iter() - .any(|row| row.endpoint.occurrence == tails[0]) - ); - let found = rows(&p, owner, &[], "Contribution 0519").unwrap(); - assert_eq!(found.len(), 1); - assert_eq!(found[0].endpoint.occurrence, tails[0]); - let mut draft = Draft::default(); - draft.update(Event::Pick(OccurrenceEndpoint { - references: vec![reference], - occurrence: leaf, - })); - draft.update(Event::Side(true)); - draft.update(Event::Pick(found[0].endpoint.clone())); - let chosen = (draft.source.clone(), draft.target.clone()); - draft.update(Event::Expand(OccurrenceEndpoint { - references: vec![], - occurrence: reference, - })); - draft.update(Event::Search("Contribution 0519".into())); - let nested = rows(&p, owner, &draft.scope, &draft.search).unwrap(); - assert_eq!(nested.len(), 1); - assert_eq!( - nested[0].endpoint, - OccurrenceEndpoint { - references: vec![reference], - occurrence: tails[1] - } - ); - let saved = serde_json::to_value(&draft).unwrap(); - let mut restored: Draft = serde_json::from_value(saved.clone()).unwrap(); - assert_eq!(serde_json::to_value(&restored).unwrap(), saved); - restored.update(Event::Up); - assert!(restored.scope.is_empty()); - assert_eq!((restored.source.clone(), restored.target.clone()), chosen); - assert!(restored.command(&p, owner, "Tail constraint").is_ok()); - let mut historical = saved; - historical.as_object_mut().unwrap().remove("scope"); - let restored: Draft = serde_json::from_value(historical).unwrap(); - assert!(restored.scope.is_empty()); - assert_eq!((restored.source, restored.target), chosen); - } - #[test] - fn qualified_link_gui_nested_preview_folds_only_the_exact_reference_instance() { - let (mut p, owner, a, b, leaf, _) = fixture(Layer::Oa); - let source = p.elements[&leaf].parent.unwrap(); - let deep = p - .make_element(Kind::Process, Layer::Oa, "Nested definition", None) - .unwrap(); - let deep_id = deep.id; - p = execute_batch(&p, &[Command::CreateElement(deep)]).unwrap(); - let deep_leaf = occurrence( - &p, - deep_id, - "Nested control", - OccurrenceBody::Control { - kind: ControlKind::Choice, - }, - ); - let deep_leaf_id = deep_leaf.id; - let nested = occurrence( - &p, - source, - "Nested use", - OccurrenceBody::SequenceReference { - sequence: Some(deep_id), - }, - ); - let nested_id = nested.id; - p = execute_batch( - &p, - &[ - Command::CreateElement(deep_leaf), - Command::CreateElement(nested), - ], - ) - .unwrap(); - let path = OccurrenceEndpoint { - references: vec![a], - occurrence: nested_id, - }; - let mut state = State::default(); - assert!(state.expand_context(&p, owner, path.clone())); - state.expanded.insert(b); - let graph = Graph::expanded_qualified( - &p, - owner, - owner, - &state.expanded, - &state.contextual_expanded, - ); - assert!( - graph - .nodes - .iter() - .find(|n| n.id == a) - .unwrap() - .preview - .iter() - .any(|(v, _)| v.references == vec![a, nested_id] && v.occurrence == deep_leaf_id) - ); - assert!( - !graph - .nodes - .iter() - .find(|n| n.id == b) - .unwrap() - .preview - .iter() - .any(|(v, _)| v.occurrence == deep_leaf_id) - ); - assert!(state.expand_context(&p, owner, path)); - let graph = Graph::expanded_qualified( - &p, - owner, - owner, - &state.expanded, - &state.contextual_expanded, - ); - assert!( - !graph - .nodes - .iter() - .any(|n| n.preview.iter().any(|(v, _)| v.occurrence == deep_leaf_id)) - ); - } -} +//! Lazy qualified authoring over the core address resolver. A folded reference +//! never replaces its retained endpoint with the reference or a same-named leaf. +use super::*; +use iced::widget::column; +use oa_core::topology_context::TopologyIndex; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)] +pub enum Purpose { + #[default] + Precedence, + Exchange, + CommonWork, +} +impl std::fmt::Display for Purpose { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Precedence => "Precedence", + Self::Exchange => "Exchange assembly", + Self::CommonWork => "Common work incidence", + }) + } +} +#[derive(Debug, Clone)] +pub enum Event { + Side(bool), + Pick(OccurrenceEndpoint), + Expand(OccurrenceEndpoint), + Up, + Root, + Purpose(Purpose), + Evidence(Choice), + Guard(String), + Search(String), + Page(usize), +} +#[derive(Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub(super) struct Draft { + edit: Option, + source: Option, + target: Option, + target_side: bool, + purpose: Purpose, + evidence: Option, + guard: String, + search: String, + page: usize, + #[serde(default)] + scope: Vec, +} +impl Draft { + pub(super) fn seed_source(&mut self, endpoint: OccurrenceEndpoint) { + if self.edit.is_none() { + self.source = Some(endpoint); + } + } + pub(super) fn new(p: &Project, owner: Id, selected: Option) -> Self { + if let Some(r) = selected.and_then(|id| p.relations.get(&id)) + && let RelationBody::ContextualLink { + owner: caller, + source, + target, + purpose, + } = &r.body + && *caller == owner + { + let (purpose, evidence, guard) = match purpose { + ContextualLinkPurpose::Precedence { guard } => { + (Purpose::Precedence, None, guard.clone()) + } + ContextualLinkPurpose::Exchange { exchange } => { + (Purpose::Exchange, Some(*exchange), String::new()) + } + ContextualLinkPurpose::CommonWork { work } => { + (Purpose::CommonWork, Some(*work), String::new()) + } + }; + return Self { + edit: Some(r.id), + source: Some(source.clone()), + target: Some(target.clone()), + purpose, + evidence, + guard, + ..Self::default() + }; + } + Self { + source: selected + .filter(|id| is_contribution(p, owner, *id)) + .map(|occurrence| OccurrenceEndpoint { + references: vec![], + occurrence, + }), + ..Self::default() + } + } + pub(super) fn update(&mut self, event: Event) { + match event { + Event::Side(target) => self.target_side = target, + Event::Pick(endpoint) => { + if self.target_side { + self.target = Some(endpoint); + } else { + self.source = Some(endpoint); + } + } + Event::Expand(endpoint) => { + self.scope = endpoint.references; + self.scope.push(endpoint.occurrence); + self.search.clear(); + self.page = 0; + } + Event::Up => { + self.scope.pop(); + self.search.clear(); + self.page = 0; + } + Event::Root => { + self.scope.clear(); + self.search.clear(); + self.page = 0; + } + Event::Purpose(purpose) => self.purpose = purpose, + Event::Evidence(choice) => self.evidence = Some(choice.id), + Event::Guard(raw) => self.guard = raw, + Event::Search(raw) => { + self.search = raw; + self.page = 0; + } + Event::Page(page) => self.page = page, + } + } + fn purpose(&self) -> Result { + Ok(match self.purpose { + Purpose::Precedence => ContextualLinkPurpose::Precedence { + guard: self.guard.clone(), + }, + Purpose::Exchange => ContextualLinkPurpose::Exchange { + exchange: self + .evidence + .ok_or("Choose the exact exchange used for assembly.")?, + }, + Purpose::CommonWork => ContextualLinkPurpose::CommonWork { + work: self.evidence.ok_or("Choose the shared work identity.")?, + }, + }) + } + pub(super) fn command(&self, p: &Project, owner: Id, name: &str) -> Result { + let source = self + .source + .as_ref() + .ok_or("Choose the qualified source contribution.")?; + let target = self + .target + .as_ref() + .ok_or("Choose the qualified target contribution.")?; + let purpose = self.purpose()?; + TopologyIndex::new(p) + .validate_link(owner, source, target, &purpose) + .map_err(|e| e.to_string())?; + let mut relation = if let Some(id) = self.edit { + p.relations.get(&id).filter(|r| matches!(&r.body, RelationBody::ContextualLink { owner: caller, .. } if *caller==owner)).ok_or("This caller-owned link no longer exists.")?.clone() + } else { + let id = Id::new_v4(); + Relation { + id, + alias: p.fresh_alias("contextual_link", "relation", id), + name: self.purpose.to_string(), + description: String::new(), + body: RelationBody::ContextualLink { + owner, + source: source.clone(), + target: target.clone(), + purpose: purpose.clone(), + }, + } + }; + if !name.trim().is_empty() { + relation.name = name.to_owned(); + } + relation.body = RelationBody::ContextualLink { + owner, + source: source.clone(), + target: target.clone(), + purpose, + }; + let command = if self.edit.is_some() { + Command::EditRelation(relation.id, relation) + } else { + Command::CreateRelation(relation) + }; + execute_batch(p, std::slice::from_ref(&command)).map_err(|e| e.to_string())?; + Ok(command) + } +} + +pub(crate) fn address_label(p: &Project, owner: Id, endpoint: &OccurrenceEndpoint) -> String { + std::iter::once(&owner) + .chain(endpoint.references.iter()) + .chain(std::iter::once(&endpoint.occurrence)) + .map(|id| { + p.elements + .get(id) + .map(|e| format!("{} (@{})", e.name, e.alias)) + .unwrap_or_else(|| format!("Unavailable {id}")) + }) + .collect::>() + .join(" → ") +} +struct AddressRow { + endpoint: OccurrenceEndpoint, + reference: bool, +} +fn rows(p: &Project, owner: Id, scope: &[Id], search: &str) -> Result, String> { + let index = TopologyIndex::new(p); + let sequence = index + .resolve_scope(owner, scope) + .map_err(|e| e.to_string())?; + let query = search.trim().to_lowercase(); + Ok(index + .contributions(sequence) + .into_iter() + .filter_map(|occurrence| { + let endpoint = OccurrenceEndpoint { + references: scope.to_vec(), + occurrence, + }; + if !address_label(p, owner, &endpoint) + .to_lowercase() + .contains(&query) + { + return None; + } + let reference = matches!( + p.elements.get(&occurrence).and_then(|e| e.shared()), + Some(SharedBody::Occurrence { + body: OccurrenceBody::SequenceReference { sequence: Some(_) }, + .. + }) + ); + Some(AddressRow { + endpoint, + reference, + }) + }) + .collect()) +} +fn event(value: Event) -> Message { + super::event(super::Event::Contextual(value)) +} + +pub(super) fn preview( + index: &TopologyIndex<'_>, + p: &Project, + owner: Id, + reference: Id, + expanded: &BTreeSet, + limit: usize, +) -> Vec { + let children = |endpoint: &OccurrenceEndpoint| { + if index.resolve(owner, endpoint).is_err() { + return vec![]; + } + let sequence = p + .elements + .get(&endpoint.occurrence) + .and_then(|e| e.shared()) + .and_then(|body| match body { + SharedBody::Occurrence { + body: OccurrenceBody::SequenceReference { sequence }, + .. + } => *sequence, + _ => None, + }); + let Some(sequence) = sequence else { + return vec![]; + }; + let available: BTreeSet<_> = index.contributions(sequence).into_iter().collect(); + let mut references = endpoint.references.clone(); + references.push(endpoint.occurrence); + p.elements + .get(&sequence) + .and_then(|e| e.steps()) + .into_iter() + .flatten() + .filter(|id| available.contains(id)) + .map(|occurrence| OccurrenceEndpoint { + references: references.clone(), + occurrence: *occurrence, + }) + .collect::>() + }; + let root = OccurrenceEndpoint { + references: vec![], + occurrence: reference, + }; + let mut pending = children(&root); + pending.reverse(); + let mut visible = vec![]; + while let Some(endpoint) = pending.pop() { + if visible.len() == limit { + break; + } + if index.resolve(owner, &endpoint).is_err() { + continue; + } + if expanded.contains(&endpoint) { + pending.extend(children(&endpoint).into_iter().rev()); + } + visible.push(endpoint); + } + visible +} +pub(super) fn view<'a>(p: &'a Project, owner: Id, d: &'a Draft) -> Element<'a, Message> { + let mut out = column![ + text("Choose each endpoint in its caller context. Enter a reference to browse all its contributions; repeated uses stay distinct. Browsing preserves both selected endpoints.").size(12), + row![button("Choose source").style(if !d.target_side { button::primary } else { style::nav }).on_press(event(Event::Side(false))), button("Choose target").style(if d.target_side { button::primary } else { style::nav }).on_press(event(Event::Side(true)))].spacing(6), + text(format!("Source: {}", d.source.as_ref().map(|a| address_label(p,owner,a)).unwrap_or("Choose a contribution".into()))).size(12), + text(format!("Target: {}", d.target.as_ref().map(|a| address_label(p,owner,a)).unwrap_or("Choose a contribution".into()))).size(12), + pick_list(vec![Purpose::Precedence,Purpose::Exchange,Purpose::CommonWork],Some(d.purpose),|p| event(Event::Purpose(p))).width(Fill).text_size(12).padding(8) + ].spacing(7); + match d.purpose { + Purpose::Precedence => { + out = out.push( + text_input("Precedence guard (optional)", &d.guard) + .on_input(|v| event(Event::Guard(v))) + .padding(8) + .size(12), + ) + } + purpose => { + let choices = if purpose == Purpose::Exchange { + targets(p, owner, Tool::Exchange) + } else { + targets(p, owner, Tool::Work) + }; + let selected = d.evidence.map(|id| Choice { + id, + label: p.named(id).to_owned(), + }); + out = out + .push( + pick_list(choices, selected, |c| event(Event::Evidence(c))) + .placeholder("Choose exact engineering identity…") + .width(Fill) + .text_size(12) + .padding(8), + ) + .push( + text("Assembly incidence does not imply execution order or fuse executions.") + .size(11), + ); + } + } + out = out.push( + text_input("Search all contributions in this scope…", &d.search) + .on_input(|v| event(Event::Search(v))) + .padding(7) + .size(12), + ); + let scope_label = std::iter::once(&owner) + .chain(d.scope.iter()) + .map(|id| { + p.elements + .get(id) + .map(|e| format!("{} (@{})", e.name, e.alias)) + .unwrap_or_else(|| format!("Unavailable {id}")) + }) + .collect::>() + .join(" → "); + out = out + .push(text(format!("Browsing: {scope_label}")).size(12)) + .push( + row![ + button("Root").on_press_maybe((!d.scope.is_empty()).then(|| event(Event::Root))), + button("Up one reference") + .on_press_maybe((!d.scope.is_empty()).then(|| event(Event::Up))) + ] + .spacing(6), + ); + let rows = match rows(p, owner, &d.scope, &d.search) { + Ok(rows) => rows, + Err(error) => return out.push(text(error).size(12)).into(), + }; + let range = super::view::choice_page(rows.len(), d.page); + let page = range.start / 24; + out = out.push( + row![ + button("Previous").on_press_maybe((page > 0).then(|| event(Event::Page(page - 1)))), + text(format!( + "{} matching contributions · page {}", + rows.len(), + page + 1 + )) + .size(11), + button("Next") + .on_press_maybe((range.end < rows.len()).then(|| event(Event::Page(page + 1)))) + ] + .spacing(6), + ); + for r in &rows[range] { + let mut line = row![ + button(text(address_label(p, owner, &r.endpoint)).size(12)) + .width(Fill) + .on_press(event(Event::Pick(r.endpoint.clone()))) + ] + .spacing(5); + if r.reference { + line = line + .push(button("Enter reference").on_press(event(Event::Expand(r.endpoint.clone())))); + } + out = out.push(line); + } + out.into() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::document::Document; + use oa_core::source_project::ProjectLimits; + + fn fixture(layer: Layer) -> (Project, Id, Id, Id, Id, Id) { + let f = super::super::direct_tests::fixture(layer); + let mut p = f.p; + let sequence = p + .make_element( + if layer == Layer::Oa { + Kind::Process + } else { + Kind::FunctionalChain + }, + layer, + "Reusable check", + None, + ) + .unwrap(); + let sequence_id = sequence.id; + p = execute_batch(&p, &[Command::CreateElement(sequence)]).unwrap(); + let leaf = occurrence( + &p, + sequence_id, + "Verify pressure", + OccurrenceBody::Action { + action: Some(f.work[0]), + lifeline: None, + }, + ); + let leaf_id = leaf.id; + p = execute_batch(&p, &[Command::CreateElement(leaf)]).unwrap(); + let a = occurrence( + &p, + f.owner, + "First check", + OccurrenceBody::SequenceReference { + sequence: Some(sequence_id), + }, + ); + let b = occurrence( + &p, + f.owner, + "Second check", + OccurrenceBody::SequenceReference { + sequence: Some(sequence_id), + }, + ); + let (a_id, b_id) = (a.id, b.id); + p = execute_batch(&p, &[Command::CreateElement(a), Command::CreateElement(b)]).unwrap(); + (p, f.owner, a_id, b_id, leaf_id, f.work[0]) + } + #[test] + fn qualified_link_gui_repeated_sources_remain_distinct_and_apply_has_exact_source_history() { + for layer in [Layer::Oa, Layer::Sa] { + let (p, owner, a, b, leaf, work) = fixture(layer); + let mut state = State::default(); + state + .update( + &p, + super::super::Event::Start(owner, Tool::Contextual, None), + ) + .unwrap(); + let source = OccurrenceEndpoint { + references: vec![a], + occurrence: leaf, + }; + let target = OccurrenceEndpoint { + references: vec![b], + occurrence: leaf, + }; + for event in [ + Event::Pick(source.clone()), + Event::Side(true), + Event::Pick(target.clone()), + Event::Purpose(Purpose::CommonWork), + Event::Evidence(Choice { + id: work, + label: String::new(), + }), + ] { + state + .update(&p, super::super::Event::Contextual(event)) + .unwrap(); + } + let raw = state.recovery_snapshot().unwrap().unwrap(); + let restored = State::from_recovery(raw.clone()).unwrap(); + assert_eq!(restored.recovery_snapshot().unwrap(), Some(raw)); + let command = restored.prepare_pending(&p).unwrap(); + let Command::CreateRelation(record) = &command else { + panic!("one caller-owned record") + }; + assert_eq!( + record.body, + RelationBody::ContextualLink { + owner, + source: source.clone(), + target: target.clone(), + purpose: ContextualLinkPurpose::CommonWork { work } + } + ); + let expected = execute_batch(&p, std::slice::from_ref(&command)).unwrap(); + let mut doc = Document::from_project(p.clone(), ProjectLimits::default()).unwrap(); + let before = doc.bundle().clone(); + doc.execute(command.clone()).unwrap(); + assert_eq!(doc.project(), &expected); + assert_eq!( + doc.project().elements, + p.elements, + "no copied work or source occurrences" + ); + let after = doc.bundle().clone(); + let reopened = Document::from_bundle(after.clone(), ProjectLimits::default()).unwrap(); + assert_eq!(reopened.project(), &expected); + assert!(doc.undo()); + assert_eq!(doc.bundle(), &before); + assert!(doc.redo()); + assert_eq!(doc.bundle(), &after); + assert_ne!( + address_label(&p, owner, &source), + address_label(&p, owner, &target) + ); + assert!( + Graph::new(&expected, owner, record.id) + .edges + .iter() + .any(|edge| edge.id == record.id && edge.incidence) + ); + } + } + #[test] + fn qualified_link_gui_rejected_endpoint_keeps_exact_raw_intent_after_recovery() { + let (p, owner, a, _, leaf, _) = fixture(Layer::Oa); + let mut state = State::default(); + state + .update( + &p, + super::super::Event::Start(owner, Tool::Contextual, None), + ) + .unwrap(); + for event in [ + Event::Pick(OccurrenceEndpoint { + references: vec![a], + occurrence: leaf, + }), + Event::Side(true), + Event::Pick(OccurrenceEndpoint { + references: vec![Id::new_v4()], + occurrence: leaf, + }), + Event::Guard(" pressure > && ".into()), + ] { + state + .update(&p, super::super::Event::Contextual(event)) + .unwrap(); + } + let before = state.recovery_snapshot().unwrap(); + assert!(state.prepare_pending(&p).is_err()); + assert_eq!(state.recovery_snapshot().unwrap(), before); + let restored = State::from_recovery(before.clone().unwrap()).unwrap(); + assert!(restored.prepare_pending(&p).is_err()); + assert_eq!(restored.recovery_snapshot().unwrap(), before); + } + #[test] + fn qualified_link_gui_deleted_transient_selection_does_not_seed_or_hide_retained_authored_endpoint() + { + let (p, owner, reference, _, leaf, _) = fixture(Layer::Oa); + let endpoint = OccurrenceEndpoint { + references: vec![reference], + occurrence: leaf, + }; + let mut state = State::default(); + state.select_context(oa_core::topology_context::ContextAddress { + owner, + endpoint: endpoint.clone(), + }); + state + .update( + &p, + super::super::Event::Start(owner, Tool::Contextual, None), + ) + .unwrap(); + let before = state.recovery_snapshot().unwrap(); + let deleted = execute_batch(&p, &[Command::Delete(leaf)]).unwrap(); + assert!(state.qualified_selection(&deleted, owner).is_none()); + assert_eq!( + state.recovery_snapshot().unwrap(), + before, + "Authored endpoint is retained for explicit repair" + ); + assert!(state.prepare_pending(&deleted).is_err()); + state.remember(owner, reference); + assert_eq!(state.back(&deleted), Some((owner, reference))); + assert!(state.selected_context.is_none()); + state.draft = None; + state + .update( + &deleted, + super::super::Event::Start(owner, Tool::Contextual, None), + ) + .unwrap(); + assert!( + state + .draft + .as_ref() + .unwrap() + .contextual + .as_ref() + .unwrap() + .source + .is_none() + ); + } + #[test] + fn qualified_link_gui_scope_paging_and_search_reach_tail_beyond_512_without_losing_endpoints() { + let (p, owner, reference, _, leaf, _) = fixture(Layer::Oa); + let sequence = p.elements[&leaf].parent.unwrap(); + let mut commands = Vec::new(); + let mut tails = Vec::new(); + for (scope_index, scope) in [owner, sequence].into_iter().enumerate() { + for n in 0..520 { + let mut record = occurrence( + &p, + scope, + &format!("Contribution {n:04}"), + OccurrenceBody::Control { + kind: ControlKind::Choice, + }, + ); + record.alias = format!("scope_{scope_index}_contribution_{n:04}"); + if n == 519 { + tails.push(record.id); + } + commands.push(Command::CreateElement(record)); + } + } + let p = execute_batch(&p, &commands).unwrap(); + let all = rows(&p, owner, &[], "").unwrap(); + assert!(all.len() > 512); + let last_page = super::super::view::choice_page(all.len(), usize::MAX); + assert!( + all[last_page] + .iter() + .any(|row| row.endpoint.occurrence == tails[0]) + ); + let found = rows(&p, owner, &[], "Contribution 0519").unwrap(); + assert_eq!(found.len(), 1); + assert_eq!(found[0].endpoint.occurrence, tails[0]); + let mut draft = Draft::default(); + draft.update(Event::Pick(OccurrenceEndpoint { + references: vec![reference], + occurrence: leaf, + })); + draft.update(Event::Side(true)); + draft.update(Event::Pick(found[0].endpoint.clone())); + let chosen = (draft.source.clone(), draft.target.clone()); + draft.update(Event::Expand(OccurrenceEndpoint { + references: vec![], + occurrence: reference, + })); + draft.update(Event::Search("Contribution 0519".into())); + let nested = rows(&p, owner, &draft.scope, &draft.search).unwrap(); + assert_eq!(nested.len(), 1); + assert_eq!( + nested[0].endpoint, + OccurrenceEndpoint { + references: vec![reference], + occurrence: tails[1] + } + ); + let saved = serde_json::to_value(&draft).unwrap(); + let mut restored: Draft = serde_json::from_value(saved.clone()).unwrap(); + assert_eq!(serde_json::to_value(&restored).unwrap(), saved); + restored.update(Event::Up); + assert!(restored.scope.is_empty()); + assert_eq!((restored.source.clone(), restored.target.clone()), chosen); + assert!(restored.command(&p, owner, "Tail constraint").is_ok()); + let mut historical = saved; + historical.as_object_mut().unwrap().remove("scope"); + let restored: Draft = serde_json::from_value(historical).unwrap(); + assert!(restored.scope.is_empty()); + assert_eq!((restored.source, restored.target), chosen); + } + #[test] + fn qualified_link_gui_nested_preview_folds_only_the_exact_reference_instance() { + let (mut p, owner, a, b, leaf, _) = fixture(Layer::Oa); + let source = p.elements[&leaf].parent.unwrap(); + let deep = p + .make_element(Kind::Process, Layer::Oa, "Nested definition", None) + .unwrap(); + let deep_id = deep.id; + p = execute_batch(&p, &[Command::CreateElement(deep)]).unwrap(); + let deep_leaf = occurrence( + &p, + deep_id, + "Nested control", + OccurrenceBody::Control { + kind: ControlKind::Choice, + }, + ); + let deep_leaf_id = deep_leaf.id; + let nested = occurrence( + &p, + source, + "Nested use", + OccurrenceBody::SequenceReference { + sequence: Some(deep_id), + }, + ); + let nested_id = nested.id; + p = execute_batch( + &p, + &[ + Command::CreateElement(deep_leaf), + Command::CreateElement(nested), + ], + ) + .unwrap(); + let path = OccurrenceEndpoint { + references: vec![a], + occurrence: nested_id, + }; + let mut state = State::default(); + assert!(state.expand_context(&p, owner, path.clone())); + state.expanded.insert(b); + let graph = Graph::expanded_qualified( + &p, + owner, + owner, + &state.expanded, + &state.contextual_expanded, + ); + assert!( + graph + .nodes + .iter() + .find(|n| n.id == a) + .unwrap() + .preview + .iter() + .any(|(v, _)| v.references == vec![a, nested_id] && v.occurrence == deep_leaf_id) + ); + assert!( + !graph + .nodes + .iter() + .find(|n| n.id == b) + .unwrap() + .preview + .iter() + .any(|(v, _)| v.occurrence == deep_leaf_id) + ); + assert!(state.expand_context(&p, owner, path)); + let graph = Graph::expanded_qualified( + &p, + owner, + owner, + &state.expanded, + &state.contextual_expanded, + ); + assert!( + !graph + .nodes + .iter() + .any(|n| n.preview.iter().any(|(v, _)| v.occurrence == deep_leaf_id)) + ); + } +} diff --git a/crates/oa-desktop/src/guide.rs b/crates/oa-desktop/src/guide.rs index 22ef0fa9..47c10b7c 100644 --- a/crates/oa-desktop/src/guide.rs +++ b/crates/oa-desktop/src/guide.rs @@ -1,1368 +1,1368 @@ -//! Offline engineering help. Topics describe the shared model and real UI controls. -//! -//! This module owns only reading/navigation state. Application navigation and demo -//! loading use normal App messages, so help never bypasses the model's draft guards. -use crate::{ - app::{Message, Page, Pending}, - style, -}; -use iced::{ - Element, Fill, Font, - widget::{button, column, container, keyed_column, row, scrollable, text, text_input}, -}; - -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub enum Topic { - #[default] - FirstProject, - Perspectives, - Participants, - Nesting, - Capabilities, - Exchanges, - Chains, - Scenarios, - States, - Interfaces, - Transitions, - SemanticReview, - Artifacts, - Source, - ModelChecks, - Recovery, - Files, - Server, -} - -#[derive(Debug, Clone, Default)] -pub struct State { - pub query: String, - pub topic: Topic, -} - -#[derive(Debug, Clone)] -pub enum Event { - Search(String), - Topic(Topic), -} - -pub(crate) const ARTICLE_SCROLL_ID: &str = "engineering-guide-article"; - -impl State { - /// Returns whether navigation selected a different article. - pub fn update(&mut self, event: Event) -> bool { - let previous = self.topic; - match event { - Event::Search(query) => { - self.query = query; - let matches = search(&self.query); - if !matches.contains(&self.topic) - && let Some(first) = matches.first() - { - self.topic = *first; - } - } - Event::Topic(topic) => { - self.topic = topic; - // Related/contextual links always reveal the requested topic. - self.query.clear(); - } - } - self.topic != previous - } -} - -impl Topic { - const ALL: [Self; 18] = [ - Self::FirstProject, - Self::Perspectives, - Self::Participants, - Self::Nesting, - Self::Capabilities, - Self::Exchanges, - Self::Chains, - Self::Scenarios, - Self::States, - Self::Interfaces, - Self::Transitions, - Self::SemanticReview, - Self::Artifacts, - Self::Source, - Self::ModelChecks, - Self::Recovery, - Self::Files, - Self::Server, - ]; - - pub fn title(self) -> &'static str { - match self { - Self::FirstProject => "Build your first model", - Self::Perspectives => "Operational & System Analysis", - Self::Participants => "Participants, roles & work", - Self::Nesting => "Ownership, allocation & views", - Self::Capabilities => "Missions & capabilities", - Self::Exchanges => "Exchanges & dataflow", - Self::Chains => "Processes & functional chains", - Self::Scenarios => "Scenarios & alternatives", - Self::States => "States, modes & transitions", - Self::Interfaces => "Data types, ports & interfaces", - Self::Transitions => "Move between OA & SA", - Self::SemanticReview => "Review semantic changes", - Self::Artifacts => "Connect engineering evidence", - Self::Source => "Textual editing & Git", - Self::ModelChecks => "Read model checks", - Self::Recovery => "Drafts & crash recovery", - Self::Files => "Files & keyboard shortcuts", - Self::Server => "Hosted models & REST API", - } - } - - fn category(self) -> &'static str { - match self { - Self::FirstProject | Self::Perspectives => "START HERE", - Self::Participants | Self::Nesting | Self::Capabilities | Self::Exchanges => { - "MODEL THE SYSTEM" - } - Self::Chains | Self::Scenarios | Self::States | Self::Interfaces => "DESCRIBE BEHAVIOR", - Self::Transitions | Self::SemanticReview | Self::Artifacts | Self::Source => { - "CONNECT YOUR WORK" - } - Self::ModelChecks | Self::Recovery | Self::Files | Self::Server => "REFERENCE", - } - } -} - -struct Article { - summary: &'static str, - keywords: &'static str, - sections: &'static [Section], - related: &'static [Topic], - destination: Option<(Page, &'static str)>, -} - -struct Section { - title: &'static str, - body: &'static str, - steps: &'static [&'static str], - code: Option<&'static str>, -} - -const fn explain(title: &'static str, body: &'static str) -> Section { - Section { - title, - body, - steps: &[], - code: None, - } -} - -const fn walkthrough(title: &'static str, steps: &'static [&'static str]) -> Section { - Section { - title, - body: "", - steps, - code: None, - } -} - -const fn example(title: &'static str, body: &'static str, code: &'static str) -> Section { - Section { - title, - body, - steps: &[], - code: Some(code), - } -} - -fn article(topic: Topic) -> Article { - match topic { - Topic::FirstProject => { - const { - Article { - summary: "Create a small operational model, then derive the system contribution. Use concrete names for work and keep your first diagram focused.", - keywords: "getting started tutorial beginner quick start new example rescue coordinator allocation", - sections: &[ - walkthrough( - "Create an operational context", - &[ - "Choose New in the top bar. Use Project properties to name the model and describe its purpose. Apply changes commits the property draft.", - "On Architecture, choose Create architecture view. Architecture · allocated work shows work inside its performer.", - "Expand the diagram's side palette to see tool names. Choose Entity, click its position in the diagram, enter Rescue coordinator and press Enter. Escape cancels the draft. Use Actor for an external participant such as a Vessel in distress. Right-clicking a blank area also offers Create Entity here.", - "Right-click Rescue coordinator and choose Add activity. Enter Assess incident and press Enter to create the activity and its performer allocation together. Ordinary Activity creation in the palette leaves work unallocated; use + Relationship with Allocation to assign existing work. The allocated-work view shows work inside its performer.", - "Use Add existing to place an element that is already in the model. The initial choices match this view; no search is required. Include already shown offers Locate for elements already placed here.", - "Add a second activity for another participant. Select an activity, choose + Relationship, select Interaction and choose the compatible target. Name the exchange after what is conveyed.", - "Visit Model checks to review unfinished work. Save all writes the accepted model and applied drafts to your chosen file.", - ], - ), - explain( - "Continue into system design", - "Use Create System counterpart on an operational element to create a linked SA element. Choose System for the system under study or System actor for an external participant, then choose the result diagram. The original operational element stays in OA. The transition tutorial explains scope and reuse.", - ), - walkthrough( - "Build a reviewable OA → SA baseline", - &[ - "State the mission and capability: for example, protect life at sea by coordinating a rescue. Link the mission to the capability and identify the participants that support it.", - "Describe the normal operational Process and an exception Scenario using the activities and Interactions already in the architecture. Give the scenario an explicit Context capability and connect the process with Chain involvement.", - "Review which operational work the System will support. Create or reuse its SA counterparts, then check the resulting System boundary, external actors, leaf Function allocations and exchanged payloads.", - "Refine the system Functional chain, Scenario and State machine. Specify data contracts and entry/do/exit work where they clarify responsibilities. Link requirements, analyses or tests to the particular records they support.", - "Read Model checks, review changes through counterpart traces, and inspect both perspectives. Save the complete source project and review its Git diff as one engineering milestone.", - ], - ), - explain( - "Explore a worked model", - "Open the coastal rescue example to inspect allocation, decomposition, repeated scenario messages, operating modes, typed interfaces and artifact references. Loading the example uses the normal unsaved-work prompt. It replaces the current project only after that workflow completes.", - ), - ], - related: &[ - Topic::Perspectives, - Topic::Participants, - Topic::Nesting, - Topic::Transitions, - Topic::ModelChecks, - ], - destination: Some((Page::Diagram, "Open Architecture")), - } - } - } - Topic::Perspectives => { - const { - Article { - summary: "Operational Analysis asks what people and organizations need to accomplish. System Analysis asks what the system must do within that operation.", - keywords: "oa sa arcadia capella system architecture operational need boundary abstraction actor system of interest", - sections: &[ - explain( - "Operational Analysis: understand the need", - "Describe entities and actors, their activities, roles, interactions and operational capabilities without assuming a particular technical solution. An activity such as Assess incident states work in the operation; it does not imply which software module implements it.", - ), - explain( - "System Analysis: define the contribution", - "Describe one System under study, external System actors, Functions, functional exchanges, missions and system capabilities. A participant's operational activity may be supported by several system functions or may remain outside the system boundary. Review that engineering decision explicitly.", - ), - explain( - "Switching perspective does not transition the model", - "The 01 Operational and 02 System controls change the visible perspective and creation choices. To represent an element in the other layer, use Create System counterpart or Create Operational counterpart. Both elements remain linked by a trace.", - ), - explain( - "Keep the boundary explicit", - "Separate work performed by the System from work performed by external actors. Allocate leaf functions to the responsible participant. Logical and Physical Architecture belong to later design layers; a system-level function is not a software class or a physical component.", - ), - ], - related: &[Topic::FirstProject, Topic::Capabilities, Topic::Transitions], - destination: Some((Page::Catalog, "Open Model explorer")), - } - } - } - Topic::Participants => { - const { - Article { - summary: "Represent who participates separately from the work they perform. Decomposition and responsibility answer different engineering questions.", - keywords: "entity actor participant role realization assignment leaf activity function performer decomposition responsibility", - sections: &[ - walkthrough( - "Create and allocate work", - &[ - "Choose the Operational or System perspective. Create participants using Entity/Actor in OA or System/System actor in SA.", - "In the diagram's side palette, choose Activity in OA or Function in SA, click its position, enter a name and press Enter. Escape cancels without creating an element. Select a card and press F2 to rename it in place. For semantic decomposition, use Model explorer, select existing work and use + In selected to create smaller work units.", - "Right-click a participant and choose Add activity or Add function to create new work and its allocation together. To assign existing leaf work, use + Relationship with Allocation from the participant to the work. Model explorer also offers + Allocated when a participant and the appropriate work kind are selected.", - "Choose Architecture · allocated work to review responsibilities. Choose Functional dataflow to review the activity/function hierarchy and its exchanges independently.", - ], - ), - explain( - "Operational roles", - "A Role describes a responsibility independently of a particular entity. Role realization links an Entity or Actor to a Role. Role assignment links a Role to an Activity. These relations express role responsibilities; an Allocation identifies the concrete performer of leaf work.", - ), - explain( - "Name work as an action", - "Prefer Assess incident or Deliver electrical power for work, and Rescue coordinator or Power controller for participants. A meaningful description records assumptions, input/output expectations and the decision behind an allocation.", - ), - explain( - "Creation choices are constrained", - "+ In selected is enabled only for a legal semantic parent. + Allocated appears when the selected participant can perform the chosen work. Pickers show compatible endpoints. If a control is unavailable, check the active perspective, selected owner and element kind.", - ), - ], - related: &[Topic::Nesting, Topic::Exchanges, Topic::Capabilities], - destination: Some((Page::Catalog, "Open Model explorer")), - } - } - } - Topic::Nesting => { - const { - Article { - summary: "A diagram is a view of the model. Its boxes, positions and nesting reveal selected relationships; moving a box does not redefine engineering ownership.", - keywords: "containment semantic parent hierarchy nested nesting allocation deployment diagram placement arrange fit fold hide zoom resize routing connection bend waypoint detour label reset selective visibility category categories coverage process context duplicate copy items", - sections: &[ - explain( - "Three things to distinguish", - "Semantic parent records ownership: an activity contains subactivities, or a scenario owns occurrences. Allocation records who performs leaf work. A placement records where one element appears in a diagram. One element can have placements in several views without becoming several semantic elements.", - ), - walkthrough( - "Browse a large model", - &[ - "Open Model explorer. Choose a group, then a container to see its direct children. Breadcrumbs return to its owner or group; Allocated work is a separate list of responsibilities.", - "Search names, kinds or @aliases in the left outline. Matching elements retain their ancestor context; the main browser lists up to 100 results per page. Select a result to open its details and clear the search.", - "Use Collapse all to fold the outline and Locate to reveal the selected element. Search keeps matching ancestry open; clear the search before folding individual groups or containers.", - ], - ), - walkthrough( - "Choose and edit the relationship you mean", - &[ - "Use the diagram selector or + View to work with a focused diagram. View properties edits its name, perspective, projection and Saved view visibility. Apply changes commits the view settings.", - "Choose Containment for semantic children, Architecture · allocated work for responsibilities, Functional dataflow for work and exchanges, or Missions & capabilities for outcomes. All relationships is a broader exploration view; OA ↔ SA traceability displays counterparts.", - "Select an element and edit Semantic parent to change ownership. Use an Allocation relationship to change responsibility. Apply changes validates the semantic change.", - "Drag nodes to arrange the current view, scroll to zoom and use Fit to frame its contents. Arrange changes diagram geometry. Export SVG produces a shareable view.", - "Select a diagram node to access Placement, Fold / unfold and Hide. Hide removes its view placement; Delete from model removes model records and requires review of the affected references.", - ], - ), - explain( - "Keep diagrams readable", - "Use separate views for responsibility, decomposition and cross-layer coverage. Place existing elements with Show in this diagram. A placement's Diagram field can relocate it while preserving its identity and artifact references. Do not duplicate a semantic element merely to show it in another view.", - ), - walkthrough( - "Save a focused exchange view", - &[ - "To preserve the current presentation, right-click empty diagram space and choose Duplicate view. The copy has independent placements, routes and visibility settings while representing the same model elements and relationships. View-specific artifact links stay with the original view.", - "Open View properties. Under Work exchange categories or Participant exchange categories, uncheck All, including uncategorized exchanges, then Include the categories you need. Selected categories combine by union; none selected shows none of that family. Work means OA Interactions or SA Functional exchanges; participant means OA Communication means or SA Boundary exchanges, including supported legacy records.", - "Work exchange coverage across the whole model offers All work exchanges, Used in behavior and Outside behavior. This uses explicit process, chain and scenario occurrences, including reuse. To focus on particular behavior, uncheck All work contexts and Include those processes, chains or scenarios. Context and coverage intersect with categories; no selected contexts means no work lines.", - "Choose Exchange labels: Names, Items or Names and items. In Individual lines and omission reasons, search a name or alias and check Hide to retain an explicit hide. Unchecking Hide removes only that reason; other settings or missing endpoints may still omit the line. Clear all explicit hides removes the manual hide list.", - "Apply changes and inspect the result. To restore all eligible lines, enable All relationship line kinds, both category All choices and All work contexts, choose All work exchanges, and clear explicit hides. Empty relationship-kind selection shows no lines. These settings do not remove nodes or model facts; a line still needs endpoints eligible for the projection.", - ], - ), - explain( - "Read allocation summaries and ports", - "A dashed parent activity/function with an italic name and SUMMARY heading groups leaves assigned to the same performer. It has no direct allocation of its own; its inspector explains the leaf performer. Small ports belong to the boundary of their visible owner and are selectable model elements. Folding a container hides its inner work and ports; summary exchange lines do not invent new ports on the container.", - ), - walkthrough( - "Route connections around the model", - &[ - "Select an architecture connection and drag a segment to add a bend. Right-click a line for Add bend here; right-click a circle for Remove bend. With the canvas focused, Insert adds a bend and Delete removes the selected bend. Drag the label independently. Escape cancels a gesture; Undo reverses one completed drag. Shift+F10 opens the selected diagram item's context menu; use arrow keys and Enter to choose an action.", - "Automatic orthogonal routing avoids cards and container headers where space permits. Amber connections need attention: move an obstructing element or a bend. In Select / move, drag a port to any border of its owner. Manual positions are per diagram; right-click Reset automatic position restores connection-aware placement. Ports remain attached to their actual owners. Manual routes are saved separately for each diagram and appear in source, API and SVG export.", - "Right-click a connection and choose Reset routing to clear its pins and label position in this view. The Properties Actions menu provides the same commands. Arrange preserves pins. Moving one endpoint keeps pins fixed; moving a container carrying both endpoints translates their pins. When folding promotes an endpoint, expand it again to resume and edit its saved route.", - ], - ), - explain( - "What nesting does not imply", - "Containment in an allocation projection expresses a performer relationship. It is not a physical deployment statement. Physical deployment is a different architectural concern; do not infer hardware installation from an OA/SA box layout.", - ), - ], - related: &[Topic::Participants, Topic::Perspectives, Topic::Artifacts], - destination: Some((Page::Diagram, "Open Architecture")), - } - } - } - Topic::Capabilities => { - const { - Article { - summary: "Capabilities describe an ability to achieve an outcome. Missions explain the purpose served by those abilities. Both Operational and System Analysis have missions, capabilities and participants; activities and functions describe supporting work.", - keywords: "capability mission outcome operational capability system capability involvement contribution trace coverage use case", - sections: &[ - walkthrough( - "Connect outcomes to the work", - &[ - "Create an OA Capability such as Protect life at sea. Describe the operational outcome and the conditions in which it matters.", - "Select a relevant participant, role or activity. Use + Relationship → Capability involvement and choose the capability as the target.", - "Create an Operational mission in OA, or a Mission in SA. Select it and choose Exploit capability to connect it to a capability in the same perspective. Involve participant associates an entity or actor with the mission.", - "Connect System participants/functions to the capability using Capability involvement. Connect a Process or Functional chain to a capability using Chain involvement.", - "Review OA/SA counterpart links in Traceability and read Model checks for incomplete coverage. A named capability alone does not demonstrate that the necessary behavior exists.", - ], - ), - walkthrough( - "Include, extend or specialize a capability", - &[ - "Select a capability in the diagram or explorer, then open Actions at the top of its properties. Include capability identifies behavior that is always part of it; choose the included capability as the target.", - "Extend capability connects this extending capability to the extended capability. Enter the condition under which the extension adds behavior. An empty condition remains editable and appears in Model checks.", - "Specialize capability connects a specific capability to a more general one. Inclusion and specialization cannot form cycles.", - "The relationship preview names both ends and generates a readable name. Apply adds one undoable change; Cancel preserves the model. Include and extend labels remain visible in the diagram.", - ], - ), - explain( - "Use scenarios as concrete evidence", - "A scenario illustrates normal response, degraded operation or an exception. In its Properties, choose Context to name the capability it supports in the same perspective; record assumptions and pre/postconditions too. Connect a Process or Functional chain with Chain involvement. Descriptive prose explains the intent, while these explicit references make the context inspectable.", - ), - explain( - "Avoid implementation names at this stage", - "Protect life at sea is a capability. Coordinate rescue is work. Send a JSON request is usually a design choice. Start with the engineering obligation and add technical detail when the perspective calls for it.", - ), - ], - related: &[ - Topic::Perspectives, - Topic::Chains, - Topic::Scenarios, - Topic::Transitions, - ], - destination: Some((Page::Catalog, "Open Model explorer")), - } - } - } - Topic::Exchanges => { - const { - Article { - summary: "An exchange says what passes between work or across a participant boundary. Its payload, endpoints and allocation make that statement precise.", - keywords: "interaction functional exchange boundary exchange communication mean participant directed bidirectional category categories flow information material energy event source target port exchange allocation", - sections: &[ - walkthrough( - "Create a typed exchange", - &[ - "Create the source and target activities/functions in one perspective. Give each an engineering name and allocate leaf work to its responsible participant.", - "Choose Interaction or Functional exchange in the diagram palette. Click the source, then a highlighted compatible target, or drag between them. Functional exchanges connect output ports to input ports; function cards create or reuse real ports in the same transaction. Middle-button drag pans while keeping the source; arrow keys also pan when the drawing canvas has focus. Click empty space to restart, or Escape to cancel.", - "Create an Exchange item under Data & interfaces. Choose its nature (Information, Material or Energy) and its mechanism (Flow, Event, Operation or Shared data). Describe the payload independently of the connection.", - "Select the exchange in Model explorer or the diagram. Edit its properties to reference the appropriate exchange items and available ports; Apply changes validates the complete record.", - "In System Analysis, choose Boundary exchange and connect compatible component ports. Flow goes from Out to In; Standard is unoriented. Select / move returns to arranging. Exchange allocation associates a boundary exchange with the functional exchange it carries.", - ], - ), - explain( - "Flow and sequence are different", - "A dataflow graph shows possible dependencies between activities/functions. It does not by itself impose an execution order. Use a Process, Functional chain or Scenario when you need to describe an ordered path, repeated exchanges or alternatives.", - ), - explain( - "Use engineering payloads", - "Choose Information for data such as a distress report, Energy for delivered power and Material for a transferred substance. Mechanism separately describes how it is exchanged: an Event is a notification, an Operation has parameters, and a Flow carries values or a physical quantity. Data types and fields make payloads precise. Artifact links can point to interface-control documents.", - ), - explain( - "Operational and system boundaries", - "An OA Interaction connects activities. A Communication mean connects operational Entities or Actors directly; it requires no system ports. In Operational Analysis, choose Communication means and connect its participants. Its properties hold carried items and direction; the relationship form offers Directed source → target (unchecked: bidirectional). Existing OA Boundary exchanges with Legacy ports remain editable for compatibility.", - ), - explain( - "Relate operational transport and concerns", - "Use Exchange allocation to associate a Communication mean with the Interactions it carries. Allocate each activity to its actual performer and check that those performers match the communication endpoints and direction. Create an Exchange category at the perspective root, choose Work exchanges or Participant exchanges in its properties, and Include existing exchange members. Categories name reusable concerns for view filtering; they do not change endpoints, allocate work or imply execution. A category may be empty, and an exchange may belong to several categories.", - ), - ], - related: &[ - Topic::Interfaces, - Topic::Chains, - Topic::Scenarios, - Topic::Artifacts, - ], - destination: Some((Page::Catalog, "Open Model explorer")), - } - } - } - Topic::Chains => { - const { - Article { - summary: "An operational Process or system Functional chain connects contributions of existing work and exchanges, including alternatives and reusable procedures. It explains how work contributes to an outcome.", - keywords: "process functional chain ordered occurrence sequence path involvement dataflow exchange repeated steps caller contextual qualified assembly common work focus reuse", - sections: &[ - walkthrough( - "Describe a path through exchanges", - &[ - "A blank path offers Add first activity/function contribution. You can use existing work or stage a new source in that form; exchanges still need explicit compatible source and target work.", - "Open Behavior & scenarios → New. Choose Operational process in OA or Functional chain in SA, enter its exact name, then Create and open. The new behavior belongs at the layer root and opens immediately; Undo removes the creation as one edit.", - "Open Tools and choose Work or Exchange, then a highlighted position. In Work, Create new source keeps the contribution name and position while you name its Activity/Function and choose its semantic parent. Use this new source stages it; Create commits source and contribution in one Undo. An adjacent exchange may require a particular existing work identity.", - "In Tools, choose Sequence link and click or drag from the preceding contribution to the following contribution. Choose Tools → Select / reorder to drag a selected path endpoint and reconnect it; a contribution's ↕ handle changes its position within a column. These are local contributions and sequence links, not new architecture exchanges.", - "Select a contribution, then add another after it. Tools → Two paths… creates a split, two paths and their merge in one undoable edit. Enter explicit alternative conditions. Fit overview uses the visible canvas area; 100% keeps working scale with scrollbars. If the minimum zoom is reached, scroll to inspect the remainder.", - "Use Chain involvement from the Process/Functional chain to the capability it supports. Read Model checks for unfinished or disconnected paths.", - ], - ), - explain( - "Choose the right behavioral description", - "A chain concentrates on a route through existing exchanges. A scenario can also carry actions, state observations, lifelines and nested fragments. The architecture diagram remains a view of the possible relationships rather than an execution trace.", - ), - explain( - "Review the path as an engineer", - "Check that exchanges express meaningful handoffs and that the links and branch guards support the selected outcome. Use Tools → Reuse to involve an existing process or chain as a nested reference. For an imported ordered exchange path, Append exchange… continues that path; Convert ordered path to explicit links exposes its implied links for topology editing. Conversion preserves the original order in one undoable change.", - ), - walkthrough( - "Connect a particular use of a procedure", - &[ - "Choose Focus canvas for more working space; Restore panels brings the explorer and properties back. Select a contribution inside a reusable preview, then open Selection details to read its complete caller and reference identity.", - "In Selection details, use Expand nested reference or Fold nested reference to inspect that instance. Open source or Open contribution definition visits the shared definition; Back to caller returns to the original caller context.", - "Open Tools → Qualified link / assembly… and choose its source and target. Use Enter reference to open an instance's contributions, Up one reference for its caller scope, or Root for the link owner. Search and page through all contributions in that scope, then select the exact endpoint. The first and second uses of a procedure remain different choices even when their work has the same name.", - "Choose Precedence to describe order and its guard, Exchange assembly to identify a work exchange between the contributions, or Common work incidence to state that both use the same work. Select the engineering identity when required, then Apply link.", - ], - ), - explain( - "Keep caller intent separate from the definition", - "A link into the first assessment's second contribution belongs to that caller. It does not change the shared assessment or its second use, and it does not delay the first contribution by targeting the whole procedure. Common work records shared identity without merging executions. Model checks identify the particular caller path and report incomplete analysis when expansion cannot be completed.", - ), - ], - related: &[Topic::Exchanges, Topic::Capabilities, Topic::Scenarios], - destination: Some((Page::Behavior, "Open Behavior & scenarios")), - } - } - } - Topic::Scenarios => { - const { - Article { - summary: "Use a scenario to tell a concrete behavioral story, including repeated messages, actions, state observations and guarded alternatives.", - keywords: "scenario sequence lifeline sender receiver fragment branch alternative loop guard parallel strict optional occurrence exchange action state interface operation allocation call reply unresolved", - sections: &[ - walkthrough( - "Create a scenario", - &[ - "Open Behavior & scenarios → New → Scenario and enter its name. Explicitly choose Activities or Participants in OA, or Functions or System boundary in SA. Add optional context only when needed: this is a reference, while the scenario itself belongs at the layer root. Create and open starts the empty scenario.", - "Use Add first lifeline on an empty scenario, or + Lifeline. Choose existing work/participant or Create new source. The explicit viewpoint limits its kinds; choose participant kinds deliberately. Source name and lifeline name are separate, and the source semantic parent is independent of the scenario. No allocation or capability involvement is inferred.", - "Choose + Message and its style. Call + correlated reply creates both occurrences in one edit. Select the exchange and compatible sending/receiving lifelines. Reply to existing call offers valid unanswered calls.", - "Use Action, Observe state or Fragment for other behavior. A local timer has an expression and a lifeline, with no global exchange. Create and delete messages describe participant lifetime; Model checks also flag risks inside repeated loops.", - "Choose a message style and Draw message. Start at a highlighted sequence position on a lifeline, then click or drag to the receiving lifeline. The form retains the chosen position while you select the exchange and details. Escape cancels the gesture.", - "In Select / reorder, click an occurrence and drag its ↕ handle to another position within the same sequence. The ordered list's up/down controls provide another way to reorder. Select an occurrence to inspect its particular use of the exchange, including its message values.", - ], - ), - walkthrough( - "Describe alternatives", - &[ - "Choose Fragment and select ALT, PAR, LOOP or OPT. Enter the branch conditions; the tool creates the fragment and its operands together.", - "Use the Destination selector in subsequent message or action forms to choose the intended operand. New occurrences append to that operand's ordered behavior.", - "Use Reuse scenario to refer to existing behavior. Bind every referenced lifeline to a compatible local lifeline explicitly. Open source follows the original scenario without copying its contents.", - "Inspect the scenario projection and Model checks. State exactly what each guard means, including units and assumptions where relevant.", - ], - ), - walkthrough( - "Invoke an interface operation", - &[ - "In an SA BoundaryInterface scenario, create or edit a Call or Asynchronous message. Choose its boundary exchange and actual sending and receiving lifelines first. The receiving Standard component port must provide the interface; the sending port must require a compatible interface.", - "Enable Invoke a specific interface operation and choose the exact eligible allocation. Two parameterless operations on one channel remain distinct. Choose Keep operation unresolved when the operation decision is unfinished; existing value expressions remain available.", - "For a Reply, inspect Reply operation from Call. It derives the selection from that particular Call and cannot be selected independently. Enter values for the selected operation's call or reply parameters; incompatible values must be corrected explicitly.", - "When transitioning that invocation to OA, explicitly choose to omit the technical operation and enter the engineering rationale in the preview or semantic review row. The SA source keeps its operation identity.", - ], - ), - explain( - "Conditions are engineering statements", - "Guards, actions and effects document behavior; a drawn scenario does not execute code or prove timing. Add evidence references when a simulation, test or analysis establishes a stronger claim.", - ), - explain( - "Keep uses distinct from definitions", - "Several occurrences can refer to one exchange without duplicating it. Edit occurrence properties for this message's sender, receiver and value expressions; edit the exchange for the shared endpoints and payload. A Reply refers to a particular Call, not merely a similar name. Reordering across fragment operands or changing lifeline bindings needs an explicit structural edit, not an accidental drag across a boundary.", - ), - ], - related: &[ - Topic::Chains, - Topic::States, - Topic::Exchanges, - Topic::Artifacts, - ], - destination: Some((Page::Behavior, "Open Behavior & scenarios")), - } - } - } - Topic::States => { - const { - Article { - summary: "Describe the conditions in which the operation or system behaves differently. States and modes belong to regions within a state machine.", - keywords: "state machine mode region initial final transition trigger guard effect entry exit do availability degraded nominal diagram draw reconnect local internal timed fold unfold placement pan zoom undo", - sections: &[ - walkthrough( - "Build a state machine", - &[ - "In Behavior & scenarios → New → State machine, enter its name and search for the existing same-layer participant that owns it. Candidate labels show parent context and alias to distinguish repeated names.", - "If the owner does not exist, Create new owner retains the machine name and first-view option. Choose its participant kind and semantic parent explicitly. Use this new source returns without changing the model; Create and open commits owner, machine and optional view together.", - "Optionally select Create the first state view, then Create and open. The new machine starts without regions or states. For an existing machine, Create diagram places its existing regions and vertices in a saved view; + View creates another independent layout.", - "Choose + Region and click the highlighted machine, or a State/Mode for a nested region. Choose Initial, State, Mode or another kind from + State / symbol, then click its highlighted owning region. Rename the new element in Properties.", - "Choose Transition and drag from a source vertex to a compatible target, or click the source and then the target. In Select / move, drag states or containers to arrange them; a container moves its descendant placements without changing ownership.", - "Select a drawn transition and drag its endpoint handle to reconnect it. Its right-click menu also offers Reconnect source and Reconnect target; choose one, then click a compatible vertex.", - "Right-click a state or the background for New transition with details… to create Local, Internal or timed behavior. Right-click an existing transition and choose Edit transition to edit its kind, triggers, guard and effects. EntryPoint outgoing transitions use Local. Apply accepts the complete form; invalid drafts remain available for correction.", - ], - ), - explain( - "Navigate and recover a view", - "Middle-button drag pans; scroll or use −/+ to zoom, and Fit frames the diagram. Right-click a container for Fold contents or Unfold contents, or choose Placement properties for exact coordinates and size. Hide from this diagram removes only its placement; Show an element not yet in this view restores missing elements. Escape cancels a drawing gesture. Undo restores each completed gesture or menu edit, and Model checks identifies unfinished structures.", - ), - explain( - "One saved diagram across editors", - "Each state view stores a reference to its state machine and its own placements. Text, JSON and REST edit those same diagram records, including the persisted machine context; saving and reopening retains them. Merely opening the behavior page does not create a diagram. State-view edits preserve other diagram layouts.", - ), - explain( - "Availability and state actions", - "Availability links a participant or behavior to a State/Mode in which it is available. State action links a State/Mode to existing Activity/Function work and records Entry, Do or Exit. The saved state view shows those named action rows above internal transitions and nested regions. Select a row to edit its actual State action relationship. The linked work remains in its own semantic owner; the row does not contain or copy it. Free-text entry/do/exit notes remain separate engineering prose.", - ), - walkthrough( - "Add timing without losing an existing transition", - &[ - "Select the transition and choose Edit transition. Its current endpoints, kind, existing triggers, guard and effects populate the form.", - "Choose Add a time event with this transition, select After for a delay or At for a time, then enter a time expression such as 30 s. This adds a typed Event to the trigger alternatives; it does not replace existing triggers.", - "Review the complete transition before Apply. The event and transition update are one undoable change. If the form is rejected, correct the retained values; a rejected candidate creates no orphan Event.", - "Save the entry and any included source files. On reopening, check the transition's trigger list and the Event expression. An expression documents timing intent; it is not evaluated by a simulator.", - ], - ), - explain( - "Keep conditions testable", - "Prefer a guard such as battery_level < reserve_threshold over a vague phrase such as low battery, and define the quantities elsewhere in the model or linked evidence. A state diagram documents the intended behavior; it is not a running state-machine simulator.", - ), - ], - related: &[ - Topic::Scenarios, - Topic::Participants, - Topic::Recovery, - Topic::Artifacts, - ], - destination: Some((Page::Behavior, "Open Behavior & scenarios")), - } - } - } - Topic::Interfaces => { - const { - Article { - summary: "Define exchanged payloads independently of the ports and participants that carry them. Reuse the same data definitions in related exchanges.", - keywords: "data interface port direction input output inout return contract provision requirement exchange item enum enumeration record primitive field value type multiplicity minimum maximum bounds default unit specialization inheritance association storage reference recursive path transmission acquisition protocol", - sections: &[ - walkthrough( - "Define a reusable payload", - &[ - "Open Data & interfaces and create an Exchange item. Choose Information, Material or Energy for nature; choose Flow, Event, Operation or Shared data for mechanism. Keep Unspecified when the engineering decision is still open.", - "Create a Data type for a reusable definition. Choose Boolean, Integer, Real, String, Enumeration or Record. Numeric bounds are inclusive; String bounds count Unicode characters. A typed default must satisfy those bounds or enumeration literals. Units annotate quantities without converting them.", - "Create fields within an Exchange item or Record type. Set Value type, minimum/maximum count, unit and optional default. A default can be absent, explicitly empty when minimum is zero, or a list of typed scalar values. A type default describes one element; it does not create a required collection.", - "Select the exchange and reference the payload items. Apply changes and check the result in Model checks.", - ], - ), - explain( - "Operation parameters and message values", - "An Operation's direct fields are parameters. In and InOut supply call values; Out, InOut and Return supply reply values, from the provider's perspective. There is at most one Return parameter. Qualified paths distinguish origin.latitude from destination.latitude when both use the same Coordinate record. Nested fields inherit the parameter direction. Message expressions are annotations, not evaluated values.", - ), - explain( - "Reuse, ownership and references", - "A Record or Exchange item can specialize one compatible base. Inherited fields keep their original owner and identity; edit the base to change them. Value fields compose a payload. Reference fields refer to separately identified Record objects and stop payload expansion. Optional recursive values and reference cycles are valid; a mandatory value cycle warns that no finite complete value exists. Data associations describe named reference ends, multiplicities and navigation between Record types without creating contained fields.", - ), - walkthrough( - "Allocate an item to interfaces", - &[ - "Create an Interface and allocate an Exchange item. Select its allocation row to edit transmission, acquisition and order; Open item navigates to the shared payload definition.", - "Two interfaces may allocate the same item with different protocols. Attach CAD, code or document evidence to the allocation when it concerns that particular contract.", - "Removing an allocation keeps the shared item and other allocations. If a live boundary depends on it, first revise that boundary contract. Undo restores an accepted removal.", - "An Interface may specialize one base and inherit its allocations. Duplicate allocation of an already inherited item is rejected. A specialized item satisfies its base contract; a base item does not satisfy a more specific contract.", - ], - ), - walkthrough( - "Connect participant boundaries", - &[ - "In System Analysis, select a System or System Actor and choose Add port in Model explorer or Properties. Name it, choose Standard or Flow, then Create port. Standard ports describe interfaces; Flow ports have their own input/output orientation. Functions use separate input/output port actions.", - "Use Interface provision and Interface requirement relationships to state who provides or needs the contract.", - "Select a visible component port and choose Draw boundary exchange, then click or drag to a highlighted compatible port. The diagram palette offers the same tool. If the endpoints are outside the diagram, Configure boundary exchange opens the form with owner names and aliases. Create the matching port first if needed. Exchange orientation is separate from Flow port orientation. Exchange allocation associates carried functional exchanges with the boundary connection.", - "Link the detailed interface-control document, CAD connector or schematic through an Artifact reference when an external artifact supplies additional evidence.", - ], - ), - explain( - "Preserve meaning across perspectives", - "Shared kinds such as Data type and Interface still belong to an engineering perspective. Their Engineering perspective property is explicit. Use counterpart traces for separate OA and SA definitions that correspond; do not assume that similar names create a trace.", - ), - ], - related: &[Topic::Exchanges, Topic::Artifacts, Topic::Transitions], - destination: Some((Page::Data, "Open Data & interfaces")), - } - } - } - Topic::Transitions => { - const { - Article { - summary: "Create or reuse counterpart records across OA and SA through a reviewable preview. Both perspectives keep their identities and remain linked.", - keywords: "transition counterpart bidirectional mapping realization drift conflict merge keep target take source baseline scope dependency reverse update trace reuse", - sections: &[ - walkthrough( - "Review a counterpart transition", - &[ - "Select an operational element and choose Create System counterpart. For a system element, Create Operational counterpart starts the reverse direction. The source stays in its original layer.", - "Choose Include: Selected only, Required references or Whole subtree. Required dependencies must have compatible counterparts before the plan can apply.", - "For each source, choose Create new counterpart or reuse an existing target. For an operational participant, explicitly choose System for the system under study or System actor for an external participant. Use Show result in to choose a compatible diagram; Automatic selects an architecture view for ordinary elements.", - "Read the proposed changes and conflicts. For a descriptive conflict, choose the source value, keep the target value, or enter a merged value. Changing the target/kind requires reviewing that target's decisions again.", - "Apply only after every blocking decision is resolved. The accepted plan is one undoable model change; Cancel preserves the current model.", - ], - ), - explain( - "Review drift deliberately", - "On an existing trace, choose Review counterpart changes… to compare typed content and prior observations. Choose Keep for the destination, Take for the source, or Set final value / Set final kind and payload where offered. Review mapped references and proposed changes, then choose Apply reviewed changes. Independent edits may be intentional; keeping a value records that decision. Manual traces record engineer-defined correspondence. Ordinary edits never synchronize counterparts automatically; an explicit supported transition or review can update selected properties, including for a Manual trace.", - ), - explain( - "Know what reconciliation changes", - "Semantic review supports structural and scalar changes, typed constructors, individual collection rows, explicit ordered collections, existing attachments and artifact links. Several trace rows can contribute to one combined destination proposal. Candidate validation checks the complete proposed model before one undoable transaction. The Review semantic changes topic explains included rows, mapped references and each explicit attachment decision.", - ), - explain( - "Avoid duplicate counterparts", - "Check an existing trace before requesting a new counterpart. Several engineering records can correspond, but each mapping should have a reason. Reverse transitions also use the trace to reuse the intended record rather than silently replacing the original.", - ), - ], - related: &[ - Topic::Perspectives, - Topic::Participants, - Topic::Capabilities, - Topic::Recovery, - Topic::SemanticReview, - ], - destination: Some((Page::Traces, "Open Traceability")), - } - } - } - Topic::SemanticReview => { - const { - Article { - summary: "Review what changed between corresponding OA and SA records. Choose the engineering result explicitly and retain the reasoned correspondence for later changes.", - keywords: "semantic drift review keep take final candidate previously reviewed history reset reference pairs annotation qualified field path binding lifeline membership order up down reconcile steps fragment branch direction multi-source batch", - sections: &[ - walkthrough( - "Compare the right records and direction", - &[ - "From an existing counterpart trace, choose Review counterpart changes…. Confirm the OA → SA or SA → OA direction and select the intended trace / review history. Each correspondence and direction has its own retained row draft; opening another row does not discard the first row's choices.", - "In Properties, select a fact. Compare Previously reviewed with Current on both sides. Unreviewed means there is no prior observation; Both changed means both sides changed since that observation, not that one side is automatically wrong.", - "Choose Keep for the destination, Take for the source, or Set final value…. For a kind change, Set final kind and payload… reviews the complete replacement and its affected references. Leave unselected makes no decision for that fact.", - "Read Reference mapping and Additional changes before Apply reviewed changes. A missing or ambiguous correspondence must be resolved explicitly. Apply commits the validated model and the reviewed history together; Undo restores both.", - ], - ), - explain( - "Map references by meaning", - "Reference mapping connect exact OA and SA identities used by the proposed change. Similar names are not sufficient. Map the records that play the same engineering role, including enclosing fields in qualified message-value paths. Mapping a referenced record does not authorize creating, moving or editing that record. If it needs a counterpart, establish that correspondence first.", - ), - walkthrough( - "Review one message value or lifeline binding", - &[ - "Open Contents and order and choose the exact identities on both sides. For a message value, browse the enclosing record fields as well as the final field: origin.latitude and destination.latitude are distinct addresses.", - "For a binding, choose the referenced lifeline key and its compatible local lifeline. Check which scenario owns each side. Keep or Take concerns that row, not all bindings in the scenario.", - "Use Set final value… to enter the destination expression or local lifeline. Include in destination collection controls that row's presence. Unchecking removes the entry without deleting its referenced engineering object.", - "If a row key changed, review the old entry's removal and the new entry's addition separately. A missing source identity is not the same decision as an identified source entry that has been deleted. Read the proposed value before applying.", - ], - ), - walkthrough( - "Review membership and sequence order separately", - &[ - "Review which occurrences belong to Steps, or which operands belong to Fragment branches, using Contents and order. This changes membership; it does not silently reparent or create the referenced records.", - "Choose Review final order… or the corresponding order fact in Properties. Keep retains destination positions. Take uses mapped source order when it can preserve destination-only members without an ambiguous placement.", - "For an explicit result, choose Set final value…, then use Up and Down. If you change membership afterward, choose Reconcile with selected membership: surviving positions are retained, removed members leave the list, and new members append for you to position.", - "Every surviving destination member must appear exactly once. Resolve an ambiguous insertion with an explicit final order; do not remove an engineering contribution merely to make the preview pass.", - ], - ), - explain( - "Review attached records and external evidence", - "In Owned elements and relationships, choose exact OA and SA child or relationship identities, then Use exact attachment pair. Review a child's parent or a relationship endpoint independently; creating a new child or relationship requires its explicit transition mapping. In Evidence links, pair the exact links and choose Review this exact artifact pair. Presence concerns attachment to the reviewed owner. Taking an identified source absence can delete the destination link; a destination-only link is retained. Reserve a new destination counterpart only when one is needed, then explicitly review Presence and all seven metadata facts before the combined Apply. The reservation creates nothing by itself.", - ), - explain( - "Preserve intentional differences and useful history", - "Keep can acknowledge a deliberate difference without overwriting the destination. Reset this exact previous observation clears only the chosen review history; it does not restore an old model value. Overlapping row addresses may require an explicit history reset. Review those messages rather than clearing unrelated observations. Pending review choices and raw values can be recovered after an unexpected exit, but the plan is recomputed against the recovered accepted model.", - ), - walkthrough( - "Review several sources together", - &[ - "Use Add linked object, then Review object. Include this object determines whether its decisions contribute to the shared preview. The object selector switches the object you are editing; excluded rows retain their drafts.", - "Review each included source's contribution to the destination. Compatible decisions combine; contradictory Keep, Take or final-value decisions must be resolved explicitly. Read the combined conflicts, Reference mapping and Additional changes before Apply reviewed changes. One Apply commits the combined model and review histories; one Undo restores them.", - "Only one trace history per source can be included. Some reverse correspondences cannot share the core reference mapping; the preview explains the conflict. Exclude a row or choose a distinct correspondence without discarding its draft. Switching direction retains the other direction's choices for later review.", - "Recovery retains included and excluded row drafts and raw final values, then recomputes the plan against the recovered accepted model. Refresh preview is not automatic synchronization and does not authorize changes to attached records or artifacts.", - ], - ), - ], - related: &[ - Topic::Transitions, - Topic::Interfaces, - Topic::Scenarios, - Topic::Recovery, - ], - destination: Some((Page::Traces, "Open Traceability")), - } - } - } - Topic::Artifacts => { - const { - Article { - summary: "Connect model decisions to external engineering evidence using stable references to code, CAD, schematics, documents and other artifacts.", - keywords: "artifact traceability evidence code cad schematic electrical docx requirements revision fragment uri git commit owner placement document", - sections: &[ - walkthrough( - "Link an artifact", - &[ - "Select the model record the evidence concerns. This may be an element, relationship, diagram, placement or other supported persistent record.", - "Choose + Artifact reference in Properties, or + Artifact in Traceability. Name the reference so another engineer understands its purpose.", - "Enter the Artifact URI, optional external Revision and Fragment. Use a commit or document revision when a reproducible reference matters; the fragment identifies a particular part of the artifact.", - "Choose the relation to the artifact: References, Satisfies, Verifies or Implements. Add a description explaining the evidence and its limits. Apply changes, then inspect the owner and reference in Traceability.", - ], - ), - explain( - "Find existing evidence", - "In Traceability, choose Development artifacts to show artifact links directly. Search by the link's name or alias, its owner, URI, revision or fragment. Layer traces shows OA/SA correspondences; All shows both lists. Filtering changes only what is shown and preserves the selected record and unfinished property input.", - ), - explain( - "A reference is not an attachment", - "The model preserves the author-provided URI, revision and fragment. Loading or saving a model does not fetch, execute or verify an external artifact. A path on one workstation may not exist on another; choose team-resolvable locations and identifiers.", - ), - explain( - "Choose the right owner", - "Link semantic evidence to the element or relationship it supports. Link view-specific evidence to a diagram or placement. A diagram node's placement identity remains distinct from its represented element, so the reference can express the intended scope.", - ), - explain( - "Automation uses the same records", - "A CAD or version-control plugin can query and edit artifact links through the REST API. Its update should preserve stable model IDs and use the server's conditional write contract to avoid overwriting another engineer's work.", - ), - ], - related: &[Topic::Source, Topic::Server, Topic::Nesting], - destination: Some((Page::Traces, "Open Traceability")), - } - } - } - Topic::Source => { - const { - Article { - summary: "The text editor and graphical forms edit the same model. Source projects preserve file organization and comments for human review, automation and version control.", - keywords: "text textual source language syntax highlighting lint parser git aliases uuid identity rename diff merge restructuredtext spec canonical parity", - sections: &[ - walkthrough( - "Edit and review source", - &[ - "Open Textual model and expand folders to choose a source file, or choose Actions > Open source in Properties to locate the selected record. Switching files preserves each draft. Collapsed folders show modified-file and diagnostic counts; Locate reveals the active file. Right-click a file for its actions.", - "Use Find / go to, Ctrl+F or Ctrl+G to navigate. Literal search is case sensitive. Enter/F3 selects the next match, Shift+F3 selects the previous match, and results wrap. Go to line accepts the displayed one-based line number.", - "Edit the draft. Syntax highlighting distinguishes source structure; live lint reports errors with line and column information.", - "Choose Apply all to parse and validate all reachable included files. A successful application is one model change; graphical views and Properties then reflect it. Finish or revert source drafts before editing graphical properties.", - "If parsing or validation fails, the accepted model remains unchanged and the source draft is preserved. Correct the reported location or use Revert draft to restore the accepted source.", - "Save all writes the entry and its included UTF-8 files. Review the Git diff before committing, including relationships, traces and diagram changes that belong to the same engineering edit.", - ], - ), - explain( - "Names, aliases and IDs serve different purposes", - "The displayed name is engineering prose. A stable @alias is the readable source reference and need not change when the name changes. UUID identity distinguishes the persistent record. Preserve IDs when revising the same engineering object; changing identity is a deliberate operation that affects references.", - ), - example( - "Readable references", - "For example, renaming an activity does not require renaming @assess_incident. A related record can still refer to that alias.", - "source @receive_distress;\ntarget @assess_incident;", - ), - explain( - "Use one contract", - "The normative specification is docs/language.rst in the distribution. It defines source grammar and supported graphical semantics together. Text preserves semantic records, diagram placements, ordered occurrences, traces and artifact references. Graphical edits preserve unaffected source formatting and comments; accepted model Undo/Redo also restores the exact source files. Comments and source-file organization are not part of JSON export.", - ), - example( - "Keep included files with their entry", - "An include is relative to the file that declares it. Put declarations in the included file and keep one project entry. A newly created graphical record may live in another source file; use Open source to locate it and include every affected file in your review and save.", - "// Inside the project declaration:\ninclude \"oa/operations.arcadia\";\ninclude \"sa/model.arcadia\";", - ), - explain( - "Resolve source-control conflicts before applying", - "Ordinary Git conflict markers are not Arcadia syntax. Resolve the text merge, preserve intended UUIDs/references, then apply and inspect Model checks. The editor does not decide which engineer's model change is correct.", - ), - ], - related: &[ - Topic::Recovery, - Topic::Files, - Topic::Artifacts, - Topic::Server, - ], - destination: Some((Page::Text, "Open Textual model")), - } - } - } - Topic::ModelChecks => { - const { - Article { - summary: "Use checks to find invalid structure and unfinished engineering work. A valid model can still need decisions, behavior evidence or allocation.", - keywords: "validation warning error completeness coverage non_leaf_work_exchange work_without_behavior_context exchange_without_behavior_context mission_without_capability capability_without_behavior_context unsupported_capability sequence_gap allocation leaf context diagnostics lint", - sections: &[ - explain( - "Errors and warnings answer different questions", - "An error rejects the candidate, leaving the accepted model unchanged. A warning describes an incomplete or questionable model that can still be saved. Read the record, field and source location rather than treating the number of warnings as a quality score. Fix structural errors first: the completeness pass waits for valid structure, so its warnings may appear after those errors are corrected.", - ), - walkthrough( - "Connect work to explicit behavior", - &[ - "For an exchange on non-leaf work, identify the leaf Activity or Function responsible for that endpoint. In SA, keep its typed input/output port owned by that Function. Do not add a fake child just to suppress the warning.", - "For leaf work without behavior context, include a work action or one of its exchanges in an ordered Process/Functional chain or Scenario. Allocation alone states who performs the work, not how it is used.", - "For an exchange without behavior context, include that exact Interaction or Functional exchange in an exchange occurrence. A similarly named exchange or an unlisted occurrence under the container does not provide evidence.", - "Only included steps, fragment operands and valid reused behavior contribute. State actions, availability links and prose remain useful but do not stand in for an explicit process/scenario use. Coverage demonstrates inclusion, not execution or completeness of every possible path.", - ], - ), - walkthrough( - "Connect purpose to behavioral evidence", - &[ - "For a mission without a capability, connect the mission to the capability that delivers it. Merely involving a participant in the mission does not identify that capability.", - "For a supported capability without behavior context, connect a Process/Functional chain using Chain involvement or set a Scenario's Context to that capability in the same perspective.", - "A capability used only to group child capabilities is treated differently from an isolated leaf. A directly contextualized Scenario establishes support; adding an unrelated participant link is not required merely to clear a warning.", - "Review the engineering statement after each correction. Do not invent relationships solely to achieve a warning-free list; leave an unfinished decision visible until the model can express it accurately.", - ], - ), - explain( - "Review sequence gaps and incomplete contracts", - "A sequence gap calls attention to disconnected handoffs; inspect the contribution endpoints and intended path. Missing allocations, unspecified exchange mechanisms and incomplete conditions need their own engineering decisions. An accepted model does not prove that guards are executable, units are converted, external artifacts exist or timing requirements are met.", - ), - explain( - "The same checks are available to text and automation", - "Model checks, source lint and server validation use the shared model rules. Source-project diagnostics identify the included file and location; native JSON checks identify the model record and field. A validation request can inspect a candidate without committing it. Correct errors in the complete candidate before applying it graphically, as text or through the API.", - ), - ], - related: &[ - Topic::Capabilities, - Topic::Chains, - Topic::Scenarios, - Topic::Recovery, - ], - destination: Some((Page::Validation, "Open Model checks")), - } - } - } - Topic::Recovery => { - const { - Article { - summary: "Correct rejected drafts, inspect model checks and recover retained work after an unexpected exit. Recovery copies preserve editing separately from your saved models.", - keywords: "validation checks error warning invalid alias missing reference undo redo revert recover recovery checkpoint autosave crash draft unsaved structural external conflict lint home", - sections: &[ - walkthrough( - "Resolve a model issue", - &[ - "Open Model checks and read the issue's message and severity. Use its Open action, where available, to inspect the affected record.", - "Correct its properties or missing relationships. Check the active perspective and compatible parent/endpoints before changing record kinds.", - "Apply changes and revisit Model checks. A warning about missing allocation or coverage may describe unfinished work; a rejected structural error must be corrected before that candidate can become the model.", - ], - ), - explain( - "A property draft is not yet the model", - "Apply changes validates the candidate. When it is rejected, correct the retained draft or use Revert. The canvas continues to show accepted values. Navigation that needs to apply an invalid draft may remain on the current record so your work is not lost. You can read this guide without applying or discarding that draft.", - ), - explain( - "A new behavior is still a draft", - "Inside a source subform, Back or the first Escape returns to the unchanged parent. Use this new source stages the prerequisite only. Canceling the parent discards it; choosing an existing reference excludes the staged source from creation. Recovery retains both forms and their reserved identities. An open dialog still receives Escape first. Behavior → New keeps the exact name, owner and viewpoint choices outside the model until Create and open succeeds. Cancel or Escape discards only this draft; if a dialog is open, the first Escape dismisses that dialog. New refuses other unfinished property, source or behavior drafts without applying them. Recovery restores the creation form in its original layer, including rejected raw text, for correction or cancellation.", - ), - explain( - "Aliases and references", - "Stable source aliases must follow the language's identifier rules and avoid reserved words. Use a simple descriptive identifier such as assess_incident. Spaces belong in the displayed Name, not the alias. Reference pickers offer compatible model records; creating the required target first is often the missing step.", - ), - explain( - "Undo and recover deliberately", - "Ctrl+Z and redo restore accepted model changes. While the source editor is active, they operate on the source draft instead. Revert restores the accepted source or properties. If a file changed externally, save refuses a blind overwrite: preserve your work with Save as, then compare the files.", - ), - explain( - "Recover an interrupted source save", - "If a staged multi-file save was interrupted, Textual model offers Recover interrupted save. Recovery checks the journal and file contents before restoring originals; it does not overwrite unrelated external changes. This repairs an interrupted explicit save. Home's recovery copies preserve unsaved editing separately.", - ), - walkthrough( - "Recover after an unexpected exit", - &[ - "Open Home and look under Recover unsaved work. Each retained session shows its model name, original location when known and age. Choose Recover; the current model still uses the normal Save/Discard/Cancel guard.", - "Inspect the recovered copy. Accepted model/source content and raw source buffers are retained, including invalid and detached drafts. Supported pending property/behavior forms and workflow drafts return with rejected values and semantic-review choices. Correct or revert unfinished input before applying it.", - "Choose Save as and select a destination. Recovery does not reconnect the original local or hosted destination, and never restores credentials. Undo/redo history is not restored across sessions.", - ], - ), - explain( - "What a checkpoint protects", - "While work is unsaved, a private local checkpoint is attempted every 10 seconds. It does not apply drafts, modify model files or save to the server. Recent edits since the last completed checkpoint may not survive a crash. Home reports unavailable or damaged recovery files without replacing the currently open model. Explicit saves and version control remain your durable milestones.", - ), - explain( - "Save work before leaving", - "New, Open, opening a recent model, loading the example, Recover and Close use the unsaved-work prompt. Save and continue, discard or cancel is your decision. Checkpoints are private recovery copies; continue saving meaningful milestones and using version control or backups.", - ), - ], - related: &[ - Topic::ModelChecks, - Topic::Source, - Topic::Files, - Topic::SemanticReview, - ], - destination: Some((Page::Validation, "Open Model checks")), - } - } - } - Topic::Files => { - const { - Article { - summary: "Keep editable model files and exported pictures distinct. Save the model to retain its semantics, identities and traceability.", - keywords: "file home recent missing locate open save save as keyboard shortcuts ctrl control shift export svg json arcadia backup external changes migration", - sections: &[ - explain( - "Start from Home", - "Home offers New model, Open model, the rescue example and this guide. Recent models lists local files you successfully opened or saved. A disconnected drive or moved file remains visible; use Locate to choose its new location or Refresh after reconnecting the drive. Remove forgets the recent entry without deleting its model file. Home retains the current document; Continue editing returns to the last workspace, including Textual model or a saved state view, with its drafts intact.", - ), - explain( - "Editable formats", - ".arcadia is editable text suitable for Git and can include other source files. .json is the typed model exchange format. Both preserve the persistent model, diagrams and traceability; only a source project preserves its comments and file organization. Export SVG creates a diagram image for reports; it is not an editable model backup.", - ), - example( - "Keyboard reference", - "Shortcuts act on the current project. In Textual model, undo and redo affect the source draft.", - "Ctrl+N New project\nCtrl+O Open model\nCtrl+S Save all\nCtrl+Shift+S Save as\nCtrl+Z Undo\nCtrl+Shift+Z Redo\nCtrl+Y Redo\nEscape Cancel the active dialog", - ), - walkthrough( - "Save and share a model", - &[ - "Use Save all to apply valid drafts and save the source project to the current path. For a new project, select a destination and format in the file dialog.", - "Use Save as for a new path or explicit format conversion. Check the filename extension when selecting text or JSON.", - "After saving, check All changes saved. Share or commit the entry and every included source file, together with the necessary external evidence locations.", - "If another tool changed or deleted the open file, preserve your candidate using a new Save as destination. Compare and reconcile the versions before replacing either one.", - ], - ), - explain( - "Save as preserves the engineering history", - "Saving a source project under a new entry name relocates the save destination without adding a model edit or clearing Redo. Choose a name that does not collide with an included file, including one retained in Undo/Redo history. Save all includes every accepted source file; an unchanged entry file alone does not prove that an included file was saved. If a save fails, the current model and drafts remain available.", - ), - explain( - "Open and migration", - "Open validates a complete candidate before replacing the project. Supported earlier versions migrate to the current schema; Model checks shows migration details such as preserved ports, supplied data defaults, generated interface allocations and unresolved message paths. Old Event items become Information notifications; unknown physical mechanisms remain Unspecified. Source upgrades retain files, includes, nested ownership and comments. Opening does not overwrite the original: use Save as if it must remain in its older format. Invalid input leaves the accepted project unchanged.", - ), - ], - related: &[Topic::Source, Topic::Recovery, Topic::Artifacts], - destination: None, - } - } - } - Topic::Server => { - const { - Article { - summary: "Use optional hosted storage from the desktop, scripts and engineering-tool plugins. All clients work with the same model, validation and authored source.", - keywords: "server rest api automation ai plugin curl http etag if-match revision resource_id deploy hosted cad scripting openapi token", - sections: &[ - example( - "Start a local service", - "From the repository root, run the command below in a terminal and leave it running. The default address is loopback 127.0.0.1:8787, with platform-local storage. docs/server.md also covers installed executables, authentication and Linux deployment.", - "cargo run --locked -p oa-server --bin arcadia-server\n\nGET http://127.0.0.1:8787/health\nGET http://127.0.0.1:8787/api/v1/openapi.json", - ), - walkthrough( - "Open or publish a hosted model", - &[ - "Choose Hosted models... in the top bar. Enter the service root, such as http://127.0.0.1:8787, without /api/v1. Enter the configured bearer token if required, then Connect / refresh.", - "Choose Publish as new to upload the current source project, including its files and comments, as a separate hosted resource. Its acknowledgement switches the current Save destination to that resource.", - "To open an existing resource, search the Hosted model catalog, select a model and choose Open selected. More models loads another page. Unsaved work uses the normal save/discard/cancel prompt.", - "Edit diagrams, properties or Textual model, then choose Save to server or press Ctrl+S. Valid drafts are applied; invalid drafts remain available to correct or revert. Edits and Undo stay local until Save.", - ], - ), - walkthrough( - "Review another client's changes", - &[ - "Check server reads the current revision without replacing local content. A stale Save returns 412 and automatically checks the server for review; local work is retained.", - "Review the changed file list. View server text opens a read-only file; Back to review returns to the list. There is no automatic merge.", - "Reload server version adopts the reviewed source project as one undoable local change after checking unapplied drafts. Undo restores the previous accepted local version without changing the server. Reconcile your intended changes before saving again.", - "After an unknown write outcome, use Check previous write when offered. If publication may already have succeeded, inspect the refreshed catalog before publishing another copy.", - ], - ), - explain( - "Return to a local file", - "Choose Save as local... or Ctrl+Shift+S, then save an .arcadia entry to retain included files and comments. A successful source-project save makes that file the current destination and leaves the hosted resource unchanged. JSON exports the model only and retains the hosted destination and unsaved state. Canceling or a failed save also retains the hosted destination. Forget connection becomes available after leaving the hosted session.", - ), - explain( - "Connection credentials", - "The desktop keeps credentials in memory only. Use HTTPS remotely; HTTP is allowed only for localhost or a loopback IP. Put the token in its own field, never the service URL. URLs with credentials, queries or fragments are rejected, and redirects are not followed. To update a token, enter it and Connect / refresh for the same service, then retry the check or Save.", - ), - walkthrough( - "Automate model edits", - &[ - "Create or import a project using /api/v1/projects or /api/v1/projects/import. Keep the returned service resource_id; it is distinct from the editable model UUID.", - "Read the project's envelope, source or model. Preserve its exact ETag as the precondition for your edit.", - "Submit typed commands to /transactions, or a complete candidate to /source, /model or /source-project, using If-Match. Use transaction dry-run or validation before committing substantial generated changes.", - "A stale write returns 412. Keep the user's draft, reload the current head and reconcile deliberately. After an uncertain write outcome, read the server state before retrying.", - "Query /artifact-links and /traces to connect domain-tool artifacts to model records. The server stores these references; the integration is responsible for resolving its external artifacts.", - ], - ), - explain( - "Validate and save a source bundle", - "GET /source-project returns the entry and included files. POST /validation with format source-project and source_project containing that bundle checks a candidate without committing it; warnings can coexist with valid: true. PUT /source-project accepts the complete bundle under the current If-Match ETag. Structural errors reject the write, preserving the accepted model and revision. Compare record IDs and fields across text/JSON diagnostics; source-project results also identify the included file and source span.", - ), - explain( - "Deployment and persistence", - "Non-loopback binding requires a configured bearer token. Use the documented local-disk storage and deployment configuration. The service keeps the current revision and serializes accepted updates; it does not provide retained revision history or server-side undo. Stop the service cleanly before copying its committed store for backup.", - ), - explain( - "Use the precise contract", - "docs/api.rst and docs/openapi.json define routes, command shapes, limits and errors. The running server also serves OpenAPI. No network request or service mutation is triggered by reading this guide.", - ), - ], - related: &[ - Topic::Artifacts, - Topic::Source, - Topic::Transitions, - Topic::Recovery, - ], - destination: None, - } - } - } - } -} - -/// Help search is independent of model search. All query words must occur in -/// the article, title or retrieval keywords; order and case are insignificant. -fn search(query: &str) -> Vec { - let query = query.to_lowercase(); - let words = query.split_whitespace().collect::>(); - Topic::ALL - .into_iter() - .filter(|topic| { - let article = article(*topic); - let mut haystack = - format!("{} {} {}", topic.title(), article.summary, article.keywords); - for section in article.sections { - haystack.push_str(section.title); - haystack.push(' '); - haystack.push_str(section.body); - for step in section.steps { - haystack.push(' '); - haystack.push_str(step); - } - if let Some(code) = section.code { - haystack.push(' '); - haystack.push_str(code); - } - } - let haystack = haystack.to_lowercase(); - words.iter().all(|word| haystack.contains(word)) - }) - .collect() -} - -fn topic_button(topic: Topic, selected: bool) -> iced::widget::Button<'static, Message> { - button(text(topic.title()).size(13)) - .padding([10, 11]) - .width(Fill) - .style(if selected { - style::selected - } else { - style::nav - }) - .on_press(Message::Guide(Event::Topic(topic))) -} - -fn article_view(topic: Topic) -> Element<'static, Message> { - let article = article(topic); - let mut content = column![ - text(topic.category()).size(11).color(style::MUTED), - text(topic.title()).size(25), - text(article.summary).size(15).color(style::MUTED), - ] - .spacing(14) - .width(Fill); - for section in article.sections { - let mut body = column![text(section.title).size(18)] - .spacing(12) - .width(Fill); - if !section.body.is_empty() { - body = body.push(text(section.body).size(14)); - } - for (index, step) in section.steps.iter().enumerate() { - body = body.push( - row![ - text(format!("{:02}", index + 1)) - .size(13) - .color(style::TEAL) - .width(28), - text(*step).size(14).width(Fill), - ] - .spacing(9) - .width(Fill), - ); - } - if let Some(code) = section.code { - body = body.push( - container(text(code).font(Font::MONOSPACE).size(12).width(Fill)) - .padding(14) - .width(Fill) - .style(style::subtle), - ); - } - content = content.push(container(body).padding(20).width(Fill).style(style::card)); - } - if let Some((page, label)) = article.destination { - content = content.push( - button(text(label).size(13)) - .padding([11, 15]) - .on_press(Message::Page(page)), - ); - } - if topic == Topic::FirstProject { - content = content.push(text("For a worked model, open the water-pressure walkthrough. Copy the whole examples/release1 folder and open the copied project.arcadia before editing; read its README.md. If you use Save as, also copy evidence/ beside the new entry: Save as copies model source, not external artifact targets.").size(13)); - content = content.push( - button(text("Open water-pressure walkthrough").size(13)) - .padding([11, 15]) - .style(style::nav) - .on_press(Message::Home(crate::home::Event::Walkthrough)), - ); - content = content.push( - button(text("Open coastal rescue example").size(13)) - .padding([11, 15]) - .style(style::nav) - .on_press(Message::Request(Pending::Demo)), - ); - } - content = content.push(text("CONTINUE EXPLORING").size(11).color(style::MUTED)); - for related in article.related { - content = content.push(topic_button(*related, false)); - } - scrollable(content.padding(iced::Padding { - right: 12., - bottom: 24., - ..Default::default() - })) - .id(ARTICLE_SCROLL_ID) - .width(Fill) - .height(Fill) - .into() -} - -pub fn view(state: &State) -> Element<'_, Message> { - let matches = search(&state.query); - let mut navigation = column![].spacing(5).width(Fill); - let mut category = ""; - for topic in &matches { - if category != topic.category() { - category = topic.category(); - navigation = navigation.push( - container(text(category).size(10).color(style::MUTED)).padding(iced::Padding { - top: 12., - bottom: 4., - left: 8., - right: 4., - }), - ); - } - navigation = navigation.push(topic_button(*topic, state.topic == *topic)); - } - let topic_list = container(scrollable(navigation.padding(iced::Padding { - right: 8., - ..Default::default() - }))) - .width(200) - .height(Fill); - let article: Element<'_, Message> = if matches.is_empty() { - container(column![ - text("No help topics found").size(23), - text("Try a modeling concept such as allocation, scenario, data type, Git or API. Help search does not search your model.").size(14).color(style::MUTED), - button(text("Clear search").size(13)).padding([10, 14]).on_press(Message::Guide(Event::Search(String::new()))), - ].spacing(16).max_width(520)).padding(24).width(Fill).into() - } else { - // Topic changes also issue an explicit article-only scroll reset. - keyed_column([(state.topic, article_view(state.topic))]) - .width(Fill) - .height(Fill) - .into() - }; - column![ - column![ - text("Engineering guide").size(25), - text("Practical workflows, modeling concepts and reference · available offline") - .size(12) - .color(style::MUTED), - ] - .spacing(5), - row![ - text_input("Search help: allocation, scenario, Git…", &state.query) - .on_input(|query| Message::Guide(Event::Search(query))) - .padding(11) - .size(14) - .width(Fill), - button(text("Clear").size(12)) - .padding(11) - .style(style::nav) - .on_press_maybe( - (!state.query.is_empty()).then(|| Message::Guide(Event::Search(String::new()))) - ), - ] - .spacing(8) - .width(Fill), - text(format!("{} topics", matches.len())) - .size(11) - .color(style::MUTED), - row![topic_list, article] - .spacing(20) - .width(Fill) - .height(Fill), - ] - .spacing(14) - .padding(22) - .width(Fill) - .height(Fill) - .into() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Default)] - struct ScrollProbe { - snaps: Vec>>, - } - - impl iced::advanced::widget::operation::Scrollable for ScrollProbe { - fn snap_to( - &mut self, - offset: iced::advanced::widget::operation::scrollable::RelativeOffset>, - ) { - self.snaps.push(offset); - } - fn scroll_to( - &mut self, - _: iced::advanced::widget::operation::scrollable::AbsoluteOffset>, - ) { - panic!("Guide navigation must snap to the article start"); - } - fn scroll_by( - &mut self, - _: iced::advanced::widget::operation::scrollable::AbsoluteOffset, - _: iced::Rectangle, - _: iced::Rectangle, - ) { - panic!("Guide navigation must not use a relative pixel scroll"); - } - } - - #[tokio::test] - async fn guide_topic_navigation_resets_only_article_and_same_topic_search_keeps_scroll() { - use iced::futures::StreamExt; - let mut app = crate::app::App::with_project(oa_core::Project::empty()); - app.page = Page::Guide; - let accepted = app.document.snapshot(); - let revision = app.document.revision(); - for (event, topic, reset) in [ - (Event::Topic(Topic::Scenarios), Topic::Scenarios, true), - ( - Event::Search("sender lifeline".into()), - Topic::Scenarios, - false, - ), - // Native reproduction: related topic after scrolling Scenarios. - (Event::Topic(Topic::Exchanges), Topic::Exchanges, true), - (Event::Search("If-Match 412".into()), Topic::Server, true), - (Event::Search("If-Match".into()), Topic::Server, false), - ( - Event::Search("nonexistent-topic-987".into()), - Topic::Server, - false, - ), - (Event::Search(String::new()), Topic::Server, false), - (Event::Topic(Topic::Server), Topic::Server, false), - ] { - let mut article = ScrollProbe::default(); - let mut navigation = ScrollProbe::default(); - if let Some(mut stream) = - iced_runtime::task::into_stream(app.update(Message::Guide(event))) - { - while let Some(action) = stream.next().await { - let iced_runtime::Action::Widget(mut operation) = action else { - panic!("Guide navigation must not schedule model or external actions"); - }; - for (id, probe) in [ - (ARTICLE_SCROLL_ID, &mut article), - ("guide-topic-navigation", &mut navigation), - ] { - operation.scrollable( - Some(&iced::advanced::widget::Id::new(id)), - iced::Rectangle::default(), - iced::Rectangle::default(), - iced::Vector::new(0., 900.), - probe, - ); - } - } - } - assert_eq!(app.guide.topic, topic); - assert_eq!(article.snaps.len(), usize::from(reset)); - for offset in article.snaps { - assert_eq!(offset.x, Some(0.)); - assert_eq!(offset.y, Some(0.)); - } - assert!(navigation.snaps.is_empty()); - assert_eq!(app.project(), &accepted.decoded.project); - assert_eq!(app.document.bundle(), &accepted.bundle); - assert_eq!(app.document.revision(), revision); - assert!(!app.document.can_undo()); - } - } - - #[test] - fn search_finds_concepts_in_walkthroughs_and_api_reference() { - assert!(search(" ALLOCATED rescue ").contains(&Topic::FirstProject)); - assert!(search("sender lifeline").contains(&Topic::Scenarios)); - assert!(search("If-Match 412").contains(&Topic::Server)); - assert!(search("Ctrl+Shift+S").contains(&Topic::Files)); - assert!(search("no-such-modeling-topic-987").is_empty()); - assert_eq!(search(" \n\t "), Topic::ALL); - } - - #[test] - fn filtering_and_related_navigation_keep_selection_visible() { - let mut state = State::default(); - state.update(Event::Search("If-Match 412".into())); - assert_eq!(state.topic, Topic::Server); - state.update(Event::Topic(Topic::Nesting)); - assert!(state.query.is_empty()); - assert_eq!(state.topic, Topic::Nesting); - state.update(Event::Search("nonexistent-topic-987".into())); - assert_eq!(state.topic, Topic::Nesting); - state.update(Event::Search(String::new())); - assert!(search(&state.query).contains(&state.topic)); - } -} +//! Offline engineering help. Topics describe the shared model and real UI controls. +//! +//! This module owns only reading/navigation state. Application navigation and demo +//! loading use normal App messages, so help never bypasses the model's draft guards. +use crate::{ + app::{Message, Page, Pending}, + style, +}; +use iced::{ + Element, Fill, Font, + widget::{button, column, container, keyed_column, row, scrollable, text, text_input}, +}; + +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum Topic { + #[default] + FirstProject, + Perspectives, + Participants, + Nesting, + Capabilities, + Exchanges, + Chains, + Scenarios, + States, + Interfaces, + Transitions, + SemanticReview, + Artifacts, + Source, + ModelChecks, + Recovery, + Files, + Server, +} + +#[derive(Debug, Clone, Default)] +pub struct State { + pub query: String, + pub topic: Topic, +} + +#[derive(Debug, Clone)] +pub enum Event { + Search(String), + Topic(Topic), +} + +pub(crate) const ARTICLE_SCROLL_ID: &str = "engineering-guide-article"; + +impl State { + /// Returns whether navigation selected a different article. + pub fn update(&mut self, event: Event) -> bool { + let previous = self.topic; + match event { + Event::Search(query) => { + self.query = query; + let matches = search(&self.query); + if !matches.contains(&self.topic) + && let Some(first) = matches.first() + { + self.topic = *first; + } + } + Event::Topic(topic) => { + self.topic = topic; + // Related/contextual links always reveal the requested topic. + self.query.clear(); + } + } + self.topic != previous + } +} + +impl Topic { + const ALL: [Self; 18] = [ + Self::FirstProject, + Self::Perspectives, + Self::Participants, + Self::Nesting, + Self::Capabilities, + Self::Exchanges, + Self::Chains, + Self::Scenarios, + Self::States, + Self::Interfaces, + Self::Transitions, + Self::SemanticReview, + Self::Artifacts, + Self::Source, + Self::ModelChecks, + Self::Recovery, + Self::Files, + Self::Server, + ]; + + pub fn title(self) -> &'static str { + match self { + Self::FirstProject => "Build your first model", + Self::Perspectives => "Operational & System Analysis", + Self::Participants => "Participants, roles & work", + Self::Nesting => "Ownership, allocation & views", + Self::Capabilities => "Missions & capabilities", + Self::Exchanges => "Exchanges & dataflow", + Self::Chains => "Processes & functional chains", + Self::Scenarios => "Scenarios & alternatives", + Self::States => "States, modes & transitions", + Self::Interfaces => "Data types, ports & interfaces", + Self::Transitions => "Move between OA & SA", + Self::SemanticReview => "Review semantic changes", + Self::Artifacts => "Connect engineering evidence", + Self::Source => "Textual editing & Git", + Self::ModelChecks => "Read model checks", + Self::Recovery => "Drafts & crash recovery", + Self::Files => "Files & keyboard shortcuts", + Self::Server => "Hosted models & REST API", + } + } + + fn category(self) -> &'static str { + match self { + Self::FirstProject | Self::Perspectives => "START HERE", + Self::Participants | Self::Nesting | Self::Capabilities | Self::Exchanges => { + "MODEL THE SYSTEM" + } + Self::Chains | Self::Scenarios | Self::States | Self::Interfaces => "DESCRIBE BEHAVIOR", + Self::Transitions | Self::SemanticReview | Self::Artifacts | Self::Source => { + "CONNECT YOUR WORK" + } + Self::ModelChecks | Self::Recovery | Self::Files | Self::Server => "REFERENCE", + } + } +} + +struct Article { + summary: &'static str, + keywords: &'static str, + sections: &'static [Section], + related: &'static [Topic], + destination: Option<(Page, &'static str)>, +} + +struct Section { + title: &'static str, + body: &'static str, + steps: &'static [&'static str], + code: Option<&'static str>, +} + +const fn explain(title: &'static str, body: &'static str) -> Section { + Section { + title, + body, + steps: &[], + code: None, + } +} + +const fn walkthrough(title: &'static str, steps: &'static [&'static str]) -> Section { + Section { + title, + body: "", + steps, + code: None, + } +} + +const fn example(title: &'static str, body: &'static str, code: &'static str) -> Section { + Section { + title, + body, + steps: &[], + code: Some(code), + } +} + +fn article(topic: Topic) -> Article { + match topic { + Topic::FirstProject => { + const { + Article { + summary: "Create a small operational model, then derive the system contribution. Use concrete names for work and keep your first diagram focused.", + keywords: "getting started tutorial beginner quick start new example rescue coordinator allocation", + sections: &[ + walkthrough( + "Create an operational context", + &[ + "Choose New in the top bar. Use Project properties to name the model and describe its purpose. Apply changes commits the property draft.", + "On Architecture, choose Create architecture view. Architecture · allocated work shows work inside its performer.", + "Expand the diagram's side palette to see tool names. Choose Entity, click its position in the diagram, enter Rescue coordinator and press Enter. Escape cancels the draft. Use Actor for an external participant such as a Vessel in distress. Right-clicking a blank area also offers Create Entity here.", + "Right-click Rescue coordinator and choose Add activity. Enter Assess incident and press Enter to create the activity and its performer allocation together. Ordinary Activity creation in the palette leaves work unallocated; use + Relationship with Allocation to assign existing work. The allocated-work view shows work inside its performer.", + "Use Add existing to place an element that is already in the model. The initial choices match this view; no search is required. Include already shown offers Locate for elements already placed here.", + "Add a second activity for another participant. Select an activity, choose + Relationship, select Interaction and choose the compatible target. Name the exchange after what is conveyed.", + "Visit Model checks to review unfinished work. Save all writes the accepted model and applied drafts to your chosen file.", + ], + ), + explain( + "Continue into system design", + "Use Create System counterpart on an operational element to create a linked SA element. Choose System for the system under study or System actor for an external participant, then choose the result diagram. The original operational element stays in OA. The transition tutorial explains scope and reuse.", + ), + walkthrough( + "Build a reviewable OA → SA baseline", + &[ + "State the mission and capability: for example, protect life at sea by coordinating a rescue. Link the mission to the capability and identify the participants that support it.", + "Describe the normal operational Process and an exception Scenario using the activities and Interactions already in the architecture. Give the scenario an explicit Context capability and connect the process with Chain involvement.", + "Review which operational work the System will support. Create or reuse its SA counterparts, then check the resulting System boundary, external actors, leaf Function allocations and exchanged payloads.", + "Refine the system Functional chain, Scenario and State machine. Specify data contracts and entry/do/exit work where they clarify responsibilities. Link requirements, analyses or tests to the particular records they support.", + "Read Model checks, review changes through counterpart traces, and inspect both perspectives. Save the complete source project and review its Git diff as one engineering milestone.", + ], + ), + explain( + "Explore a worked model", + "Open the coastal rescue example to inspect allocation, decomposition, repeated scenario messages, operating modes, typed interfaces and artifact references. Loading the example uses the normal unsaved-work prompt. It replaces the current project only after that workflow completes.", + ), + ], + related: &[ + Topic::Perspectives, + Topic::Participants, + Topic::Nesting, + Topic::Transitions, + Topic::ModelChecks, + ], + destination: Some((Page::Diagram, "Open Architecture")), + } + } + } + Topic::Perspectives => { + const { + Article { + summary: "Operational Analysis asks what people and organizations need to accomplish. System Analysis asks what the system must do within that operation.", + keywords: "oa sa arcadia capella system architecture operational need boundary abstraction actor system of interest", + sections: &[ + explain( + "Operational Analysis: understand the need", + "Describe entities and actors, their activities, roles, interactions and operational capabilities without assuming a particular technical solution. An activity such as Assess incident states work in the operation; it does not imply which software module implements it.", + ), + explain( + "System Analysis: define the contribution", + "Describe one System under study, external System actors, Functions, functional exchanges, missions and system capabilities. A participant's operational activity may be supported by several system functions or may remain outside the system boundary. Review that engineering decision explicitly.", + ), + explain( + "Switching perspective does not transition the model", + "The 01 Operational and 02 System controls change the visible perspective and creation choices. To represent an element in the other layer, use Create System counterpart or Create Operational counterpart. Both elements remain linked by a trace.", + ), + explain( + "Keep the boundary explicit", + "Separate work performed by the System from work performed by external actors. Allocate leaf functions to the responsible participant. Logical and Physical Architecture belong to later design layers; a system-level function is not a software class or a physical component.", + ), + ], + related: &[Topic::FirstProject, Topic::Capabilities, Topic::Transitions], + destination: Some((Page::Catalog, "Open Model explorer")), + } + } + } + Topic::Participants => { + const { + Article { + summary: "Represent who participates separately from the work they perform. Decomposition and responsibility answer different engineering questions.", + keywords: "entity actor participant role realization assignment leaf activity function performer decomposition responsibility", + sections: &[ + walkthrough( + "Create and allocate work", + &[ + "Choose the Operational or System perspective. Create participants using Entity/Actor in OA or System/System actor in SA.", + "In the diagram's side palette, choose Activity in OA or Function in SA, click its position, enter a name and press Enter. Escape cancels without creating an element. Select a card and press F2 to rename it in place. For semantic decomposition, use Model explorer, select existing work and use + In selected to create smaller work units.", + "Right-click a participant and choose Add activity or Add function to create new work and its allocation together. To assign existing leaf work, use + Relationship with Allocation from the participant to the work. Model explorer also offers + Allocated when a participant and the appropriate work kind are selected.", + "Choose Architecture · allocated work to review responsibilities. Choose Functional dataflow to review the activity/function hierarchy and its exchanges independently.", + ], + ), + explain( + "Operational roles", + "A Role describes a responsibility independently of a particular entity. Role realization links an Entity or Actor to a Role. Role assignment links a Role to an Activity. These relations express role responsibilities; an Allocation identifies the concrete performer of leaf work.", + ), + explain( + "Name work as an action", + "Prefer Assess incident or Deliver electrical power for work, and Rescue coordinator or Power controller for participants. A meaningful description records assumptions, input/output expectations and the decision behind an allocation.", + ), + explain( + "Creation choices are constrained", + "+ In selected is enabled only for a legal semantic parent. + Allocated appears when the selected participant can perform the chosen work. Pickers show compatible endpoints. If a control is unavailable, check the active perspective, selected owner and element kind.", + ), + ], + related: &[Topic::Nesting, Topic::Exchanges, Topic::Capabilities], + destination: Some((Page::Catalog, "Open Model explorer")), + } + } + } + Topic::Nesting => { + const { + Article { + summary: "A diagram is a view of the model. Its boxes, positions and nesting reveal selected relationships; moving a box does not redefine engineering ownership.", + keywords: "containment semantic parent hierarchy nested nesting allocation deployment diagram placement arrange fit fold hide zoom resize routing connection bend waypoint detour label reset selective visibility category categories coverage process context duplicate copy items", + sections: &[ + explain( + "Three things to distinguish", + "Semantic parent records ownership: an activity contains subactivities, or a scenario owns occurrences. Allocation records who performs leaf work. A placement records where one element appears in a diagram. One element can have placements in several views without becoming several semantic elements.", + ), + walkthrough( + "Browse a large model", + &[ + "Open Model explorer. Choose a group, then a container to see its direct children. Breadcrumbs return to its owner or group; Allocated work is a separate list of responsibilities.", + "Search names, kinds or @aliases in the left outline. Matching elements retain their ancestor context; the main browser lists up to 100 results per page. Select a result to open its details and clear the search.", + "Use Collapse all to fold the outline and Locate to reveal the selected element. Search keeps matching ancestry open; clear the search before folding individual groups or containers.", + ], + ), + walkthrough( + "Choose and edit the relationship you mean", + &[ + "Use the diagram selector or + View to work with a focused diagram. View properties edits its name, perspective, projection and Saved view visibility. Apply changes commits the view settings.", + "Choose Containment for semantic children, Architecture · allocated work for responsibilities, Functional dataflow for work and exchanges, or Missions & capabilities for outcomes. All relationships is a broader exploration view; OA ↔ SA traceability displays counterparts.", + "Select an element and edit Semantic parent to change ownership. Use an Allocation relationship to change responsibility. Apply changes validates the semantic change.", + "Drag nodes to arrange the current view, scroll to zoom and use Fit to frame its contents. Arrange changes diagram geometry. Export SVG produces a shareable view.", + "Select a diagram node to access Placement, Fold / unfold and Hide. Hide removes its view placement; Delete from model removes model records and requires review of the affected references.", + ], + ), + explain( + "Keep diagrams readable", + "Use separate views for responsibility, decomposition and cross-layer coverage. Place existing elements with Show in this diagram. A placement's Diagram field can relocate it while preserving its identity and artifact references. Do not duplicate a semantic element merely to show it in another view.", + ), + walkthrough( + "Save a focused exchange view", + &[ + "To preserve the current presentation, right-click empty diagram space and choose Duplicate view. The copy has independent placements, routes and visibility settings while representing the same model elements and relationships. View-specific artifact links stay with the original view.", + "Open View properties. Under Work exchange categories or Participant exchange categories, uncheck All, including uncategorized exchanges, then Include the categories you need. Selected categories combine by union; none selected shows none of that family. Work means OA Interactions or SA Functional exchanges; participant means OA Communication means or SA Boundary exchanges, including supported legacy records.", + "Work exchange coverage across the whole model offers All work exchanges, Used in behavior and Outside behavior. This uses explicit process, chain and scenario occurrences, including reuse. To focus on particular behavior, uncheck All work contexts and Include those processes, chains or scenarios. Context and coverage intersect with categories; no selected contexts means no work lines.", + "Choose Exchange labels: Names, Items or Names and items. In Individual lines and omission reasons, search a name or alias and check Hide to retain an explicit hide. Unchecking Hide removes only that reason; other settings or missing endpoints may still omit the line. Clear all explicit hides removes the manual hide list.", + "Apply changes and inspect the result. To restore all eligible lines, enable All relationship line kinds, both category All choices and All work contexts, choose All work exchanges, and clear explicit hides. Empty relationship-kind selection shows no lines. These settings do not remove nodes or model facts; a line still needs endpoints eligible for the projection.", + ], + ), + explain( + "Read allocation summaries and ports", + "A dashed parent activity/function with an italic name and SUMMARY heading groups leaves assigned to the same performer. It has no direct allocation of its own; its inspector explains the leaf performer. Small ports belong to the boundary of their visible owner and are selectable model elements. Folding a container hides its inner work and ports; summary exchange lines do not invent new ports on the container.", + ), + walkthrough( + "Route connections around the model", + &[ + "Select an architecture connection and drag a segment to add a bend. Right-click a line for Add bend here; right-click a circle for Remove bend. With the canvas focused, Insert adds a bend and Delete removes the selected bend. Drag the label independently. Escape cancels a gesture; Undo reverses one completed drag. Shift+F10 opens the selected diagram item's context menu; use arrow keys and Enter to choose an action.", + "Automatic orthogonal routing avoids cards and container headers where space permits. Amber connections need attention: move an obstructing element or a bend. In Select / move, drag a port to any border of its owner. Manual positions are per diagram; right-click Reset automatic position restores connection-aware placement. Ports remain attached to their actual owners. Manual routes are saved separately for each diagram and appear in source, API and SVG export.", + "Right-click a connection and choose Reset routing to clear its pins and label position in this view. The Properties Actions menu provides the same commands. Arrange preserves pins. Moving one endpoint keeps pins fixed; moving a container carrying both endpoints translates their pins. When folding promotes an endpoint, expand it again to resume and edit its saved route.", + ], + ), + explain( + "What nesting does not imply", + "Containment in an allocation projection expresses a performer relationship. It is not a physical deployment statement. Physical deployment is a different architectural concern; do not infer hardware installation from an OA/SA box layout.", + ), + ], + related: &[Topic::Participants, Topic::Perspectives, Topic::Artifacts], + destination: Some((Page::Diagram, "Open Architecture")), + } + } + } + Topic::Capabilities => { + const { + Article { + summary: "Capabilities describe an ability to achieve an outcome. Missions explain the purpose served by those abilities. Both Operational and System Analysis have missions, capabilities and participants; activities and functions describe supporting work.", + keywords: "capability mission outcome operational capability system capability involvement contribution trace coverage use case", + sections: &[ + walkthrough( + "Connect outcomes to the work", + &[ + "Create an OA Capability such as Protect life at sea. Describe the operational outcome and the conditions in which it matters.", + "Select a relevant participant, role or activity. Use + Relationship → Capability involvement and choose the capability as the target.", + "Create an Operational mission in OA, or a Mission in SA. Select it and choose Exploit capability to connect it to a capability in the same perspective. Involve participant associates an entity or actor with the mission.", + "Connect System participants/functions to the capability using Capability involvement. Connect a Process or Functional chain to a capability using Chain involvement.", + "Review OA/SA counterpart links in Traceability and read Model checks for incomplete coverage. A named capability alone does not demonstrate that the necessary behavior exists.", + ], + ), + walkthrough( + "Include, extend or specialize a capability", + &[ + "Select a capability in the diagram or explorer, then open Actions at the top of its properties. Include capability identifies behavior that is always part of it; choose the included capability as the target.", + "Extend capability connects this extending capability to the extended capability. Enter the condition under which the extension adds behavior. An empty condition remains editable and appears in Model checks.", + "Specialize capability connects a specific capability to a more general one. Inclusion and specialization cannot form cycles.", + "The relationship preview names both ends and generates a readable name. Apply adds one undoable change; Cancel preserves the model. Include and extend labels remain visible in the diagram.", + ], + ), + explain( + "Use scenarios as concrete evidence", + "A scenario illustrates normal response, degraded operation or an exception. In its Properties, choose Context to name the capability it supports in the same perspective; record assumptions and pre/postconditions too. Connect a Process or Functional chain with Chain involvement. Descriptive prose explains the intent, while these explicit references make the context inspectable.", + ), + explain( + "Avoid implementation names at this stage", + "Protect life at sea is a capability. Coordinate rescue is work. Send a JSON request is usually a design choice. Start with the engineering obligation and add technical detail when the perspective calls for it.", + ), + ], + related: &[ + Topic::Perspectives, + Topic::Chains, + Topic::Scenarios, + Topic::Transitions, + ], + destination: Some((Page::Catalog, "Open Model explorer")), + } + } + } + Topic::Exchanges => { + const { + Article { + summary: "An exchange says what passes between work or across a participant boundary. Its payload, endpoints and allocation make that statement precise.", + keywords: "interaction functional exchange boundary exchange communication mean participant directed bidirectional category categories flow information material energy event source target port exchange allocation", + sections: &[ + walkthrough( + "Create a typed exchange", + &[ + "Create the source and target activities/functions in one perspective. Give each an engineering name and allocate leaf work to its responsible participant.", + "Choose Interaction or Functional exchange in the diagram palette. Click the source, then a highlighted compatible target, or drag between them. Functional exchanges connect output ports to input ports; function cards create or reuse real ports in the same transaction. Middle-button drag pans while keeping the source; arrow keys also pan when the drawing canvas has focus. Click empty space to restart, or Escape to cancel.", + "Create an Exchange item under Data & interfaces. Choose its nature (Information, Material or Energy) and its mechanism (Flow, Event, Operation or Shared data). Describe the payload independently of the connection.", + "Select the exchange in Model explorer or the diagram. Edit its properties to reference the appropriate exchange items and available ports; Apply changes validates the complete record.", + "In System Analysis, choose Boundary exchange and connect compatible component ports. Flow goes from Out to In; Standard is unoriented. Select / move returns to arranging. Exchange allocation associates a boundary exchange with the functional exchange it carries.", + ], + ), + explain( + "Flow and sequence are different", + "A dataflow graph shows possible dependencies between activities/functions. It does not by itself impose an execution order. Use a Process, Functional chain or Scenario when you need to describe an ordered path, repeated exchanges or alternatives.", + ), + explain( + "Use engineering payloads", + "Choose Information for data such as a distress report, Energy for delivered power and Material for a transferred substance. Mechanism separately describes how it is exchanged: an Event is a notification, an Operation has parameters, and a Flow carries values or a physical quantity. Data types and fields make payloads precise. Artifact links can point to interface-control documents.", + ), + explain( + "Operational and system boundaries", + "An OA Interaction connects activities. A Communication mean connects operational Entities or Actors directly; it requires no system ports. In Operational Analysis, choose Communication means and connect its participants. Its properties hold carried items and direction; the relationship form offers Directed source → target (unchecked: bidirectional). Existing OA Boundary exchanges with Legacy ports remain editable for compatibility.", + ), + explain( + "Relate operational transport and concerns", + "Use Exchange allocation to associate a Communication mean with the Interactions it carries. Allocate each activity to its actual performer and check that those performers match the communication endpoints and direction. Create an Exchange category at the perspective root, choose Work exchanges or Participant exchanges in its properties, and Include existing exchange members. Categories name reusable concerns for view filtering; they do not change endpoints, allocate work or imply execution. A category may be empty, and an exchange may belong to several categories.", + ), + ], + related: &[ + Topic::Interfaces, + Topic::Chains, + Topic::Scenarios, + Topic::Artifacts, + ], + destination: Some((Page::Catalog, "Open Model explorer")), + } + } + } + Topic::Chains => { + const { + Article { + summary: "An operational Process or system Functional chain connects contributions of existing work and exchanges, including alternatives and reusable procedures. It explains how work contributes to an outcome.", + keywords: "process functional chain ordered occurrence sequence path involvement dataflow exchange repeated steps caller contextual qualified assembly common work focus reuse", + sections: &[ + walkthrough( + "Describe a path through exchanges", + &[ + "A blank path offers Add first activity/function contribution. You can use existing work or stage a new source in that form; exchanges still need explicit compatible source and target work.", + "Open Behavior & scenarios → New. Choose Operational process in OA or Functional chain in SA, enter its exact name, then Create and open. The new behavior belongs at the layer root and opens immediately; Undo removes the creation as one edit.", + "Open Tools and choose Work or Exchange, then a highlighted position. In Work, Create new source keeps the contribution name and position while you name its Activity/Function and choose its semantic parent. Use this new source stages it; Create commits source and contribution in one Undo. An adjacent exchange may require a particular existing work identity.", + "In Tools, choose Sequence link and click or drag from the preceding contribution to the following contribution. Choose Tools → Select / reorder to drag a selected path endpoint and reconnect it; a contribution's ↕ handle changes its position within a column. These are local contributions and sequence links, not new architecture exchanges.", + "Select a contribution, then add another after it. Tools → Two paths… creates a split, two paths and their merge in one undoable edit. Enter explicit alternative conditions. Fit overview uses the visible canvas area; 100% keeps working scale with scrollbars. If the minimum zoom is reached, scroll to inspect the remainder.", + "Use Chain involvement from the Process/Functional chain to the capability it supports. Read Model checks for unfinished or disconnected paths.", + ], + ), + explain( + "Choose the right behavioral description", + "A chain concentrates on a route through existing exchanges. A scenario can also carry actions, state observations, lifelines and nested fragments. The architecture diagram remains a view of the possible relationships rather than an execution trace.", + ), + explain( + "Review the path as an engineer", + "Check that exchanges express meaningful handoffs and that the links and branch guards support the selected outcome. Use Tools → Reuse to involve an existing process or chain as a nested reference. For an imported ordered exchange path, Append exchange… continues that path; Convert ordered path to explicit links exposes its implied links for topology editing. Conversion preserves the original order in one undoable change.", + ), + walkthrough( + "Connect a particular use of a procedure", + &[ + "Choose Focus canvas for more working space; Restore panels brings the explorer and properties back. Select a contribution inside a reusable preview, then open Selection details to read its complete caller and reference identity.", + "In Selection details, use Expand nested reference or Fold nested reference to inspect that instance. Open source or Open contribution definition visits the shared definition; Back to caller returns to the original caller context.", + "Open Tools → Qualified link / assembly… and choose its source and target. Use Enter reference to open an instance's contributions, Up one reference for its caller scope, or Root for the link owner. Search and page through all contributions in that scope, then select the exact endpoint. The first and second uses of a procedure remain different choices even when their work has the same name.", + "Choose Precedence to describe order and its guard, Exchange assembly to identify a work exchange between the contributions, or Common work incidence to state that both use the same work. Select the engineering identity when required, then Apply link.", + ], + ), + explain( + "Keep caller intent separate from the definition", + "A link into the first assessment's second contribution belongs to that caller. It does not change the shared assessment or its second use, and it does not delay the first contribution by targeting the whole procedure. Common work records shared identity without merging executions. Model checks identify the particular caller path and report incomplete analysis when expansion cannot be completed.", + ), + ], + related: &[Topic::Exchanges, Topic::Capabilities, Topic::Scenarios], + destination: Some((Page::Behavior, "Open Behavior & scenarios")), + } + } + } + Topic::Scenarios => { + const { + Article { + summary: "Use a scenario to tell a concrete behavioral story, including repeated messages, actions, state observations and guarded alternatives.", + keywords: "scenario sequence lifeline sender receiver fragment branch alternative loop guard parallel strict optional occurrence exchange action state interface operation allocation call reply unresolved", + sections: &[ + walkthrough( + "Create a scenario", + &[ + "Open Behavior & scenarios → New → Scenario and enter its name. Explicitly choose Activities or Participants in OA, or Functions or System boundary in SA. Add optional context only when needed: this is a reference, while the scenario itself belongs at the layer root. Create and open starts the empty scenario.", + "Use Add first lifeline on an empty scenario, or + Lifeline. Choose existing work/participant or Create new source. The explicit viewpoint limits its kinds; choose participant kinds deliberately. Source name and lifeline name are separate, and the source semantic parent is independent of the scenario. No allocation or capability involvement is inferred.", + "Choose + Message and its style. Call + correlated reply creates both occurrences in one edit. Select the exchange and compatible sending/receiving lifelines. Reply to existing call offers valid unanswered calls.", + "Use Action, Observe state or Fragment for other behavior. A local timer has an expression and a lifeline, with no global exchange. Create and delete messages describe participant lifetime; Model checks also flag risks inside repeated loops.", + "Choose a message style and Draw message. Start at a highlighted sequence position on a lifeline, then click or drag to the receiving lifeline. The form retains the chosen position while you select the exchange and details. Escape cancels the gesture.", + "In Select / reorder, click an occurrence and drag its ↕ handle to another position within the same sequence. The ordered list's up/down controls provide another way to reorder. Select an occurrence to inspect its particular use of the exchange, including its message values.", + ], + ), + walkthrough( + "Describe alternatives", + &[ + "Choose Fragment and select ALT, PAR, LOOP or OPT. Enter the branch conditions; the tool creates the fragment and its operands together.", + "Use the Destination selector in subsequent message or action forms to choose the intended operand. New occurrences append to that operand's ordered behavior.", + "Use Reuse scenario to refer to existing behavior. Bind every referenced lifeline to a compatible local lifeline explicitly. Open source follows the original scenario without copying its contents.", + "Inspect the scenario projection and Model checks. State exactly what each guard means, including units and assumptions where relevant.", + ], + ), + walkthrough( + "Invoke an interface operation", + &[ + "In an SA BoundaryInterface scenario, create or edit a Call or Asynchronous message. Choose its boundary exchange and actual sending and receiving lifelines first. The receiving Standard component port must provide the interface; the sending port must require a compatible interface.", + "Enable Invoke a specific interface operation and choose the exact eligible allocation. Two parameterless operations on one channel remain distinct. Choose Keep operation unresolved when the operation decision is unfinished; existing value expressions remain available.", + "For a Reply, inspect Reply operation from Call. It derives the selection from that particular Call and cannot be selected independently. Enter values for the selected operation's call or reply parameters; incompatible values must be corrected explicitly.", + "When transitioning that invocation to OA, explicitly choose to omit the technical operation and enter the engineering rationale in the preview or semantic review row. The SA source keeps its operation identity.", + ], + ), + explain( + "Conditions are engineering statements", + "Guards, actions and effects document behavior; a drawn scenario does not execute code or prove timing. Add evidence references when a simulation, test or analysis establishes a stronger claim.", + ), + explain( + "Keep uses distinct from definitions", + "Several occurrences can refer to one exchange without duplicating it. Edit occurrence properties for this message's sender, receiver and value expressions; edit the exchange for the shared endpoints and payload. A Reply refers to a particular Call, not merely a similar name. Reordering across fragment operands or changing lifeline bindings needs an explicit structural edit, not an accidental drag across a boundary.", + ), + ], + related: &[ + Topic::Chains, + Topic::States, + Topic::Exchanges, + Topic::Artifacts, + ], + destination: Some((Page::Behavior, "Open Behavior & scenarios")), + } + } + } + Topic::States => { + const { + Article { + summary: "Describe the conditions in which the operation or system behaves differently. States and modes belong to regions within a state machine.", + keywords: "state machine mode region initial final transition trigger guard effect entry exit do availability degraded nominal diagram draw reconnect local internal timed fold unfold placement pan zoom undo", + sections: &[ + walkthrough( + "Build a state machine", + &[ + "In Behavior & scenarios → New → State machine, enter its name and search for the existing same-layer participant that owns it. Candidate labels show parent context and alias to distinguish repeated names.", + "If the owner does not exist, Create new owner retains the machine name and first-view option. Choose its participant kind and semantic parent explicitly. Use this new source returns without changing the model; Create and open commits owner, machine and optional view together.", + "Optionally select Create the first state view, then Create and open. The new machine starts without regions or states. For an existing machine, Create diagram places its existing regions and vertices in a saved view; + View creates another independent layout.", + "Choose + Region and click the highlighted machine, or a State/Mode for a nested region. Choose Initial, State, Mode or another kind from + State / symbol, then click its highlighted owning region. Rename the new element in Properties.", + "Choose Transition and drag from a source vertex to a compatible target, or click the source and then the target. In Select / move, drag states or containers to arrange them; a container moves its descendant placements without changing ownership.", + "Select a drawn transition and drag its endpoint handle to reconnect it. Its right-click menu also offers Reconnect source and Reconnect target; choose one, then click a compatible vertex.", + "Right-click a state or the background for New transition with details… to create Local, Internal or timed behavior. Right-click an existing transition and choose Edit transition to edit its kind, triggers, guard and effects. EntryPoint outgoing transitions use Local. Apply accepts the complete form; invalid drafts remain available for correction.", + ], + ), + explain( + "Navigate and recover a view", + "Middle-button drag pans; scroll or use −/+ to zoom, and Fit frames the diagram. Right-click a container for Fold contents or Unfold contents, or choose Placement properties for exact coordinates and size. Hide from this diagram removes only its placement; Show an element not yet in this view restores missing elements. Escape cancels a drawing gesture. Undo restores each completed gesture or menu edit, and Model checks identifies unfinished structures.", + ), + explain( + "One saved diagram across editors", + "Each state view stores a reference to its state machine and its own placements. Text, JSON and REST edit those same diagram records, including the persisted machine context; saving and reopening retains them. Merely opening the behavior page does not create a diagram. State-view edits preserve other diagram layouts.", + ), + explain( + "Availability and state actions", + "Availability links a participant or behavior to a State/Mode in which it is available. State action links a State/Mode to existing Activity/Function work and records Entry, Do or Exit. The saved state view shows those named action rows above internal transitions and nested regions. Select a row to edit its actual State action relationship. The linked work remains in its own semantic owner; the row does not contain or copy it. Free-text entry/do/exit notes remain separate engineering prose.", + ), + walkthrough( + "Add timing without losing an existing transition", + &[ + "Select the transition and choose Edit transition. Its current endpoints, kind, existing triggers, guard and effects populate the form.", + "Choose Add a time event with this transition, select After for a delay or At for a time, then enter a time expression such as 30 s. This adds a typed Event to the trigger alternatives; it does not replace existing triggers.", + "Review the complete transition before Apply. The event and transition update are one undoable change. If the form is rejected, correct the retained values; a rejected candidate creates no orphan Event.", + "Save the entry and any included source files. On reopening, check the transition's trigger list and the Event expression. An expression documents timing intent; it is not evaluated by a simulator.", + ], + ), + explain( + "Keep conditions testable", + "Prefer a guard such as battery_level < reserve_threshold over a vague phrase such as low battery, and define the quantities elsewhere in the model or linked evidence. A state diagram documents the intended behavior; it is not a running state-machine simulator.", + ), + ], + related: &[ + Topic::Scenarios, + Topic::Participants, + Topic::Recovery, + Topic::Artifacts, + ], + destination: Some((Page::Behavior, "Open Behavior & scenarios")), + } + } + } + Topic::Interfaces => { + const { + Article { + summary: "Define exchanged payloads independently of the ports and participants that carry them. Reuse the same data definitions in related exchanges.", + keywords: "data interface port direction input output inout return contract provision requirement exchange item enum enumeration record primitive field value type multiplicity minimum maximum bounds default unit specialization inheritance association storage reference recursive path transmission acquisition protocol", + sections: &[ + walkthrough( + "Define a reusable payload", + &[ + "Open Data & interfaces and create an Exchange item. Choose Information, Material or Energy for nature; choose Flow, Event, Operation or Shared data for mechanism. Keep Unspecified when the engineering decision is still open.", + "Create a Data type for a reusable definition. Choose Boolean, Integer, Real, String, Enumeration or Record. Numeric bounds are inclusive; String bounds count Unicode characters. A typed default must satisfy those bounds or enumeration literals. Units annotate quantities without converting them.", + "Create fields within an Exchange item or Record type. Set Value type, minimum/maximum count, unit and optional default. A default can be absent, explicitly empty when minimum is zero, or a list of typed scalar values. A type default describes one element; it does not create a required collection.", + "Select the exchange and reference the payload items. Apply changes and check the result in Model checks.", + ], + ), + explain( + "Operation parameters and message values", + "An Operation's direct fields are parameters. In and InOut supply call values; Out, InOut and Return supply reply values, from the provider's perspective. There is at most one Return parameter. Qualified paths distinguish origin.latitude from destination.latitude when both use the same Coordinate record. Nested fields inherit the parameter direction. Message expressions are annotations, not evaluated values.", + ), + explain( + "Reuse, ownership and references", + "A Record or Exchange item can specialize one compatible base. Inherited fields keep their original owner and identity; edit the base to change them. Value fields compose a payload. Reference fields refer to separately identified Record objects and stop payload expansion. Optional recursive values and reference cycles are valid; a mandatory value cycle warns that no finite complete value exists. Data associations describe named reference ends, multiplicities and navigation between Record types without creating contained fields.", + ), + walkthrough( + "Allocate an item to interfaces", + &[ + "Create an Interface and allocate an Exchange item. Select its allocation row to edit transmission, acquisition and order; Open item navigates to the shared payload definition.", + "Two interfaces may allocate the same item with different protocols. Attach CAD, code or document evidence to the allocation when it concerns that particular contract.", + "Removing an allocation keeps the shared item and other allocations. If a live boundary depends on it, first revise that boundary contract. Undo restores an accepted removal.", + "An Interface may specialize one base and inherit its allocations. Duplicate allocation of an already inherited item is rejected. A specialized item satisfies its base contract; a base item does not satisfy a more specific contract.", + ], + ), + walkthrough( + "Connect participant boundaries", + &[ + "In System Analysis, select a System or System Actor and choose Add port in Model explorer or Properties. Name it, choose Standard or Flow, then Create port. Standard ports describe interfaces; Flow ports have their own input/output orientation. Functions use separate input/output port actions.", + "Use Interface provision and Interface requirement relationships to state who provides or needs the contract.", + "Select a visible component port and choose Draw boundary exchange, then click or drag to a highlighted compatible port. The diagram palette offers the same tool. If the endpoints are outside the diagram, Configure boundary exchange opens the form with owner names and aliases. Create the matching port first if needed. Exchange orientation is separate from Flow port orientation. Exchange allocation associates carried functional exchanges with the boundary connection.", + "Link the detailed interface-control document, CAD connector or schematic through an Artifact reference when an external artifact supplies additional evidence.", + ], + ), + explain( + "Preserve meaning across perspectives", + "Shared kinds such as Data type and Interface still belong to an engineering perspective. Their Engineering perspective property is explicit. Use counterpart traces for separate OA and SA definitions that correspond; do not assume that similar names create a trace.", + ), + ], + related: &[Topic::Exchanges, Topic::Artifacts, Topic::Transitions], + destination: Some((Page::Data, "Open Data & interfaces")), + } + } + } + Topic::Transitions => { + const { + Article { + summary: "Create or reuse counterpart records across OA and SA through a reviewable preview. Both perspectives keep their identities and remain linked.", + keywords: "transition counterpart bidirectional mapping realization drift conflict merge keep target take source baseline scope dependency reverse update trace reuse", + sections: &[ + walkthrough( + "Review a counterpart transition", + &[ + "Select an operational element and choose Create System counterpart. For a system element, Create Operational counterpart starts the reverse direction. The source stays in its original layer.", + "Choose Include: Selected only, Required references or Whole subtree. Required dependencies must have compatible counterparts before the plan can apply.", + "For each source, choose Create new counterpart or reuse an existing target. For an operational participant, explicitly choose System for the system under study or System actor for an external participant. Use Show result in to choose a compatible diagram; Automatic selects an architecture view for ordinary elements.", + "Read the proposed changes and conflicts. For a descriptive conflict, choose the source value, keep the target value, or enter a merged value. Changing the target/kind requires reviewing that target's decisions again.", + "Apply only after every blocking decision is resolved. The accepted plan is one undoable model change; Cancel preserves the current model.", + ], + ), + explain( + "Review drift deliberately", + "On an existing trace, choose Review counterpart changes… to compare typed content and prior observations. Choose Keep for the destination, Take for the source, or Set final value / Set final kind and payload where offered. Review mapped references and proposed changes, then choose Apply reviewed changes. Independent edits may be intentional; keeping a value records that decision. Manual traces record engineer-defined correspondence. Ordinary edits never synchronize counterparts automatically; an explicit supported transition or review can update selected properties, including for a Manual trace.", + ), + explain( + "Know what reconciliation changes", + "Semantic review supports structural and scalar changes, typed constructors, individual collection rows, explicit ordered collections, existing attachments and artifact links. Several trace rows can contribute to one combined destination proposal. Candidate validation checks the complete proposed model before one undoable transaction. The Review semantic changes topic explains included rows, mapped references and each explicit attachment decision.", + ), + explain( + "Avoid duplicate counterparts", + "Check an existing trace before requesting a new counterpart. Several engineering records can correspond, but each mapping should have a reason. Reverse transitions also use the trace to reuse the intended record rather than silently replacing the original.", + ), + ], + related: &[ + Topic::Perspectives, + Topic::Participants, + Topic::Capabilities, + Topic::Recovery, + Topic::SemanticReview, + ], + destination: Some((Page::Traces, "Open Traceability")), + } + } + } + Topic::SemanticReview => { + const { + Article { + summary: "Review what changed between corresponding OA and SA records. Choose the engineering result explicitly and retain the reasoned correspondence for later changes.", + keywords: "semantic drift review keep take final candidate previously reviewed history reset reference pairs annotation qualified field path binding lifeline membership order up down reconcile steps fragment branch direction multi-source batch", + sections: &[ + walkthrough( + "Compare the right records and direction", + &[ + "From an existing counterpart trace, choose Review counterpart changes…. Confirm the OA → SA or SA → OA direction and select the intended trace / review history. Each correspondence and direction has its own retained row draft; opening another row does not discard the first row's choices.", + "In Properties, select a fact. Compare Previously reviewed with Current on both sides. Unreviewed means there is no prior observation; Both changed means both sides changed since that observation, not that one side is automatically wrong.", + "Choose Keep for the destination, Take for the source, or Set final value…. For a kind change, Set final kind and payload… reviews the complete replacement and its affected references. Leave unselected makes no decision for that fact.", + "Read Reference mapping and Additional changes before Apply reviewed changes. A missing or ambiguous correspondence must be resolved explicitly. Apply commits the validated model and the reviewed history together; Undo restores both.", + ], + ), + explain( + "Map references by meaning", + "Reference mapping connect exact OA and SA identities used by the proposed change. Similar names are not sufficient. Map the records that play the same engineering role, including enclosing fields in qualified message-value paths. Mapping a referenced record does not authorize creating, moving or editing that record. If it needs a counterpart, establish that correspondence first.", + ), + walkthrough( + "Review one message value or lifeline binding", + &[ + "Open Contents and order and choose the exact identities on both sides. For a message value, browse the enclosing record fields as well as the final field: origin.latitude and destination.latitude are distinct addresses.", + "For a binding, choose the referenced lifeline key and its compatible local lifeline. Check which scenario owns each side. Keep or Take concerns that row, not all bindings in the scenario.", + "Use Set final value… to enter the destination expression or local lifeline. Include in destination collection controls that row's presence. Unchecking removes the entry without deleting its referenced engineering object.", + "If a row key changed, review the old entry's removal and the new entry's addition separately. A missing source identity is not the same decision as an identified source entry that has been deleted. Read the proposed value before applying.", + ], + ), + walkthrough( + "Review membership and sequence order separately", + &[ + "Review which occurrences belong to Steps, or which operands belong to Fragment branches, using Contents and order. This changes membership; it does not silently reparent or create the referenced records.", + "Choose Review final order… or the corresponding order fact in Properties. Keep retains destination positions. Take uses mapped source order when it can preserve destination-only members without an ambiguous placement.", + "For an explicit result, choose Set final value…, then use Up and Down. If you change membership afterward, choose Reconcile with selected membership: surviving positions are retained, removed members leave the list, and new members append for you to position.", + "Every surviving destination member must appear exactly once. Resolve an ambiguous insertion with an explicit final order; do not remove an engineering contribution merely to make the preview pass.", + ], + ), + explain( + "Review attached records and external evidence", + "In Owned elements and relationships, choose exact OA and SA child or relationship identities, then Use exact attachment pair. Review a child's parent or a relationship endpoint independently; creating a new child or relationship requires its explicit transition mapping. In Evidence links, pair the exact links and choose Review this exact artifact pair. Presence concerns attachment to the reviewed owner. Taking an identified source absence can delete the destination link; a destination-only link is retained. Reserve a new destination counterpart only when one is needed, then explicitly review Presence and all seven metadata facts before the combined Apply. The reservation creates nothing by itself.", + ), + explain( + "Preserve intentional differences and useful history", + "Keep can acknowledge a deliberate difference without overwriting the destination. Reset this exact previous observation clears only the chosen review history; it does not restore an old model value. Overlapping row addresses may require an explicit history reset. Review those messages rather than clearing unrelated observations. Pending review choices and raw values can be recovered after an unexpected exit, but the plan is recomputed against the recovered accepted model.", + ), + walkthrough( + "Review several sources together", + &[ + "Use Add linked object, then Review object. Include this object determines whether its decisions contribute to the shared preview. The object selector switches the object you are editing; excluded rows retain their drafts.", + "Review each included source's contribution to the destination. Compatible decisions combine; contradictory Keep, Take or final-value decisions must be resolved explicitly. Read the combined conflicts, Reference mapping and Additional changes before Apply reviewed changes. One Apply commits the combined model and review histories; one Undo restores them.", + "Only one trace history per source can be included. Some reverse correspondences cannot share the core reference mapping; the preview explains the conflict. Exclude a row or choose a distinct correspondence without discarding its draft. Switching direction retains the other direction's choices for later review.", + "Recovery retains included and excluded row drafts and raw final values, then recomputes the plan against the recovered accepted model. Refresh preview is not automatic synchronization and does not authorize changes to attached records or artifacts.", + ], + ), + ], + related: &[ + Topic::Transitions, + Topic::Interfaces, + Topic::Scenarios, + Topic::Recovery, + ], + destination: Some((Page::Traces, "Open Traceability")), + } + } + } + Topic::Artifacts => { + const { + Article { + summary: "Connect model decisions to external engineering evidence using stable references to code, CAD, schematics, documents and other artifacts.", + keywords: "artifact traceability evidence code cad schematic electrical docx requirements revision fragment uri git commit owner placement document", + sections: &[ + walkthrough( + "Link an artifact", + &[ + "Select the model record the evidence concerns. This may be an element, relationship, diagram, placement or other supported persistent record.", + "Choose + Artifact reference in Properties, or + Artifact in Traceability. Name the reference so another engineer understands its purpose.", + "Enter the Artifact URI, optional external Revision and Fragment. Use a commit or document revision when a reproducible reference matters; the fragment identifies a particular part of the artifact.", + "Choose the relation to the artifact: References, Satisfies, Verifies or Implements. Add a description explaining the evidence and its limits. Apply changes, then inspect the owner and reference in Traceability.", + ], + ), + explain( + "Find existing evidence", + "In Traceability, choose Development artifacts to show artifact links directly. Search by the link's name or alias, its owner, URI, revision or fragment. Layer traces shows OA/SA correspondences; All shows both lists. Filtering changes only what is shown and preserves the selected record and unfinished property input.", + ), + explain( + "A reference is not an attachment", + "The model preserves the author-provided URI, revision and fragment. Loading or saving a model does not fetch, execute or verify an external artifact. A path on one workstation may not exist on another; choose team-resolvable locations and identifiers.", + ), + explain( + "Choose the right owner", + "Link semantic evidence to the element or relationship it supports. Link view-specific evidence to a diagram or placement. A diagram node's placement identity remains distinct from its represented element, so the reference can express the intended scope.", + ), + explain( + "Automation uses the same records", + "A CAD or version-control plugin can query and edit artifact links through the REST API. Its update should preserve stable model IDs and use the server's conditional write contract to avoid overwriting another engineer's work.", + ), + ], + related: &[Topic::Source, Topic::Server, Topic::Nesting], + destination: Some((Page::Traces, "Open Traceability")), + } + } + } + Topic::Source => { + const { + Article { + summary: "The text editor and graphical forms edit the same model. Source projects preserve file organization and comments for human review, automation and version control.", + keywords: "text textual source language syntax highlighting lint parser git aliases uuid identity rename diff merge restructuredtext spec canonical parity", + sections: &[ + walkthrough( + "Edit and review source", + &[ + "Open Textual model and expand folders to choose a source file, or choose Actions > Open source in Properties to locate the selected record. Switching files preserves each draft. Collapsed folders show modified-file and diagnostic counts; Locate reveals the active file. Right-click a file for its actions.", + "Use Find / go to, Ctrl+F or Ctrl+G to navigate. Literal search is case sensitive. Enter/F3 selects the next match, Shift+F3 selects the previous match, and results wrap. Go to line accepts the displayed one-based line number.", + "Edit the draft. Syntax highlighting distinguishes source structure; live lint reports errors with line and column information.", + "Choose Apply all to parse and validate all reachable included files. A successful application is one model change; graphical views and Properties then reflect it. Finish or revert source drafts before editing graphical properties.", + "If parsing or validation fails, the accepted model remains unchanged and the source draft is preserved. Correct the reported location or use Revert draft to restore the accepted source.", + "Save all writes the entry and its included UTF-8 files. Review the Git diff before committing, including relationships, traces and diagram changes that belong to the same engineering edit.", + ], + ), + explain( + "Names, aliases and IDs serve different purposes", + "The displayed name is engineering prose. A stable @alias is the readable source reference and need not change when the name changes. UUID identity distinguishes the persistent record. Preserve IDs when revising the same engineering object; changing identity is a deliberate operation that affects references.", + ), + example( + "Readable references", + "For example, renaming an activity does not require renaming @assess_incident. A related record can still refer to that alias.", + "source @receive_distress;\ntarget @assess_incident;", + ), + explain( + "Use one contract", + "The normative specification is docs/language.rst in the distribution. It defines source grammar and supported graphical semantics together. Text preserves semantic records, diagram placements, ordered occurrences, traces and artifact references. Graphical edits preserve unaffected source formatting and comments; accepted model Undo/Redo also restores the exact source files. Comments and source-file organization are not part of JSON export.", + ), + example( + "Keep included files with their entry", + "An include is relative to the file that declares it. Put declarations in the included file and keep one project entry. A newly created graphical record may live in another source file; use Open source to locate it and include every affected file in your review and save.", + "// Inside the project declaration:\ninclude \"oa/operations.arcadia\";\ninclude \"sa/model.arcadia\";", + ), + explain( + "Resolve source-control conflicts before applying", + "Ordinary Git conflict markers are not Arcadia syntax. Resolve the text merge, preserve intended UUIDs/references, then apply and inspect Model checks. The editor does not decide which engineer's model change is correct.", + ), + ], + related: &[ + Topic::Recovery, + Topic::Files, + Topic::Artifacts, + Topic::Server, + ], + destination: Some((Page::Text, "Open Textual model")), + } + } + } + Topic::ModelChecks => { + const { + Article { + summary: "Use checks to find invalid structure and unfinished engineering work. A valid model can still need decisions, behavior evidence or allocation.", + keywords: "validation warning error completeness coverage non_leaf_work_exchange work_without_behavior_context exchange_without_behavior_context mission_without_capability capability_without_behavior_context unsupported_capability sequence_gap allocation leaf context diagnostics lint", + sections: &[ + explain( + "Errors and warnings answer different questions", + "An error rejects the candidate, leaving the accepted model unchanged. A warning describes an incomplete or questionable model that can still be saved. Read the record, field and source location rather than treating the number of warnings as a quality score. Fix structural errors first: the completeness pass waits for valid structure, so its warnings may appear after those errors are corrected.", + ), + walkthrough( + "Connect work to explicit behavior", + &[ + "For an exchange on non-leaf work, identify the leaf Activity or Function responsible for that endpoint. In SA, keep its typed input/output port owned by that Function. Do not add a fake child just to suppress the warning.", + "For leaf work without behavior context, include a work action or one of its exchanges in an ordered Process/Functional chain or Scenario. Allocation alone states who performs the work, not how it is used.", + "For an exchange without behavior context, include that exact Interaction or Functional exchange in an exchange occurrence. A similarly named exchange or an unlisted occurrence under the container does not provide evidence.", + "Only included steps, fragment operands and valid reused behavior contribute. State actions, availability links and prose remain useful but do not stand in for an explicit process/scenario use. Coverage demonstrates inclusion, not execution or completeness of every possible path.", + ], + ), + walkthrough( + "Connect purpose to behavioral evidence", + &[ + "For a mission without a capability, connect the mission to the capability that delivers it. Merely involving a participant in the mission does not identify that capability.", + "For a supported capability without behavior context, connect a Process/Functional chain using Chain involvement or set a Scenario's Context to that capability in the same perspective.", + "A capability used only to group child capabilities is treated differently from an isolated leaf. A directly contextualized Scenario establishes support; adding an unrelated participant link is not required merely to clear a warning.", + "Review the engineering statement after each correction. Do not invent relationships solely to achieve a warning-free list; leave an unfinished decision visible until the model can express it accurately.", + ], + ), + explain( + "Review sequence gaps and incomplete contracts", + "A sequence gap calls attention to disconnected handoffs; inspect the contribution endpoints and intended path. Missing allocations, unspecified exchange mechanisms and incomplete conditions need their own engineering decisions. An accepted model does not prove that guards are executable, units are converted, external artifacts exist or timing requirements are met.", + ), + explain( + "The same checks are available to text and automation", + "Model checks, source lint and server validation use the shared model rules. Source-project diagnostics identify the included file and location; native JSON checks identify the model record and field. A validation request can inspect a candidate without committing it. Correct errors in the complete candidate before applying it graphically, as text or through the API.", + ), + ], + related: &[ + Topic::Capabilities, + Topic::Chains, + Topic::Scenarios, + Topic::Recovery, + ], + destination: Some((Page::Validation, "Open Model checks")), + } + } + } + Topic::Recovery => { + const { + Article { + summary: "Correct rejected drafts, inspect model checks and recover retained work after an unexpected exit. Recovery copies preserve editing separately from your saved models.", + keywords: "validation checks error warning invalid alias missing reference undo redo revert recover recovery checkpoint autosave crash draft unsaved structural external conflict lint home", + sections: &[ + walkthrough( + "Resolve a model issue", + &[ + "Open Model checks and read the issue's message and severity. Use its Open action, where available, to inspect the affected record.", + "Correct its properties or missing relationships. Check the active perspective and compatible parent/endpoints before changing record kinds.", + "Apply changes and revisit Model checks. A warning about missing allocation or coverage may describe unfinished work; a rejected structural error must be corrected before that candidate can become the model.", + ], + ), + explain( + "A property draft is not yet the model", + "Apply changes validates the candidate. When it is rejected, correct the retained draft or use Revert. The canvas continues to show accepted values. Navigation that needs to apply an invalid draft may remain on the current record so your work is not lost. You can read this guide without applying or discarding that draft.", + ), + explain( + "A new behavior is still a draft", + "Inside a source subform, Back or the first Escape returns to the unchanged parent. Use this new source stages the prerequisite only. Canceling the parent discards it; choosing an existing reference excludes the staged source from creation. Recovery retains both forms and their reserved identities. An open dialog still receives Escape first. Behavior → New keeps the exact name, owner and viewpoint choices outside the model until Create and open succeeds. Cancel or Escape discards only this draft; if a dialog is open, the first Escape dismisses that dialog. New refuses other unfinished property, source or behavior drafts without applying them. Recovery restores the creation form in its original layer, including rejected raw text, for correction or cancellation.", + ), + explain( + "Aliases and references", + "Stable source aliases must follow the language's identifier rules and avoid reserved words. Use a simple descriptive identifier such as assess_incident. Spaces belong in the displayed Name, not the alias. Reference pickers offer compatible model records; creating the required target first is often the missing step.", + ), + explain( + "Undo and recover deliberately", + "Ctrl+Z and redo restore accepted model changes. While the source editor is active, they operate on the source draft instead. Revert restores the accepted source or properties. If a file changed externally, save refuses a blind overwrite: preserve your work with Save as, then compare the files.", + ), + explain( + "Recover an interrupted source save", + "If a staged multi-file save was interrupted, Textual model offers Recover interrupted save. Recovery checks the journal and file contents before restoring originals; it does not overwrite unrelated external changes. This repairs an interrupted explicit save. Home's recovery copies preserve unsaved editing separately.", + ), + walkthrough( + "Recover after an unexpected exit", + &[ + "Open Home and look under Recover unsaved work. Each retained session shows its model name, original location when known and age. Choose Recover; the current model still uses the normal Save/Discard/Cancel guard.", + "Inspect the recovered copy. Accepted model/source content and raw source buffers are retained, including invalid and detached drafts. Supported pending property/behavior forms and workflow drafts return with rejected values and semantic-review choices. Correct or revert unfinished input before applying it.", + "Choose Save as and select a destination. Recovery does not reconnect the original local or hosted destination, and never restores credentials. Undo/redo history is not restored across sessions.", + ], + ), + explain( + "What a checkpoint protects", + "While work is unsaved, a private local checkpoint is attempted every 10 seconds. It does not apply drafts, modify model files or save to the server. Recent edits since the last completed checkpoint may not survive a crash. Home reports unavailable or damaged recovery files without replacing the currently open model. Explicit saves and version control remain your durable milestones.", + ), + explain( + "Save work before leaving", + "New, Open, opening a recent model, loading the example, Recover and Close use the unsaved-work prompt. Save and continue, discard or cancel is your decision. Checkpoints are private recovery copies; continue saving meaningful milestones and using version control or backups.", + ), + ], + related: &[ + Topic::ModelChecks, + Topic::Source, + Topic::Files, + Topic::SemanticReview, + ], + destination: Some((Page::Validation, "Open Model checks")), + } + } + } + Topic::Files => { + const { + Article { + summary: "Keep editable model files and exported pictures distinct. Save the model to retain its semantics, identities and traceability.", + keywords: "file home recent missing locate open save save as keyboard shortcuts ctrl control shift export svg json arcadia backup external changes migration", + sections: &[ + explain( + "Start from Home", + "Home offers New model, Open model, the rescue example and this guide. Recent models lists local files you successfully opened or saved. A disconnected drive or moved file remains visible; use Locate to choose its new location or Refresh after reconnecting the drive. Remove forgets the recent entry without deleting its model file. Home retains the current document; Continue editing returns to the last workspace, including Textual model or a saved state view, with its drafts intact.", + ), + explain( + "Editable formats", + ".arcadia is editable text suitable for Git and can include other source files. .json is the typed model exchange format. Both preserve the persistent model, diagrams and traceability; only a source project preserves its comments and file organization. Export SVG creates a diagram image for reports; it is not an editable model backup.", + ), + example( + "Keyboard reference", + "Shortcuts act on the current project. In Textual model, undo and redo affect the source draft.", + "Ctrl+N New project\nCtrl+O Open model\nCtrl+S Save all\nCtrl+Shift+S Save as\nCtrl+Z Undo\nCtrl+Shift+Z Redo\nCtrl+Y Redo\nEscape Cancel the active dialog", + ), + walkthrough( + "Save and share a model", + &[ + "Use Save all to apply valid drafts and save the source project to the current path. For a new project, select a destination and format in the file dialog.", + "Use Save as for a new path or explicit format conversion. Check the filename extension when selecting text or JSON.", + "After saving, check All changes saved. Share or commit the entry and every included source file, together with the necessary external evidence locations.", + "If another tool changed or deleted the open file, preserve your candidate using a new Save as destination. Compare and reconcile the versions before replacing either one.", + ], + ), + explain( + "Save as preserves the engineering history", + "Saving a source project under a new entry name relocates the save destination without adding a model edit or clearing Redo. Choose a name that does not collide with an included file, including one retained in Undo/Redo history. Save all includes every accepted source file; an unchanged entry file alone does not prove that an included file was saved. If a save fails, the current model and drafts remain available.", + ), + explain( + "Open and migration", + "Open validates a complete candidate before replacing the project. Supported earlier versions migrate to the current schema; Model checks shows migration details such as preserved ports, supplied data defaults, generated interface allocations and unresolved message paths. Old Event items become Information notifications; unknown physical mechanisms remain Unspecified. Source upgrades retain files, includes, nested ownership and comments. Opening does not overwrite the original: use Save as if it must remain in its older format. Invalid input leaves the accepted project unchanged.", + ), + ], + related: &[Topic::Source, Topic::Recovery, Topic::Artifacts], + destination: None, + } + } + } + Topic::Server => { + const { + Article { + summary: "Use optional hosted storage from the desktop, scripts and engineering-tool plugins. All clients work with the same model, validation and authored source.", + keywords: "server rest api automation ai plugin curl http etag if-match revision resource_id deploy hosted cad scripting openapi token", + sections: &[ + example( + "Start a local service", + "From the repository root, run the command below in a terminal and leave it running. The default address is loopback 127.0.0.1:8787, with platform-local storage. docs/server.md also covers installed executables, authentication and Linux deployment.", + "cargo run --locked -p oa-server --bin arcadia-server\n\nGET http://127.0.0.1:8787/health\nGET http://127.0.0.1:8787/api/v1/openapi.json", + ), + walkthrough( + "Open or publish a hosted model", + &[ + "Choose Hosted models... in the top bar. Enter the service root, such as http://127.0.0.1:8787, without /api/v1. Enter the configured bearer token if required, then Connect / refresh.", + "Choose Publish as new to upload the current source project, including its files and comments, as a separate hosted resource. Its acknowledgement switches the current Save destination to that resource.", + "To open an existing resource, search the Hosted model catalog, select a model and choose Open selected. More models loads another page. Unsaved work uses the normal save/discard/cancel prompt.", + "Edit diagrams, properties or Textual model, then choose Save to server or press Ctrl+S. Valid drafts are applied; invalid drafts remain available to correct or revert. Edits and Undo stay local until Save.", + ], + ), + walkthrough( + "Review another client's changes", + &[ + "Check server reads the current revision without replacing local content. A stale Save returns 412 and automatically checks the server for review; local work is retained.", + "Review the changed file list. View server text opens a read-only file; Back to review returns to the list. There is no automatic merge.", + "Reload server version adopts the reviewed source project as one undoable local change after checking unapplied drafts. Undo restores the previous accepted local version without changing the server. Reconcile your intended changes before saving again.", + "After an unknown write outcome, use Check previous write when offered. If publication may already have succeeded, inspect the refreshed catalog before publishing another copy.", + ], + ), + explain( + "Return to a local file", + "Choose Save as local... or Ctrl+Shift+S, then save an .arcadia entry to retain included files and comments. A successful source-project save makes that file the current destination and leaves the hosted resource unchanged. JSON exports the model only and retains the hosted destination and unsaved state. Canceling or a failed save also retains the hosted destination. Forget connection becomes available after leaving the hosted session.", + ), + explain( + "Connection credentials", + "The desktop keeps credentials in memory only. Use HTTPS remotely; HTTP is allowed only for localhost or a loopback IP. Put the token in its own field, never the service URL. URLs with credentials, queries or fragments are rejected, and redirects are not followed. To update a token, enter it and Connect / refresh for the same service, then retry the check or Save.", + ), + walkthrough( + "Automate model edits", + &[ + "Create or import a project using /api/v1/projects or /api/v1/projects/import. Keep the returned service resource_id; it is distinct from the editable model UUID.", + "Read the project's envelope, source or model. Preserve its exact ETag as the precondition for your edit.", + "Submit typed commands to /transactions, or a complete candidate to /source, /model or /source-project, using If-Match. Use transaction dry-run or validation before committing substantial generated changes.", + "A stale write returns 412. Keep the user's draft, reload the current head and reconcile deliberately. After an uncertain write outcome, read the server state before retrying.", + "Query /artifact-links and /traces to connect domain-tool artifacts to model records. The server stores these references; the integration is responsible for resolving its external artifacts.", + ], + ), + explain( + "Validate and save a source bundle", + "GET /source-project returns the entry and included files. POST /validation with format source-project and source_project containing that bundle checks a candidate without committing it; warnings can coexist with valid: true. PUT /source-project accepts the complete bundle under the current If-Match ETag. Structural errors reject the write, preserving the accepted model and revision. Compare record IDs and fields across text/JSON diagnostics; source-project results also identify the included file and source span.", + ), + explain( + "Deployment and persistence", + "Non-loopback binding requires a configured bearer token. Use the documented local-disk storage and deployment configuration. The service keeps the current revision and serializes accepted updates; it does not provide retained revision history or server-side undo. Stop the service cleanly before copying its committed store for backup.", + ), + explain( + "Use the precise contract", + "docs/api.rst and docs/openapi.json define routes, command shapes, limits and errors. The running server also serves OpenAPI. No network request or service mutation is triggered by reading this guide.", + ), + ], + related: &[ + Topic::Artifacts, + Topic::Source, + Topic::Transitions, + Topic::Recovery, + ], + destination: None, + } + } + } + } +} + +/// Help search is independent of model search. All query words must occur in +/// the article, title or retrieval keywords; order and case are insignificant. +fn search(query: &str) -> Vec { + let query = query.to_lowercase(); + let words = query.split_whitespace().collect::>(); + Topic::ALL + .into_iter() + .filter(|topic| { + let article = article(*topic); + let mut haystack = + format!("{} {} {}", topic.title(), article.summary, article.keywords); + for section in article.sections { + haystack.push_str(section.title); + haystack.push(' '); + haystack.push_str(section.body); + for step in section.steps { + haystack.push(' '); + haystack.push_str(step); + } + if let Some(code) = section.code { + haystack.push(' '); + haystack.push_str(code); + } + } + let haystack = haystack.to_lowercase(); + words.iter().all(|word| haystack.contains(word)) + }) + .collect() +} + +fn topic_button(topic: Topic, selected: bool) -> iced::widget::Button<'static, Message> { + button(text(topic.title()).size(13)) + .padding([10, 11]) + .width(Fill) + .style(if selected { + style::selected + } else { + style::nav + }) + .on_press(Message::Guide(Event::Topic(topic))) +} + +fn article_view(topic: Topic) -> Element<'static, Message> { + let article = article(topic); + let mut content = column![ + text(topic.category()).size(11).color(style::MUTED), + text(topic.title()).size(25), + text(article.summary).size(15).color(style::MUTED), + ] + .spacing(14) + .width(Fill); + for section in article.sections { + let mut body = column![text(section.title).size(18)] + .spacing(12) + .width(Fill); + if !section.body.is_empty() { + body = body.push(text(section.body).size(14)); + } + for (index, step) in section.steps.iter().enumerate() { + body = body.push( + row![ + text(format!("{:02}", index + 1)) + .size(13) + .color(style::TEAL) + .width(28), + text(*step).size(14).width(Fill), + ] + .spacing(9) + .width(Fill), + ); + } + if let Some(code) = section.code { + body = body.push( + container(text(code).font(Font::MONOSPACE).size(12).width(Fill)) + .padding(14) + .width(Fill) + .style(style::subtle), + ); + } + content = content.push(container(body).padding(20).width(Fill).style(style::card)); + } + if let Some((page, label)) = article.destination { + content = content.push( + button(text(label).size(13)) + .padding([11, 15]) + .on_press(Message::Page(page)), + ); + } + if topic == Topic::FirstProject { + content = content.push(text("For a worked model, open the water-pressure walkthrough. Copy the whole examples/release1 folder and open the copied project.arcadia before editing; read its README.md. If you use Save as, also copy evidence/ beside the new entry: Save as copies model source, not external artifact targets.").size(13)); + content = content.push( + button(text("Open water-pressure walkthrough").size(13)) + .padding([11, 15]) + .style(style::nav) + .on_press(Message::Home(crate::home::Event::Walkthrough)), + ); + content = content.push( + button(text("Open coastal rescue example").size(13)) + .padding([11, 15]) + .style(style::nav) + .on_press(Message::Request(Pending::Demo)), + ); + } + content = content.push(text("CONTINUE EXPLORING").size(11).color(style::MUTED)); + for related in article.related { + content = content.push(topic_button(*related, false)); + } + scrollable(content.padding(iced::Padding { + right: 12., + bottom: 24., + ..Default::default() + })) + .id(ARTICLE_SCROLL_ID) + .width(Fill) + .height(Fill) + .into() +} + +pub fn view(state: &State) -> Element<'_, Message> { + let matches = search(&state.query); + let mut navigation = column![].spacing(5).width(Fill); + let mut category = ""; + for topic in &matches { + if category != topic.category() { + category = topic.category(); + navigation = navigation.push( + container(text(category).size(10).color(style::MUTED)).padding(iced::Padding { + top: 12., + bottom: 4., + left: 8., + right: 4., + }), + ); + } + navigation = navigation.push(topic_button(*topic, state.topic == *topic)); + } + let topic_list = container(scrollable(navigation.padding(iced::Padding { + right: 8., + ..Default::default() + }))) + .width(200) + .height(Fill); + let article: Element<'_, Message> = if matches.is_empty() { + container(column![ + text("No help topics found").size(23), + text("Try a modeling concept such as allocation, scenario, data type, Git or API. Help search does not search your model.").size(14).color(style::MUTED), + button(text("Clear search").size(13)).padding([10, 14]).on_press(Message::Guide(Event::Search(String::new()))), + ].spacing(16).max_width(520)).padding(24).width(Fill).into() + } else { + // Topic changes also issue an explicit article-only scroll reset. + keyed_column([(state.topic, article_view(state.topic))]) + .width(Fill) + .height(Fill) + .into() + }; + column![ + column![ + text("Engineering guide").size(25), + text("Practical workflows, modeling concepts and reference · available offline") + .size(12) + .color(style::MUTED), + ] + .spacing(5), + row![ + text_input("Search help: allocation, scenario, Git…", &state.query) + .on_input(|query| Message::Guide(Event::Search(query))) + .padding(11) + .size(14) + .width(Fill), + button(text("Clear").size(12)) + .padding(11) + .style(style::nav) + .on_press_maybe( + (!state.query.is_empty()).then(|| Message::Guide(Event::Search(String::new()))) + ), + ] + .spacing(8) + .width(Fill), + text(format!("{} topics", matches.len())) + .size(11) + .color(style::MUTED), + row![topic_list, article] + .spacing(20) + .width(Fill) + .height(Fill), + ] + .spacing(14) + .padding(22) + .width(Fill) + .height(Fill) + .into() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Default)] + struct ScrollProbe { + snaps: Vec>>, + } + + impl iced::advanced::widget::operation::Scrollable for ScrollProbe { + fn snap_to( + &mut self, + offset: iced::advanced::widget::operation::scrollable::RelativeOffset>, + ) { + self.snaps.push(offset); + } + fn scroll_to( + &mut self, + _: iced::advanced::widget::operation::scrollable::AbsoluteOffset>, + ) { + panic!("Guide navigation must snap to the article start"); + } + fn scroll_by( + &mut self, + _: iced::advanced::widget::operation::scrollable::AbsoluteOffset, + _: iced::Rectangle, + _: iced::Rectangle, + ) { + panic!("Guide navigation must not use a relative pixel scroll"); + } + } + + #[tokio::test] + async fn guide_topic_navigation_resets_only_article_and_same_topic_search_keeps_scroll() { + use iced::futures::StreamExt; + let mut app = crate::app::App::with_project(oa_core::Project::empty()); + app.page = Page::Guide; + let accepted = app.document.snapshot(); + let revision = app.document.revision(); + for (event, topic, reset) in [ + (Event::Topic(Topic::Scenarios), Topic::Scenarios, true), + ( + Event::Search("sender lifeline".into()), + Topic::Scenarios, + false, + ), + // Native reproduction: related topic after scrolling Scenarios. + (Event::Topic(Topic::Exchanges), Topic::Exchanges, true), + (Event::Search("If-Match 412".into()), Topic::Server, true), + (Event::Search("If-Match".into()), Topic::Server, false), + ( + Event::Search("nonexistent-topic-987".into()), + Topic::Server, + false, + ), + (Event::Search(String::new()), Topic::Server, false), + (Event::Topic(Topic::Server), Topic::Server, false), + ] { + let mut article = ScrollProbe::default(); + let mut navigation = ScrollProbe::default(); + if let Some(mut stream) = + iced_runtime::task::into_stream(app.update(Message::Guide(event))) + { + while let Some(action) = stream.next().await { + let iced_runtime::Action::Widget(mut operation) = action else { + panic!("Guide navigation must not schedule model or external actions"); + }; + for (id, probe) in [ + (ARTICLE_SCROLL_ID, &mut article), + ("guide-topic-navigation", &mut navigation), + ] { + operation.scrollable( + Some(&iced::advanced::widget::Id::new(id)), + iced::Rectangle::default(), + iced::Rectangle::default(), + iced::Vector::new(0., 900.), + probe, + ); + } + } + } + assert_eq!(app.guide.topic, topic); + assert_eq!(article.snaps.len(), usize::from(reset)); + for offset in article.snaps { + assert_eq!(offset.x, Some(0.)); + assert_eq!(offset.y, Some(0.)); + } + assert!(navigation.snaps.is_empty()); + assert_eq!(app.project(), &accepted.decoded.project); + assert_eq!(app.document.bundle(), &accepted.bundle); + assert_eq!(app.document.revision(), revision); + assert!(!app.document.can_undo()); + } + } + + #[test] + fn search_finds_concepts_in_walkthroughs_and_api_reference() { + assert!(search(" ALLOCATED rescue ").contains(&Topic::FirstProject)); + assert!(search("sender lifeline").contains(&Topic::Scenarios)); + assert!(search("If-Match 412").contains(&Topic::Server)); + assert!(search("Ctrl+Shift+S").contains(&Topic::Files)); + assert!(search("no-such-modeling-topic-987").is_empty()); + assert_eq!(search(" \n\t "), Topic::ALL); + } + + #[test] + fn filtering_and_related_navigation_keep_selection_visible() { + let mut state = State::default(); + state.update(Event::Search("If-Match 412".into())); + assert_eq!(state.topic, Topic::Server); + state.update(Event::Topic(Topic::Nesting)); + assert!(state.query.is_empty()); + assert_eq!(state.topic, Topic::Nesting); + state.update(Event::Search("nonexistent-topic-987".into())); + assert_eq!(state.topic, Topic::Nesting); + state.update(Event::Search(String::new())); + assert!(search(&state.query).contains(&state.topic)); + } +} diff --git a/crates/oa-desktop/src/semantic_review/artifacts_view.rs b/crates/oa-desktop/src/semantic_review/artifacts_view.rs index ea186228..b4958928 100644 --- a/crates/oa-desktop/src/semantic_review/artifacts_view.rs +++ b/crates/oa-desktop/src/semantic_review/artifacts_view.rs @@ -1,147 +1,147 @@ -use super::artifacts::{Editor, Event, Raw}; -use super::*; -use iced::{ - Element as View, Fill, - widget::{button, checkbox, column, pick_list, row, text, text_editor, text_input}, -}; -pub(super) fn pairs<'a>(editor: &'a Editor, p: &'a Project, trace: &'a Trace) -> View<'a, Event> { - let mut content=column![text("Artifact links and metadata").size(16),text("Pair links attached to these reviewed owners. Presence is attachment to the owner; taking identified source absence deletes the destination link, while a destination-only link is retained.").size(12),text_input("Filter artifact identities",&editor.search).on_input(Event::Search).padding(7)].spacing(9); - for (layer, label, selected) in [ - (Layer::Oa, "Operational", editor.oa.clone()), - (Layer::Sa, "System", editor.sa.clone()), - ] { - content = content.push(text(label).size(12)).push( - row![ - pick_list(editor.choices(p, trace, layer), selected, move |v| { - Event::Pick(layer, Some(v)) - }) - .placeholder("Choose exact artifact identity") - .width(Fill) - .padding(7), - button("No identity") - .padding(7) - .on_press(Event::Pick(layer, None)) - ] - .spacing(7), - ); - } - content.push(row![button("Reserve a new destination counterpart").padding(7).on_press(Event::Reserve),button("Review this exact artifact pair").padding(7).on_press(Event::Stage)].spacing(7).wrap()).push(text("Reservation retains a fresh identity and seeds metadata from the source. It creates nothing until Presence and all seven metadata facets are explicitly reviewed and the combined Apply succeeds. New unrelated links use ordinary artifact creation.").size(11)).padding(iced::Padding::default().right(18)).into() -} -pub(super) fn candidate<'a>( - editor: &'a Editor, - p: &'a Project, - facet: &'a FacetSelector, -) -> Option> { - let value = editor.drafts.get(facet)?; - let (_, which) = artifacts::parts(facet)?; - let mut content = - column![text(format!("Final artifact {}", artifacts::field(which)?)).size(12)].spacing(7); - match value { - Raw::Text(_) if which == ArtifactFacet::Description => { - let draft = editor.descriptions.get(facet)?; - let facet = facet.clone(); - content = content.push( - text_editor(draft) - .height(110) - .on_action(move |action| Event::Description(facet.clone(), action)), - ); - } - Raw::Text(value) => { - let facet = facet.clone(); - content = content.push( - text_input("Exact text", value) - .on_input(move |value| Event::Input(facet.clone(), Raw::Text(value))) - .padding(7), - ); - } - Raw::Owner(value) => { - let mut choices: Vec<_> = p - .identities() - .into_iter() - .map(|(id, alias)| Choice { - id, - label: format!("{} · @{alias}", p.named(id)), - }) - .collect(); - choices.sort_by_cached_key(|c| c.label.to_lowercase()); - let selected = value.parse::().ok().map(|id| view::choice(p, id)); - let raw_facet = facet.clone(); - let pick_facet = facet.clone(); - content = content - .push( - pick_list(choices, selected, move |v: Choice| { - Event::Input(pick_facet.clone(), Raw::Owner(v.id.to_string())) - }) - .placeholder("Explicit final owner") - .width(Fill) - .padding(7), - ) - .push( - text_input("Owner identity (retains rejected input)", value) - .on_input(move |v| Event::Input(raw_facet.clone(), Raw::Owner(v))) - .padding(7), - ); - } - Raw::Relation(value) => { - let facet = facet.clone(); - let variants = [ - ArtifactRelation::References, - ArtifactRelation::Satisfies, - ArtifactRelation::Verifies, - ArtifactRelation::Implements, - ArtifactRelation::DerivedFrom, - ]; - let choices: Vec<_> = variants.into_iter().map(RelationChoice).collect(); - content = content.push( - pick_list(choices, Some(RelationChoice(*value)), move |v| { - Event::Input(facet.clone(), Raw::Relation(v.0)) - }) - .padding(7), - ); - } - Raw::Optional { - present, - text: value, - } => { - let toggle_facet = facet.clone(); - let retained = value.clone(); - let input_facet = facet.clone(); - let enabled = *present; - content = content - .push( - checkbox(*present) - .label("Present (checked with empty text means an explicit empty value)") - .on_toggle(move |present| { - Event::Input( - toggle_facet.clone(), - Raw::Optional { - present, - text: retained.clone(), - }, - ) - }), - ) - .push( - text_input("Retained optional text", value) - .on_input(move |text| { - Event::Input( - input_facet.clone(), - Raw::Optional { - present: enabled, - text, - }, - ) - }) - .padding(7), - ); - } - } - Some(content.into()) -} -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct RelationChoice(ArtifactRelation); -impl std::fmt::Display for RelationChoice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.0) - } -} +use super::artifacts::{Editor, Event, Raw}; +use super::*; +use iced::{ + Element as View, Fill, + widget::{button, checkbox, column, pick_list, row, text, text_editor, text_input}, +}; +pub(super) fn pairs<'a>(editor: &'a Editor, p: &'a Project, trace: &'a Trace) -> View<'a, Event> { + let mut content=column![text("Artifact links and metadata").size(16),text("Pair links attached to these reviewed owners. Presence is attachment to the owner; taking identified source absence deletes the destination link, while a destination-only link is retained.").size(12),text_input("Filter artifact identities",&editor.search).on_input(Event::Search).padding(7)].spacing(9); + for (layer, label, selected) in [ + (Layer::Oa, "Operational", editor.oa.clone()), + (Layer::Sa, "System", editor.sa.clone()), + ] { + content = content.push(text(label).size(12)).push( + row![ + pick_list(editor.choices(p, trace, layer), selected, move |v| { + Event::Pick(layer, Some(v)) + }) + .placeholder("Choose exact artifact identity") + .width(Fill) + .padding(7), + button("No identity") + .padding(7) + .on_press(Event::Pick(layer, None)) + ] + .spacing(7), + ); + } + content.push(row![button("Reserve a new destination counterpart").padding(7).on_press(Event::Reserve),button("Review this exact artifact pair").padding(7).on_press(Event::Stage)].spacing(7).wrap()).push(text("Reservation retains a fresh identity and seeds metadata from the source. It creates nothing until Presence and all seven metadata facets are explicitly reviewed and the combined Apply succeeds. New unrelated links use ordinary artifact creation.").size(11)).padding(iced::Padding::default().right(18)).into() +} +pub(super) fn candidate<'a>( + editor: &'a Editor, + p: &'a Project, + facet: &'a FacetSelector, +) -> Option> { + let value = editor.drafts.get(facet)?; + let (_, which) = artifacts::parts(facet)?; + let mut content = + column![text(format!("Final artifact {}", artifacts::field(which)?)).size(12)].spacing(7); + match value { + Raw::Text(_) if which == ArtifactFacet::Description => { + let draft = editor.descriptions.get(facet)?; + let facet = facet.clone(); + content = content.push( + text_editor(draft) + .height(110) + .on_action(move |action| Event::Description(facet.clone(), action)), + ); + } + Raw::Text(value) => { + let facet = facet.clone(); + content = content.push( + text_input("Exact text", value) + .on_input(move |value| Event::Input(facet.clone(), Raw::Text(value))) + .padding(7), + ); + } + Raw::Owner(value) => { + let mut choices: Vec<_> = p + .identities() + .into_iter() + .map(|(id, alias)| Choice { + id, + label: format!("{} · @{alias}", p.named(id)), + }) + .collect(); + choices.sort_by_cached_key(|c| c.label.to_lowercase()); + let selected = value.parse::().ok().map(|id| view::choice(p, id)); + let raw_facet = facet.clone(); + let pick_facet = facet.clone(); + content = content + .push( + pick_list(choices, selected, move |v: Choice| { + Event::Input(pick_facet.clone(), Raw::Owner(v.id.to_string())) + }) + .placeholder("Explicit final owner") + .width(Fill) + .padding(7), + ) + .push( + text_input("Owner identity (retains rejected input)", value) + .on_input(move |v| Event::Input(raw_facet.clone(), Raw::Owner(v))) + .padding(7), + ); + } + Raw::Relation(value) => { + let facet = facet.clone(); + let variants = [ + ArtifactRelation::References, + ArtifactRelation::Satisfies, + ArtifactRelation::Verifies, + ArtifactRelation::Implements, + ArtifactRelation::DerivedFrom, + ]; + let choices: Vec<_> = variants.into_iter().map(RelationChoice).collect(); + content = content.push( + pick_list(choices, Some(RelationChoice(*value)), move |v| { + Event::Input(facet.clone(), Raw::Relation(v.0)) + }) + .padding(7), + ); + } + Raw::Optional { + present, + text: value, + } => { + let toggle_facet = facet.clone(); + let retained = value.clone(); + let input_facet = facet.clone(); + let enabled = *present; + content = content + .push( + checkbox(*present) + .label("Present (checked with empty text means an explicit empty value)") + .on_toggle(move |present| { + Event::Input( + toggle_facet.clone(), + Raw::Optional { + present, + text: retained.clone(), + }, + ) + }), + ) + .push( + text_input("Retained optional text", value) + .on_input(move |text| { + Event::Input( + input_facet.clone(), + Raw::Optional { + present: enabled, + text, + }, + ) + }) + .padding(7), + ); + } + } + Some(content.into()) +} +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct RelationChoice(ArtifactRelation); +impl std::fmt::Display for RelationChoice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} diff --git a/crates/oa-desktop/src/semantic_review/attachments_view.rs b/crates/oa-desktop/src/semantic_review/attachments_view.rs index e1351835..aad950de 100644 --- a/crates/oa-desktop/src/semantic_review/attachments_view.rs +++ b/crates/oa-desktop/src/semantic_review/attachments_view.rs @@ -1,79 +1,79 @@ -//! Explicit attachment pair and final destination field controls. -use super::{ - attachments::{Editor, Event, Family}, - *, -}; -use iced::{ - Element as View, Fill, - widget::{button, column, pick_list, row, text, text_input}, -}; - -pub(super) fn pairs<'a>( - editor: &'a Editor, - project: &'a Project, - trace: &'a Trace, -) -> View<'a, Event> { - let mut content = column![ - text("Existing children and relationship endpoints").size(16), - text("Choose explicit Operational and System identities. A child's final parent or a relationship's final endpoint is reviewed independently; opening another trace keeps this draft.").size(12), - pick_list([Family::Child, Family::Source, Family::Target], Some(editor.family), Event::Family).text_size(12).padding(7).width(Fill), - text_input("Filter attachment identities by name or alias", &editor.search).on_input(Event::Search).size(12).padding(7), - ].spacing(9); - for layer in [Layer::Oa, Layer::Sa] { - let label = if layer == Layer::Oa { - "Operational" - } else { - "System" - }; - let owner = if layer == Layer::Oa { - trace.oa - } else { - trace.sa - }; - content = content.push(text(format!("{label} owner: {}", project.named(owner))).size(12)); - content = content.push( - row![ - pick_list( - editor.choices(project, trace, layer), - editor.selection(layer).cloned(), - move |choice| Event::Pick(layer, Some(choice)) - ) - .placeholder(format!("Choose an existing {label} identity")) - .text_size(12) - .padding(7) - .width(Fill), - button(text("No identity on this side").size(11)) - .style(crate::style::nav) - .padding(7) - .on_press(Event::Pick(layer, None)), - ] - .spacing(7), - ); - } - content = content.push( - button(text("Use exact attachment pair").size(12)) - .padding(8) - .style(crate::style::nav) - .on_press(Event::Stage), - ); - content.push(text("Taking an absent relationship endpoint requires an explicit final replacement. New child and relationship creation uses a separate explicit transition mapping.").size(11)).padding(iced::Padding::default().right(18)).into() -} - -pub(super) fn candidate<'a>( - editor: &'a Editor, - project: &'a Project, - facet: &'a FacetSelector, - layer: Layer, -) -> Option> { - let draft = editor.drafts.get(facet)?; - let (id, _) = attachments::actual_slot(facet, layer)?; - let path = attachments::path(project, facet, layer)?; - Some( - column![ - text(format!("Destination record: {}", project.named(id))).size(12), - crate::forms::scoped(draft, project, &path, attachments::label(facet)), - ] - .spacing(7) - .into(), - ) -} +//! Explicit attachment pair and final destination field controls. +use super::{ + attachments::{Editor, Event, Family}, + *, +}; +use iced::{ + Element as View, Fill, + widget::{button, column, pick_list, row, text, text_input}, +}; + +pub(super) fn pairs<'a>( + editor: &'a Editor, + project: &'a Project, + trace: &'a Trace, +) -> View<'a, Event> { + let mut content = column![ + text("Existing children and relationship endpoints").size(16), + text("Choose explicit Operational and System identities. A child's final parent or a relationship's final endpoint is reviewed independently; opening another trace keeps this draft.").size(12), + pick_list([Family::Child, Family::Source, Family::Target], Some(editor.family), Event::Family).text_size(12).padding(7).width(Fill), + text_input("Filter attachment identities by name or alias", &editor.search).on_input(Event::Search).size(12).padding(7), + ].spacing(9); + for layer in [Layer::Oa, Layer::Sa] { + let label = if layer == Layer::Oa { + "Operational" + } else { + "System" + }; + let owner = if layer == Layer::Oa { + trace.oa + } else { + trace.sa + }; + content = content.push(text(format!("{label} owner: {}", project.named(owner))).size(12)); + content = content.push( + row![ + pick_list( + editor.choices(project, trace, layer), + editor.selection(layer).cloned(), + move |choice| Event::Pick(layer, Some(choice)) + ) + .placeholder(format!("Choose an existing {label} identity")) + .text_size(12) + .padding(7) + .width(Fill), + button(text("No identity on this side").size(11)) + .style(crate::style::nav) + .padding(7) + .on_press(Event::Pick(layer, None)), + ] + .spacing(7), + ); + } + content = content.push( + button(text("Use exact attachment pair").size(12)) + .padding(8) + .style(crate::style::nav) + .on_press(Event::Stage), + ); + content.push(text("Taking an absent relationship endpoint requires an explicit final replacement. New child and relationship creation uses a separate explicit transition mapping.").size(11)).padding(iced::Padding::default().right(18)).into() +} + +pub(super) fn candidate<'a>( + editor: &'a Editor, + project: &'a Project, + facet: &'a FacetSelector, + layer: Layer, +) -> Option> { + let draft = editor.drafts.get(facet)?; + let (id, _) = attachments::actual_slot(facet, layer)?; + let path = attachments::path(project, facet, layer)?; + Some( + column![ + text(format!("Destination record: {}", project.named(id))).size(12), + crate::forms::scoped(draft, project, &path, attachments::label(facet)), + ] + .spacing(7) + .into(), + ) +} diff --git a/crates/oa-desktop/src/semantic_review/keyed.rs b/crates/oa-desktop/src/semantic_review/keyed.rs index 5b4041c1..2cf59ec4 100644 --- a/crates/oa-desktop/src/semantic_review/keyed.rs +++ b/crates/oa-desktop/src/semantic_review/keyed.rs @@ -1,350 +1,350 @@ -//! Closed presence-only adapter. The core independently validates the full final record. -use super::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub enum Family { - Member(ReferenceCollection), - Literal, - Annotation, - Binding, -} -impl std::fmt::Display for Family { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - Self::Member(ReferenceCollection::FunctionInputItems) => "Function input items", - Self::Member(ReferenceCollection::FunctionOutputItems) => "Function output items", - Self::Member(ReferenceCollection::SignalItems) => "Signal items", - Self::Member(ReferenceCollection::CategoryExchanges) => "Category exchanges", - Self::Member(ReferenceCollection::ExchangeItems) => "Exchange items", - Self::Member(ReferenceCollection::TransitionTriggers) => "Transition triggers", - Self::Member(ReferenceCollection::TransitionEffects) => "Transition effects", - Self::Member(ReferenceCollection::Constrained) => "Constrained identities", - Self::Member(ReferenceCollection::Steps) => "Ordered steps", - Self::Member(ReferenceCollection::FragmentBranches) => "Fragment branches", - Self::Literal => "Enumeration literals", - Self::Annotation => "Message values", - Self::Binding => "Scenario lifeline bindings", - }) - } -} -pub(super) const FAMILIES: [Family; 13] = [ - Family::Member(ReferenceCollection::Steps), - Family::Member(ReferenceCollection::FragmentBranches), - Family::Member(ReferenceCollection::FunctionInputItems), - Family::Member(ReferenceCollection::FunctionOutputItems), - Family::Member(ReferenceCollection::SignalItems), - Family::Member(ReferenceCollection::ExchangeItems), - Family::Member(ReferenceCollection::CategoryExchanges), - Family::Member(ReferenceCollection::TransitionTriggers), - Family::Member(ReferenceCollection::TransitionEffects), - Family::Member(ReferenceCollection::Constrained), - Family::Literal, - Family::Annotation, - Family::Binding, -]; -pub(super) fn family(facet: &FacetSelector) -> Option { - let family = match facet { - FacetSelector::Member { collection, .. } => Family::Member(*collection), - FacetSelector::Literal(_) => Family::Literal, - FacetSelector::Annotation(_) => Family::Annotation, - FacetSelector::Binding(_) => Family::Binding, - _ => return None, - }; - FAMILIES.contains(&family).then_some(family) -} -pub(super) fn supported(facet: &FacetSelector) -> bool { - family(facet).is_some() -} - -// Fixed paths for the closed collections and the exact enum literal set. -// They are selected by the typed facet; callers cannot supply arbitrary paths. -pub(super) fn path(value: &Value, family: Family) -> Option { - let prefixes = ["/body/Oa/Shared", "/body/Sa/Shared"]; - let tails: &[&str] = match family { - Family::Member(ReferenceCollection::Steps) => { - return [ - "/body/Oa/Process/steps", - "/body/Sa/FunctionalChain/steps", - "/body/Oa/Shared/Scenario/steps", - "/body/Sa/Shared/Scenario/steps", - "/body/Oa/Shared/Occurrence/body/Branch/occurrences", - "/body/Sa/Shared/Occurrence/body/Branch/occurrences", - ] - .into_iter() - .find(|p| value.pointer(p).is_some_and(Value::is_array)) - .map(str::to_owned); - } - Family::Member(ReferenceCollection::FragmentBranches) => { - &["Occurrence/body/Fragment/branches"] - } - Family::Member(ReferenceCollection::FunctionInputItems) => { - &["Port/contract/FunctionInput/incoming_items"] - } - Family::Member(ReferenceCollection::FunctionOutputItems) => { - &["Port/contract/FunctionOutput/outgoing_items"] - } - Family::Member(ReferenceCollection::CategoryExchanges) => &["ExchangeCategory/exchanges"], - Family::Member(ReferenceCollection::SignalItems) => &["Event/body/Signal/items"], - Family::Member(ReferenceCollection::Constrained) => &["Constraint/constrained"], - Family::Literal => &["DataType/definition/Enumeration/literals"], - Family::Annotation => &["Occurrence/body/Exchange/values"], - Family::Binding => &["Occurrence/body/ScenarioUse/bindings"], - Family::Member(ReferenceCollection::ExchangeItems) => { - return [ - "Interaction", - "FunctionalExchange", - "BoundaryExchange", - "CommunicationMean", - ] - .iter() - .map(|kind| format!("/body/{kind}/items")) - .find(|p| value.pointer(p).is_some_and(Value::is_array)); - } - Family::Member(ReferenceCollection::TransitionTriggers) => { - return value - .pointer("/body/StateTransition/triggers") - .is_some_and(Value::is_array) - .then(|| "/body/StateTransition/triggers".into()); - } - Family::Member(ReferenceCollection::TransitionEffects) => { - return value - .pointer("/body/StateTransition/effects") - .is_some_and(Value::is_array) - .then(|| "/body/StateTransition/effects".into()); - } - }; - prefixes - .iter() - .flat_map(|prefix| tails.iter().map(move |tail| format!("{prefix}/{tail}"))) - .find(|p| value.pointer(p).is_some_and(Value::is_array)) -} -pub(super) fn key(facet: &FacetSelector, layer: Layer) -> Option { - match facet { - FacetSelector::Member { keys, .. } => if layer == Layer::Oa { keys.oa } else { keys.sa } - .map(|id| serde_json::to_value(id).expect("Id")), - FacetSelector::Literal(keys) => if layer == Layer::Oa { - keys.oa.as_ref() - } else { - keys.sa.as_ref() - } - .map(|v| Value::String(v.clone())), - FacetSelector::Annotation(keys) => if layer == Layer::Oa { - keys.oa.as_ref() - } else { - keys.sa.as_ref() - } - .map(|key| serde_json::to_value(key).expect("Annotation key")), - FacetSelector::Binding(keys) => if layer == Layer::Oa { keys.oa } else { keys.sa } - .map(|id| serde_json::to_value(id).expect("Binding key")), - _ => None, - } -} -pub(super) fn overlap(a: &FacetSelector, b: &FacetSelector) -> bool { - if family(a).is_none() || family(a) != family(b) { - return false; - } - [Layer::Oa, Layer::Sa] - .into_iter() - .any(|layer| key(a, layer).is_some() && key(a, layer) == key(b, layer)) -} -pub(super) fn project_presence( - value: &mut Value, - facet: &FacetSelector, - layer: Layer, - present: bool, -) -> Result<(), String> { - let family = family(facet).ok_or("This keyed family remains read-only.")?; - let key = key(facet, layer) - .ok_or("Choose the destination identity in Members before setting or taking presence.")?; - let path = - path(value, family).ok_or("This collection is not applicable to the destination kind.")?; - let list = value - .pointer_mut(&path) - .and_then(Value::as_array_mut) - .ok_or("Destination collection is unavailable.")?; - if present { - if !list.contains(&key) { - list.push(key); - } - } else { - list.retain(|v| v != &key); - } - Ok(()) -} - -impl ReviewRow { - pub(super) fn keyed_value( - &self, - project: &Project, - trace: &Trace, - value: &mut Value, - facet: &FacetSelector, - decision: SemanticDecision, - ) -> Result<(), String> { - if keyed_rows::supported(facet) { - return self.project_row(project, trace, value, facet, decision); - } - let present = match decision { - SemanticDecision::KeepTarget => return Ok(()), - SemanticDecision::TakeSource => { - // A missing source identity is a target-local row, not a removal. - if key(facet, self.direction.source_layer()).is_none() { - return Ok(()); - } - let fact = capture_facet(project, trace.oa, trace.sa, facet)?; - scalar::value(&fact, self.direction.source_layer()).and_then(|v| v.as_bool()).ok_or("Source collection is not applicable; choose an explicit final presence or Keep.")? - } - SemanticDecision::Candidate => *self - .presence - .get(facet) - .ok_or("Choose final membership presence.")?, - }; - project_presence(value, facet, self.direction.target_layer(), present) - } - - pub(super) fn member_families(&self, project: &Project) -> Vec { - let Some(trace) = self.effective_trace(project) else { - return vec![]; - }; - let values: Vec<_> = [trace.oa, trace.sa] - .into_iter() - .filter_map(|id| Draft::from_project(project, id).map(|d| d.value)) - .collect(); - FAMILIES - .into_iter() - .filter(|family| values.iter().any(|value| path(value, *family).is_some())) - .collect() - } - - pub(super) fn member_choices(&self, project: &Project, layer: Layer) -> Vec { - if self.member_family == Family::Binding { - return self.binding_choices(project, layer, true); - } - let Some(trace) = self.effective_trace(project) else { - return vec![]; - }; - let id = if layer == Layer::Oa { - trace.oa - } else { - trace.sa - }; - let Some(mut draft) = Draft::from_project(project, id) else { - return vec![]; - }; - if layer == self.direction.target_layer() { - draft.set_context(self.combined_value(project, true).ok()); - } - let selected = if layer == Layer::Oa { - &self.member_oa - } else { - &self.member_sa - }; - let Some(path) = path(&draft.context_value(), self.member_family) else { - return selected.iter().cloned().collect(); - }; - crate::forms::reference_choices( - &draft, - project, - &format!("{path}/0"), - selected.as_ref().map_or(Id::nil(), |c| c.id), - ) - .into_iter() - .filter(|c| !c.id.is_nil()) - .collect() - } - - pub(super) fn edit_pair( - &mut self, - project: &Project, - facet: FacetSelector, - ) -> Result<(), String> { - self.member_family = family(&facet).ok_or("This keyed family remains read-only.")?; - match facet { - FacetSelector::Annotation(keys) => { - self.annotation_oa.selected = keys.oa.map(|key| crate::data_values::FieldPathKey { - path: key.path, - field: key.field, - }); - self.annotation_sa.selected = keys.sa.map(|key| crate::data_values::FieldPathKey { - path: key.path, - field: key.field, - }); - } - FacetSelector::Binding(keys) => { - self.member_oa = keys.oa.map(|id| view::choice(project, id)); - self.member_sa = keys.sa.map(|id| view::choice(project, id)); - } - FacetSelector::Member { keys, .. } => { - self.member_oa = keys.oa.map(|id| view::choice(project, id)); - self.member_sa = keys.sa.map(|id| view::choice(project, id)); - } - FacetSelector::Literal(keys) => { - self.literal_oa = keys.oa.is_some(); - self.literal_sa = keys.sa.is_some(); - self.literal = keys.oa.or(keys.sa).unwrap_or_default(); - } - _ => unreachable!(), - } - self.tab = Tab::Members; - Ok(()) - } - - pub(super) fn stage_member(&mut self, project: &Project) -> Result<(), String> { - if !FAMILIES.contains(&self.member_family) { - return Err("This keyed family remains read-only.".into()); - } - let facet = match self.member_family { - Family::Member(collection) => FacetSelector::Member { - collection, - keys: KeyPair { - oa: self.member_oa.as_ref().map(|c| c.id), - sa: self.member_sa.as_ref().map(|c| c.id), - }, - }, - Family::Literal => FacetSelector::Literal(KeyPair { - oa: self.literal_oa.then(|| self.literal.clone()), - sa: self.literal_sa.then(|| self.literal.clone()), - }), - Family::Annotation => FacetSelector::Annotation(KeyPair { - oa: self - .annotation_oa - .selected - .as_ref() - .map(|key| AnnotationKey { - path: key.path.clone(), - field: key.field, - }), - sa: self - .annotation_sa - .selected - .as_ref() - .map(|key| AnnotationKey { - path: key.path.clone(), - field: key.field, - }), - }), - Family::Binding => FacetSelector::Binding(KeyPair { - oa: self.member_oa.as_ref().map(|c| c.id), - sa: self.member_sa.as_ref().map(|c| c.id), - }), - }; - let trace = self.effective_trace(project).ok_or("Choose a trace.")?; - capture_facet(project, trace.oa, trace.sa, &facet)?; - self.stage_row_references(&facet)?; - if let FacetSelector::Member { - keys: - KeyPair { - oa: Some(oa), - sa: Some(sa), - }, - .. - } = &facet - { - self.references.insert(*oa, *sa); - } - self.staged.insert(facet.clone()); - self.selected = Some(facet); - self.tab = Tab::Facts; - Ok(()) - } -} +//! Closed presence-only adapter. The core independently validates the full final record. +use super::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum Family { + Member(ReferenceCollection), + Literal, + Annotation, + Binding, +} +impl std::fmt::Display for Family { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Member(ReferenceCollection::FunctionInputItems) => "Function input items", + Self::Member(ReferenceCollection::FunctionOutputItems) => "Function output items", + Self::Member(ReferenceCollection::SignalItems) => "Signal items", + Self::Member(ReferenceCollection::CategoryExchanges) => "Category exchanges", + Self::Member(ReferenceCollection::ExchangeItems) => "Exchange items", + Self::Member(ReferenceCollection::TransitionTriggers) => "Transition triggers", + Self::Member(ReferenceCollection::TransitionEffects) => "Transition effects", + Self::Member(ReferenceCollection::Constrained) => "Constrained identities", + Self::Member(ReferenceCollection::Steps) => "Ordered steps", + Self::Member(ReferenceCollection::FragmentBranches) => "Fragment branches", + Self::Literal => "Enumeration literals", + Self::Annotation => "Message values", + Self::Binding => "Scenario lifeline bindings", + }) + } +} +pub(super) const FAMILIES: [Family; 13] = [ + Family::Member(ReferenceCollection::Steps), + Family::Member(ReferenceCollection::FragmentBranches), + Family::Member(ReferenceCollection::FunctionInputItems), + Family::Member(ReferenceCollection::FunctionOutputItems), + Family::Member(ReferenceCollection::SignalItems), + Family::Member(ReferenceCollection::ExchangeItems), + Family::Member(ReferenceCollection::CategoryExchanges), + Family::Member(ReferenceCollection::TransitionTriggers), + Family::Member(ReferenceCollection::TransitionEffects), + Family::Member(ReferenceCollection::Constrained), + Family::Literal, + Family::Annotation, + Family::Binding, +]; +pub(super) fn family(facet: &FacetSelector) -> Option { + let family = match facet { + FacetSelector::Member { collection, .. } => Family::Member(*collection), + FacetSelector::Literal(_) => Family::Literal, + FacetSelector::Annotation(_) => Family::Annotation, + FacetSelector::Binding(_) => Family::Binding, + _ => return None, + }; + FAMILIES.contains(&family).then_some(family) +} +pub(super) fn supported(facet: &FacetSelector) -> bool { + family(facet).is_some() +} + +// Fixed paths for the closed collections and the exact enum literal set. +// They are selected by the typed facet; callers cannot supply arbitrary paths. +pub(super) fn path(value: &Value, family: Family) -> Option { + let prefixes = ["/body/Oa/Shared", "/body/Sa/Shared"]; + let tails: &[&str] = match family { + Family::Member(ReferenceCollection::Steps) => { + return [ + "/body/Oa/Process/steps", + "/body/Sa/FunctionalChain/steps", + "/body/Oa/Shared/Scenario/steps", + "/body/Sa/Shared/Scenario/steps", + "/body/Oa/Shared/Occurrence/body/Branch/occurrences", + "/body/Sa/Shared/Occurrence/body/Branch/occurrences", + ] + .into_iter() + .find(|p| value.pointer(p).is_some_and(Value::is_array)) + .map(str::to_owned); + } + Family::Member(ReferenceCollection::FragmentBranches) => { + &["Occurrence/body/Fragment/branches"] + } + Family::Member(ReferenceCollection::FunctionInputItems) => { + &["Port/contract/FunctionInput/incoming_items"] + } + Family::Member(ReferenceCollection::FunctionOutputItems) => { + &["Port/contract/FunctionOutput/outgoing_items"] + } + Family::Member(ReferenceCollection::CategoryExchanges) => &["ExchangeCategory/exchanges"], + Family::Member(ReferenceCollection::SignalItems) => &["Event/body/Signal/items"], + Family::Member(ReferenceCollection::Constrained) => &["Constraint/constrained"], + Family::Literal => &["DataType/definition/Enumeration/literals"], + Family::Annotation => &["Occurrence/body/Exchange/values"], + Family::Binding => &["Occurrence/body/ScenarioUse/bindings"], + Family::Member(ReferenceCollection::ExchangeItems) => { + return [ + "Interaction", + "FunctionalExchange", + "BoundaryExchange", + "CommunicationMean", + ] + .iter() + .map(|kind| format!("/body/{kind}/items")) + .find(|p| value.pointer(p).is_some_and(Value::is_array)); + } + Family::Member(ReferenceCollection::TransitionTriggers) => { + return value + .pointer("/body/StateTransition/triggers") + .is_some_and(Value::is_array) + .then(|| "/body/StateTransition/triggers".into()); + } + Family::Member(ReferenceCollection::TransitionEffects) => { + return value + .pointer("/body/StateTransition/effects") + .is_some_and(Value::is_array) + .then(|| "/body/StateTransition/effects".into()); + } + }; + prefixes + .iter() + .flat_map(|prefix| tails.iter().map(move |tail| format!("{prefix}/{tail}"))) + .find(|p| value.pointer(p).is_some_and(Value::is_array)) +} +pub(super) fn key(facet: &FacetSelector, layer: Layer) -> Option { + match facet { + FacetSelector::Member { keys, .. } => if layer == Layer::Oa { keys.oa } else { keys.sa } + .map(|id| serde_json::to_value(id).expect("Id")), + FacetSelector::Literal(keys) => if layer == Layer::Oa { + keys.oa.as_ref() + } else { + keys.sa.as_ref() + } + .map(|v| Value::String(v.clone())), + FacetSelector::Annotation(keys) => if layer == Layer::Oa { + keys.oa.as_ref() + } else { + keys.sa.as_ref() + } + .map(|key| serde_json::to_value(key).expect("Annotation key")), + FacetSelector::Binding(keys) => if layer == Layer::Oa { keys.oa } else { keys.sa } + .map(|id| serde_json::to_value(id).expect("Binding key")), + _ => None, + } +} +pub(super) fn overlap(a: &FacetSelector, b: &FacetSelector) -> bool { + if family(a).is_none() || family(a) != family(b) { + return false; + } + [Layer::Oa, Layer::Sa] + .into_iter() + .any(|layer| key(a, layer).is_some() && key(a, layer) == key(b, layer)) +} +pub(super) fn project_presence( + value: &mut Value, + facet: &FacetSelector, + layer: Layer, + present: bool, +) -> Result<(), String> { + let family = family(facet).ok_or("This keyed family remains read-only.")?; + let key = key(facet, layer) + .ok_or("Choose the destination identity in Members before setting or taking presence.")?; + let path = + path(value, family).ok_or("This collection is not applicable to the destination kind.")?; + let list = value + .pointer_mut(&path) + .and_then(Value::as_array_mut) + .ok_or("Destination collection is unavailable.")?; + if present { + if !list.contains(&key) { + list.push(key); + } + } else { + list.retain(|v| v != &key); + } + Ok(()) +} + +impl ReviewRow { + pub(super) fn keyed_value( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + facet: &FacetSelector, + decision: SemanticDecision, + ) -> Result<(), String> { + if keyed_rows::supported(facet) { + return self.project_row(project, trace, value, facet, decision); + } + let present = match decision { + SemanticDecision::KeepTarget => return Ok(()), + SemanticDecision::TakeSource => { + // A missing source identity is a target-local row, not a removal. + if key(facet, self.direction.source_layer()).is_none() { + return Ok(()); + } + let fact = capture_facet(project, trace.oa, trace.sa, facet)?; + scalar::value(&fact, self.direction.source_layer()).and_then(|v| v.as_bool()).ok_or("Source collection is not applicable; choose an explicit final presence or Keep.")? + } + SemanticDecision::Candidate => *self + .presence + .get(facet) + .ok_or("Choose final membership presence.")?, + }; + project_presence(value, facet, self.direction.target_layer(), present) + } + + pub(super) fn member_families(&self, project: &Project) -> Vec { + let Some(trace) = self.effective_trace(project) else { + return vec![]; + }; + let values: Vec<_> = [trace.oa, trace.sa] + .into_iter() + .filter_map(|id| Draft::from_project(project, id).map(|d| d.value)) + .collect(); + FAMILIES + .into_iter() + .filter(|family| values.iter().any(|value| path(value, *family).is_some())) + .collect() + } + + pub(super) fn member_choices(&self, project: &Project, layer: Layer) -> Vec { + if self.member_family == Family::Binding { + return self.binding_choices(project, layer, true); + } + let Some(trace) = self.effective_trace(project) else { + return vec![]; + }; + let id = if layer == Layer::Oa { + trace.oa + } else { + trace.sa + }; + let Some(mut draft) = Draft::from_project(project, id) else { + return vec![]; + }; + if layer == self.direction.target_layer() { + draft.set_context(self.combined_value(project, true).ok()); + } + let selected = if layer == Layer::Oa { + &self.member_oa + } else { + &self.member_sa + }; + let Some(path) = path(&draft.context_value(), self.member_family) else { + return selected.iter().cloned().collect(); + }; + crate::forms::reference_choices( + &draft, + project, + &format!("{path}/0"), + selected.as_ref().map_or(Id::nil(), |c| c.id), + ) + .into_iter() + .filter(|c| !c.id.is_nil()) + .collect() + } + + pub(super) fn edit_pair( + &mut self, + project: &Project, + facet: FacetSelector, + ) -> Result<(), String> { + self.member_family = family(&facet).ok_or("This keyed family remains read-only.")?; + match facet { + FacetSelector::Annotation(keys) => { + self.annotation_oa.selected = keys.oa.map(|key| crate::data_values::FieldPathKey { + path: key.path, + field: key.field, + }); + self.annotation_sa.selected = keys.sa.map(|key| crate::data_values::FieldPathKey { + path: key.path, + field: key.field, + }); + } + FacetSelector::Binding(keys) => { + self.member_oa = keys.oa.map(|id| view::choice(project, id)); + self.member_sa = keys.sa.map(|id| view::choice(project, id)); + } + FacetSelector::Member { keys, .. } => { + self.member_oa = keys.oa.map(|id| view::choice(project, id)); + self.member_sa = keys.sa.map(|id| view::choice(project, id)); + } + FacetSelector::Literal(keys) => { + self.literal_oa = keys.oa.is_some(); + self.literal_sa = keys.sa.is_some(); + self.literal = keys.oa.or(keys.sa).unwrap_or_default(); + } + _ => unreachable!(), + } + self.tab = Tab::Members; + Ok(()) + } + + pub(super) fn stage_member(&mut self, project: &Project) -> Result<(), String> { + if !FAMILIES.contains(&self.member_family) { + return Err("This keyed family remains read-only.".into()); + } + let facet = match self.member_family { + Family::Member(collection) => FacetSelector::Member { + collection, + keys: KeyPair { + oa: self.member_oa.as_ref().map(|c| c.id), + sa: self.member_sa.as_ref().map(|c| c.id), + }, + }, + Family::Literal => FacetSelector::Literal(KeyPair { + oa: self.literal_oa.then(|| self.literal.clone()), + sa: self.literal_sa.then(|| self.literal.clone()), + }), + Family::Annotation => FacetSelector::Annotation(KeyPair { + oa: self + .annotation_oa + .selected + .as_ref() + .map(|key| AnnotationKey { + path: key.path.clone(), + field: key.field, + }), + sa: self + .annotation_sa + .selected + .as_ref() + .map(|key| AnnotationKey { + path: key.path.clone(), + field: key.field, + }), + }), + Family::Binding => FacetSelector::Binding(KeyPair { + oa: self.member_oa.as_ref().map(|c| c.id), + sa: self.member_sa.as_ref().map(|c| c.id), + }), + }; + let trace = self.effective_trace(project).ok_or("Choose a trace.")?; + capture_facet(project, trace.oa, trace.sa, &facet)?; + self.stage_row_references(&facet)?; + if let FacetSelector::Member { + keys: + KeyPair { + oa: Some(oa), + sa: Some(sa), + }, + .. + } = &facet + { + self.references.insert(*oa, *sa); + } + self.staged.insert(facet.clone()); + self.selected = Some(facet); + self.tab = Tab::Facts; + Ok(()) + } +} diff --git a/crates/oa-desktop/src/semantic_review/keyed_rows.rs b/crates/oa-desktop/src/semantic_review/keyed_rows.rs index 1962431a..d33d4985 100644 --- a/crates/oa-desktop/src/semantic_review/keyed_rows.rs +++ b/crates/oa-desktop/src/semantic_review/keyed_rows.rs @@ -1,345 +1,345 @@ -//! Atomic message-value and lifeline-binding rows with exact, immutable keys. -use super::*; -use crate::data_values::{FieldPathChoice, FieldPathKey}; - -#[derive(Default)] -pub struct AddressDraft { - pub selected: Option, - pub scope: Option, - pub page: usize, -} - -#[derive(Default)] -pub struct RowDraft { - pub expression: String, - pub editor: text_editor::Content, - pub local: Option, -} - -pub(super) fn supported(facet: &FacetSelector) -> bool { - matches!( - facet, - FacetSelector::Annotation(_) | FacetSelector::Binding(_) - ) -} - -pub(super) fn target_selector(facet: &FacetSelector, layer: Layer) -> FacetSelector { - match facet { - FacetSelector::Annotation(keys) => FacetSelector::Annotation(KeyPair { - oa: (layer == Layer::Oa).then(|| keys.oa.clone()).flatten(), - sa: (layer == Layer::Sa).then(|| keys.sa.clone()).flatten(), - }), - FacetSelector::Binding(keys) => FacetSelector::Binding(KeyPair { - oa: if layer == Layer::Oa { keys.oa } else { None }, - sa: if layer == Layer::Sa { keys.sa } else { None }, - }), - _ => facet.clone(), - } -} - -impl ReviewRow { - pub(super) fn annotation_address(&self, layer: Layer) -> &AddressDraft { - if layer == Layer::Oa { - &self.annotation_oa - } else { - &self.annotation_sa - } - } - - pub(super) fn annotation_address_mut(&mut self, layer: Layer) -> &mut AddressDraft { - if layer == Layer::Oa { - &mut self.annotation_oa - } else { - &mut self.annotation_sa - } - } - - fn row_owner(&self, project: &Project, layer: Layer) -> Option { - let trace = self.effective_trace(project)?; - let id = if layer == Layer::Oa { - trace.oa - } else { - trace.sa - }; - if layer == self.direction.target_layer() - && let Ok(value) = self.combined_value(project, true) - && let Ok(element) = serde_json::from_value(value) - { - return Some(element); - } - project.elements.get(&id).cloned() - } - - pub(super) fn annotation_choices( - &self, - project: &Project, - layer: Layer, - ) -> Result, String> { - let element = self.row_owner(project, layer).ok_or("Choose a trace.")?; - let Some(SharedBody::Occurrence { - body: - OccurrenceBody::Exchange { - exchange: Some(exchange), - message_kind, - .. - }, - .. - }) = element.shared() - else { - return Err( - "Choose an exchange on this message before selecting a carried field.".into(), - ); - }; - let exchange = project - .relations - .get(exchange) - .ok_or("The message exchange is unavailable.")?; - crate::data_values::at_scope( - project, - exchange, - message_kind, - self.annotation_address(layer).scope.as_ref(), - ) - } - - pub(super) fn binding_choices( - &self, - project: &Project, - layer: Layer, - referenced: bool, - ) -> Vec { - let Some(element) = self.row_owner(project, layer) else { - return vec![]; - }; - let scenario = if referenced { - match element.shared() { - Some(SharedBody::Occurrence { - body: OccurrenceBody::ScenarioUse { scenario, .. }, - .. - }) => *scenario, - _ => None, - } - } else { - element - .parent - .and_then(|parent| validation::ancestor_of_kind(project, parent, Kind::Scenario)) - }; - let mut choices: Vec<_> = project - .elements - .values() - .filter(|e| { - e.kind() == Kind::Lifeline - && e.layer() == layer - && scenario.is_some() - && e.parent == scenario - }) - .map(|e| view::choice(project, e.id)) - .collect(); - choices.sort_by_cached_key(|c| c.label.to_lowercase()); - choices - } - - pub(super) fn stage_row_references(&mut self, facet: &FacetSelector) -> Result<(), String> { - let pairs: Vec<_> = match facet { - FacetSelector::Annotation(KeyPair { - oa: Some(oa), - sa: Some(sa), - }) => { - if oa.path.len() != sa.path.len() { - return Err("Paired annotation paths must have the same depth; review distinct addresses separately.".into()); - } - oa.path - .iter() - .chain(std::iter::once(&oa.field)) - .copied() - .zip(sa.path.iter().chain(std::iter::once(&sa.field)).copied()) - .collect() - } - FacetSelector::Binding(KeyPair { - oa: Some(oa), - sa: Some(sa), - }) => vec![(*oa, *sa)], - _ => vec![], - }; - // A repeated recursive field may occur several times in one address. - // Never let the last occurrence silently replace a conflicting pair. - let mut supplied = BTreeMap::new(); - for (oa, sa) in pairs { - if supplied.insert(oa, sa).is_some_and(|prior| prior != sa) { - return Err( - "This address pairs the same Operational field with different System fields." - .into(), - ); - } - } - self.references.extend(supplied); - Ok(()) - } - - pub(super) fn initialize_row(&mut self, project: &Project, facet: &FacetSelector) { - if self.row_drafts.contains_key(facet) { - return; - } - let value = self - .effective_trace(project) - .and_then(|t| capture_facet(project, t.oa, t.sa, facet).ok()) - .and_then(|f| scalar::value(&f, self.direction.target_layer())); - let present = value.as_ref().is_some_and(|v| !v.is_null()); - let mut draft = RowDraft::default(); - if let Some(value) = value { - draft.expression = value["expression"].as_str().unwrap_or_default().into(); - draft.editor = text_editor::Content::with_text(&draft.expression); - draft.local = value["local"] - .as_str() - .and_then(|v| v.parse().ok()) - .map(|id| view::choice(project, id)); - } - self.presence.entry(facet.clone()).or_insert(present); - self.row_drafts.insert(facet.clone(), draft); - } - - pub(super) fn edit_row( - &mut self, - facet: &FacetSelector, - action: impl FnOnce(&mut RowDraft), - ) -> Result<(), String> { - if !supported(facet) - || self.selected.as_ref() != Some(facet) - || self.decisions.get(facet) != Some(&SemanticDecision::Candidate) - { - return Err("Select this row and choose Set final value before editing it.".into()); - } - action( - self.row_drafts - .get_mut(facet) - .ok_or("The row draft is unavailable.")?, - ); - Ok(()) - } - - pub(super) fn project_row( - &self, - project: &Project, - trace: &Trace, - value: &mut Value, - facet: &FacetSelector, - decision: SemanticDecision, - ) -> Result<(), String> { - if decision == SemanticDecision::KeepTarget { - return Ok(()); - } - let source_layer = self.direction.source_layer(); - let target_layer = self.direction.target_layer(); - if decision == SemanticDecision::TakeSource && keyed::key(facet, source_layer).is_none() { - return Ok(()); - } - let target_key = keyed::key(facet, target_layer) - .ok_or("Choose the destination row address before setting or taking its value.")?; - let row = if decision == SemanticDecision::Candidate { - if !self.presence.get(facet).copied().unwrap_or(false) { - Value::Null - } else { - let draft = self - .row_drafts - .get(facet) - .ok_or("Set the final row value.")?; - match facet { - FacetSelector::Annotation(_) => { - let key: AnnotationKey = serde_json::from_value(target_key.clone()) - .map_err(|e| e.to_string())?; - serde_json::to_value(MessageValue { - path: key.path, - field: key.field, - expression: draft.expression.clone(), - }) - .expect("Message value") - } - FacetSelector::Binding(_) => { - serde_json::json!({"referenced": target_key, "local": draft.local.as_ref().ok_or("Choose the destination scenario's local lifeline.")?.id}) - } - _ => return Err("Unsupported row.".into()), - } - } - } else { - let fact = capture_facet(project, trace.oa, trace.sa, facet)?; - let row = scalar::value(&fact, source_layer) - .ok_or("Source row collection is not applicable; review its constructor first.")?; - if row.is_null() { - row - } else { - let map = |id: Id| -> Result { - if source_layer == Layer::Oa { - self.references.get(&id).copied().ok_or_else(|| { - format!("Choose a reference counterpart for {}.", project.named(id)) - }) - } else { - let matches: Vec<_> = self - .references - .iter() - .filter(|(_, sa)| **sa == id) - .map(|(oa, _)| *oa) - .collect(); - if matches.len() == 1 { - Ok(matches[0]) - } else { - Err(format!( - "Choose one unambiguous reference counterpart for {}.", - project.named(id) - )) - } - } - }; - match facet { - FacetSelector::Annotation(_) => { - let mut row: MessageValue = - serde_json::from_value(row).map_err(|e| e.to_string())?; - row.path = row.path.into_iter().map(map).collect::>()?; - row.field = map(row.field)?; - if serde_json::to_value(AnnotationKey::from(&row)).expect("key") - != target_key - { - return Err("Mapped annotation address disagrees with the selected destination address.".into()); - } - serde_json::to_value(row).expect("row") - } - FacetSelector::Binding(_) => { - let row: LifelineBinding = - serde_json::from_value(row).map_err(|e| e.to_string())?; - let mapped = LifelineBinding { - referenced: map(row.referenced)?, - local: map(row.local)?, - }; - if serde_json::to_value(mapped.referenced).expect("Id") != target_key { - return Err("Mapped binding key disagrees with the selected destination lifeline.".into()); - } - serde_json::to_value(mapped).expect("row") - } - _ => return Err("Unsupported row.".into()), - } - } - }; - let family = keyed::family(facet).ok_or("Unsupported row.")?; - let path = keyed::path(value, family) - .ok_or("The destination constructor has no collection for this row.")?; - let rows = value - .pointer_mut(&path) - .and_then(Value::as_array_mut) - .ok_or("Destination rows are unavailable.")?; - let position = rows.iter().position(|row| match facet { - FacetSelector::Annotation(_) => { - serde_json::json!({"path": row["path"], "field": row["field"]}) == target_key - } - FacetSelector::Binding(_) => row["referenced"] == target_key, - _ => false, - }); - match (position, row.is_null()) { - (Some(index), false) => rows[index] = row, - (Some(index), true) => { - rows.remove(index); - } - (None, false) => rows.push(row), - (None, true) => {} - } - Ok(()) - } -} +//! Atomic message-value and lifeline-binding rows with exact, immutable keys. +use super::*; +use crate::data_values::{FieldPathChoice, FieldPathKey}; + +#[derive(Default)] +pub struct AddressDraft { + pub selected: Option, + pub scope: Option, + pub page: usize, +} + +#[derive(Default)] +pub struct RowDraft { + pub expression: String, + pub editor: text_editor::Content, + pub local: Option, +} + +pub(super) fn supported(facet: &FacetSelector) -> bool { + matches!( + facet, + FacetSelector::Annotation(_) | FacetSelector::Binding(_) + ) +} + +pub(super) fn target_selector(facet: &FacetSelector, layer: Layer) -> FacetSelector { + match facet { + FacetSelector::Annotation(keys) => FacetSelector::Annotation(KeyPair { + oa: (layer == Layer::Oa).then(|| keys.oa.clone()).flatten(), + sa: (layer == Layer::Sa).then(|| keys.sa.clone()).flatten(), + }), + FacetSelector::Binding(keys) => FacetSelector::Binding(KeyPair { + oa: if layer == Layer::Oa { keys.oa } else { None }, + sa: if layer == Layer::Sa { keys.sa } else { None }, + }), + _ => facet.clone(), + } +} + +impl ReviewRow { + pub(super) fn annotation_address(&self, layer: Layer) -> &AddressDraft { + if layer == Layer::Oa { + &self.annotation_oa + } else { + &self.annotation_sa + } + } + + pub(super) fn annotation_address_mut(&mut self, layer: Layer) -> &mut AddressDraft { + if layer == Layer::Oa { + &mut self.annotation_oa + } else { + &mut self.annotation_sa + } + } + + fn row_owner(&self, project: &Project, layer: Layer) -> Option { + let trace = self.effective_trace(project)?; + let id = if layer == Layer::Oa { + trace.oa + } else { + trace.sa + }; + if layer == self.direction.target_layer() + && let Ok(value) = self.combined_value(project, true) + && let Ok(element) = serde_json::from_value(value) + { + return Some(element); + } + project.elements.get(&id).cloned() + } + + pub(super) fn annotation_choices( + &self, + project: &Project, + layer: Layer, + ) -> Result, String> { + let element = self.row_owner(project, layer).ok_or("Choose a trace.")?; + let Some(SharedBody::Occurrence { + body: + OccurrenceBody::Exchange { + exchange: Some(exchange), + message_kind, + .. + }, + .. + }) = element.shared() + else { + return Err( + "Choose an exchange on this message before selecting a carried field.".into(), + ); + }; + let exchange = project + .relations + .get(exchange) + .ok_or("The message exchange is unavailable.")?; + crate::data_values::at_scope( + project, + exchange, + message_kind, + self.annotation_address(layer).scope.as_ref(), + ) + } + + pub(super) fn binding_choices( + &self, + project: &Project, + layer: Layer, + referenced: bool, + ) -> Vec { + let Some(element) = self.row_owner(project, layer) else { + return vec![]; + }; + let scenario = if referenced { + match element.shared() { + Some(SharedBody::Occurrence { + body: OccurrenceBody::ScenarioUse { scenario, .. }, + .. + }) => *scenario, + _ => None, + } + } else { + element + .parent + .and_then(|parent| validation::ancestor_of_kind(project, parent, Kind::Scenario)) + }; + let mut choices: Vec<_> = project + .elements + .values() + .filter(|e| { + e.kind() == Kind::Lifeline + && e.layer() == layer + && scenario.is_some() + && e.parent == scenario + }) + .map(|e| view::choice(project, e.id)) + .collect(); + choices.sort_by_cached_key(|c| c.label.to_lowercase()); + choices + } + + pub(super) fn stage_row_references(&mut self, facet: &FacetSelector) -> Result<(), String> { + let pairs: Vec<_> = match facet { + FacetSelector::Annotation(KeyPair { + oa: Some(oa), + sa: Some(sa), + }) => { + if oa.path.len() != sa.path.len() { + return Err("Paired annotation paths must have the same depth; review distinct addresses separately.".into()); + } + oa.path + .iter() + .chain(std::iter::once(&oa.field)) + .copied() + .zip(sa.path.iter().chain(std::iter::once(&sa.field)).copied()) + .collect() + } + FacetSelector::Binding(KeyPair { + oa: Some(oa), + sa: Some(sa), + }) => vec![(*oa, *sa)], + _ => vec![], + }; + // A repeated recursive field may occur several times in one address. + // Never let the last occurrence silently replace a conflicting pair. + let mut supplied = BTreeMap::new(); + for (oa, sa) in pairs { + if supplied.insert(oa, sa).is_some_and(|prior| prior != sa) { + return Err( + "This address pairs the same Operational field with different System fields." + .into(), + ); + } + } + self.references.extend(supplied); + Ok(()) + } + + pub(super) fn initialize_row(&mut self, project: &Project, facet: &FacetSelector) { + if self.row_drafts.contains_key(facet) { + return; + } + let value = self + .effective_trace(project) + .and_then(|t| capture_facet(project, t.oa, t.sa, facet).ok()) + .and_then(|f| scalar::value(&f, self.direction.target_layer())); + let present = value.as_ref().is_some_and(|v| !v.is_null()); + let mut draft = RowDraft::default(); + if let Some(value) = value { + draft.expression = value["expression"].as_str().unwrap_or_default().into(); + draft.editor = text_editor::Content::with_text(&draft.expression); + draft.local = value["local"] + .as_str() + .and_then(|v| v.parse().ok()) + .map(|id| view::choice(project, id)); + } + self.presence.entry(facet.clone()).or_insert(present); + self.row_drafts.insert(facet.clone(), draft); + } + + pub(super) fn edit_row( + &mut self, + facet: &FacetSelector, + action: impl FnOnce(&mut RowDraft), + ) -> Result<(), String> { + if !supported(facet) + || self.selected.as_ref() != Some(facet) + || self.decisions.get(facet) != Some(&SemanticDecision::Candidate) + { + return Err("Select this row and choose Set final value before editing it.".into()); + } + action( + self.row_drafts + .get_mut(facet) + .ok_or("The row draft is unavailable.")?, + ); + Ok(()) + } + + pub(super) fn project_row( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + facet: &FacetSelector, + decision: SemanticDecision, + ) -> Result<(), String> { + if decision == SemanticDecision::KeepTarget { + return Ok(()); + } + let source_layer = self.direction.source_layer(); + let target_layer = self.direction.target_layer(); + if decision == SemanticDecision::TakeSource && keyed::key(facet, source_layer).is_none() { + return Ok(()); + } + let target_key = keyed::key(facet, target_layer) + .ok_or("Choose the destination row address before setting or taking its value.")?; + let row = if decision == SemanticDecision::Candidate { + if !self.presence.get(facet).copied().unwrap_or(false) { + Value::Null + } else { + let draft = self + .row_drafts + .get(facet) + .ok_or("Set the final row value.")?; + match facet { + FacetSelector::Annotation(_) => { + let key: AnnotationKey = serde_json::from_value(target_key.clone()) + .map_err(|e| e.to_string())?; + serde_json::to_value(MessageValue { + path: key.path, + field: key.field, + expression: draft.expression.clone(), + }) + .expect("Message value") + } + FacetSelector::Binding(_) => { + serde_json::json!({"referenced": target_key, "local": draft.local.as_ref().ok_or("Choose the destination scenario's local lifeline.")?.id}) + } + _ => return Err("Unsupported row.".into()), + } + } + } else { + let fact = capture_facet(project, trace.oa, trace.sa, facet)?; + let row = scalar::value(&fact, source_layer) + .ok_or("Source row collection is not applicable; review its constructor first.")?; + if row.is_null() { + row + } else { + let map = |id: Id| -> Result { + if source_layer == Layer::Oa { + self.references.get(&id).copied().ok_or_else(|| { + format!("Choose a reference counterpart for {}.", project.named(id)) + }) + } else { + let matches: Vec<_> = self + .references + .iter() + .filter(|(_, sa)| **sa == id) + .map(|(oa, _)| *oa) + .collect(); + if matches.len() == 1 { + Ok(matches[0]) + } else { + Err(format!( + "Choose one unambiguous reference counterpart for {}.", + project.named(id) + )) + } + } + }; + match facet { + FacetSelector::Annotation(_) => { + let mut row: MessageValue = + serde_json::from_value(row).map_err(|e| e.to_string())?; + row.path = row.path.into_iter().map(map).collect::>()?; + row.field = map(row.field)?; + if serde_json::to_value(AnnotationKey::from(&row)).expect("key") + != target_key + { + return Err("Mapped annotation address disagrees with the selected destination address.".into()); + } + serde_json::to_value(row).expect("row") + } + FacetSelector::Binding(_) => { + let row: LifelineBinding = + serde_json::from_value(row).map_err(|e| e.to_string())?; + let mapped = LifelineBinding { + referenced: map(row.referenced)?, + local: map(row.local)?, + }; + if serde_json::to_value(mapped.referenced).expect("Id") != target_key { + return Err("Mapped binding key disagrees with the selected destination lifeline.".into()); + } + serde_json::to_value(mapped).expect("row") + } + _ => return Err("Unsupported row.".into()), + } + } + }; + let family = keyed::family(facet).ok_or("Unsupported row.")?; + let path = keyed::path(value, family) + .ok_or("The destination constructor has no collection for this row.")?; + let rows = value + .pointer_mut(&path) + .and_then(Value::as_array_mut) + .ok_or("Destination rows are unavailable.")?; + let position = rows.iter().position(|row| match facet { + FacetSelector::Annotation(_) => { + serde_json::json!({"path": row["path"], "field": row["field"]}) == target_key + } + FacetSelector::Binding(_) => row["referenced"] == target_key, + _ => false, + }); + match (position, row.is_null()) { + (Some(index), false) => rows[index] = row, + (Some(index), true) => { + rows.remove(index); + } + (None, false) => rows.push(row), + (None, true) => {} + } + Ok(()) + } +} diff --git a/crates/oa-desktop/src/semantic_review/keyed_tests.rs b/crates/oa-desktop/src/semantic_review/keyed_tests.rs index c28714f8..5b3429fd 100644 --- a/crates/oa-desktop/src/semantic_review/keyed_tests.rs +++ b/crates/oa-desktop/src/semantic_review/keyed_tests.rs @@ -1,792 +1,792 @@ -use super::{tests::*, *}; -use crate::{app::App, document::Document}; - -fn errors(s: &State) -> String { - s.error - .iter() - .chain(s.plan.iter().flat_map(|p| &p.conflicts)) - .cloned() - .collect::>() - .join("; ") -} -fn put(p: &mut Project, owner: Id, facet: &FacetSelector, present: bool) { - let layer = p.layer_of(owner).unwrap(); - let mut value = Draft::from_project(p, owner).unwrap().value; - keyed::project_presence(&mut value, facet, layer, present).unwrap(); - if let Some(element) = p.elements.get_mut(&owner) { - *element = serde_json::from_value(value).unwrap(); - } else { - p.relations - .insert(owner, serde_json::from_value(value).unwrap()); - } -} -fn contains(p: &Project, owner: Id, facet: &FacetSelector, oa: Id, sa: Id) -> bool { - scalar::value( - &capture_facet(p, oa, sa, facet).unwrap(), - p.layer_of(owner).unwrap(), - ) - .unwrap() - .as_bool() - .unwrap() -} -fn history(p: &mut Project, trace: Id, selectors: &[FacetSelector]) { - let t = &p.traces[&trace]; - let baseline = TraceBaseline { - oa: p.properties(t.oa).unwrap(), - sa: p.properties(t.sa).unwrap(), - semantic: Some(SemanticBaseline { - facets: selectors - .iter() - .map(|s| capture_facet(p, t.oa, t.sa, s).unwrap()) - .collect(), - }), - }; - p.traces.get_mut(&trace).unwrap().baseline = Some(baseline); -} -fn literal(name: &str) -> FacetSelector { - FacetSelector::Literal(KeyPair { - oa: Some(name.into()), - sa: Some(name.into()), - }) -} -fn enum_pair() -> (Project, Id, Id, Id) { - let mut p = Project::empty(); - let mut ids = vec![]; - for layer in [Layer::Oa, Layer::Sa] { - let id = p.add_element(Kind::DataType, layer, "Enum", None).unwrap(); - *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::DataType { - definition: DataTypeDefinition::Enumeration { - literals: vec!["retained".into()], - }, - default: Some(DataValue::Enumeration("retained".into())), - unit: String::new(), - }; - ids.push(id); - } - let trace = add_trace(&mut p, ids[0], ids[1], "Literal review"); - (p, ids[0], ids[1], trace) -} -fn member_pair(collection: ReferenceCollection) -> (Project, Id, Id, Id, FacetSelector, [Id; 2]) { - let mut p = Project::empty(); - let mut owners = vec![]; - let mut keys = vec![]; - let mut locals = vec![]; - for layer in [Layer::Oa, Layer::Sa] { - let work = if layer == Layer::Oa { - Kind::Activity - } else { - Kind::Function - }; - let kind = match collection { - ReferenceCollection::TransitionTriggers => Kind::Event, - ReferenceCollection::TransitionEffects | ReferenceCollection::Constrained => work, - _ => Kind::ExchangeItem, - }; - let key = p.add_element(kind, layer, "Mapped member", None).unwrap(); - let local = p.add_element(kind, layer, "Local member", None).unwrap(); - let owner = match collection { - ReferenceCollection::SignalItems => { - let id = p.add_element(Kind::Event, layer, "Signal", None).unwrap(); - *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Event { - body: EventBody::Signal { items: vec![] }, - }; - id - } - ReferenceCollection::Constrained => p - .add_element(Kind::Constraint, layer, "Constraint", None) - .unwrap(), - ReferenceCollection::ExchangeItems => { - let a = p.add_element(work, layer, "From", None).unwrap(); - let b = p.add_element(work, layer, "To", None).unwrap(); - let id = p.relate( - if layer == Layer::Oa { - RelationKind::Interaction - } else { - RelationKind::FunctionalExchange - }, - "Exchange", - a, - b, - ); - if let RelationBody::FunctionalExchange { - source_port, - target_port, - .. - } = p.relations[&id].body - { - for port in [source_port, target_port] { - if let Some(SharedBody::Port { contract, .. }) = - p.elements.get_mut(&port).unwrap().shared_mut() - { - match contract { - PortContract::FunctionInput { incoming_items } => { - *incoming_items = vec![key, local] - } - PortContract::FunctionOutput { outgoing_items } => { - *outgoing_items = vec![key, local] - } - _ => unreachable!(), - } - } - } - } - id - } - ReferenceCollection::TransitionTriggers | ReferenceCollection::TransitionEffects => { - let machine = p - .add_element(Kind::StateMachine, layer, "Machine", None) - .unwrap(); - let region = p - .add_element(Kind::Region, layer, "Region", Some(machine)) - .unwrap(); - let state = p - .add_element(Kind::State, layer, "State", Some(region)) - .unwrap(); - p.relate(RelationKind::StateTransition, "Transition", state, state) - } - ReferenceCollection::FunctionInputItems | ReferenceCollection::FunctionOutputItems => { - let parent = p.add_element(work, layer, "Owner", None).unwrap(); - let id = Id::new_v4(); - let contract = if layer == Layer::Oa { - PortContract::Legacy - } else if collection == ReferenceCollection::FunctionInputItems { - PortContract::FunctionInput { - incoming_items: vec![], - } - } else { - PortContract::FunctionOutput { - outgoing_items: vec![], - } - }; - let mut port = ports::make_port(&p, id, parent, "Port", contract).unwrap(); - if layer == Layer::Oa - && let Some(SharedBody::Port { legacy, .. }) = port.shared_mut() - { - *legacy = Some(LegacyPortContract { - direction: PortDirection::InOut, - interface: None, - }); - } - p.elements.insert(id, port); - id - } - _ => unreachable!(), - }; - owners.push(owner); - keys.push(key); - locals.push(local); - } - let trace = add_trace(&mut p, owners[0], owners[1], "Membership review"); - let facet = FacetSelector::Member { - collection, - keys: KeyPair { - oa: Some(keys[0]), - sa: Some(keys[1]), - }, - }; - ( - p, - owners[0], - owners[1], - trace, - facet, - [locals[0], locals[1]], - ) -} -fn stage(app: &mut App, facet: &FacetSelector) { - send(app, RowEvent::EditPair(facet.clone())); - send(app, RowEvent::StageMember); - assert_eq!( - state(app).selected.as_ref(), - Some(facet), - "{}", - errors(state(app)) - ); -} - -#[test] -fn unordered_members_keep_take_candidate_preserve_other_rows_both_directions() { - for collection in [ - ReferenceCollection::SignalItems, - ReferenceCollection::ExchangeItems, - ReferenceCollection::TransitionTriggers, - ReferenceCollection::TransitionEffects, - ReferenceCollection::Constrained, - ] { - for reverse in [false, true] { - for decision in [ - SemanticDecision::KeepTarget, - SemanticDecision::TakeSource, - SemanticDecision::Candidate, - ] { - let (mut p, oa, sa, trace, facet, locals) = member_pair(collection); - let (source, target, local) = if reverse { - (sa, oa, locals[0]) - } else { - (oa, sa, locals[1]) - }; - put(&mut p, source, &facet, true); - let local_facet = FacetSelector::Member { - collection, - keys: KeyPair { - oa: reverse.then_some(local), - sa: (!reverse).then_some(local), - }, - }; - put(&mut p, target, &local_facet, true); - let mut app = App::with_project(p); - let before = app.document.snapshot(); - start(&mut app, source); - stage(&mut app, &facet); - decide(&mut app, facet.clone(), decision); - if decision == SemanticDecision::Candidate { - send(&mut app, RowEvent::Presence(facet.clone(), true)); - } - // A target-only Take is an explicit acknowledgment, never implicit removal. - decide(&mut app, local_facet.clone(), SemanticDecision::TakeSource); - assert!( - state(&app).can_apply(), - "{collection:?} reverse={reverse} {decision:?}: {}", - errors(state(&app)) - ); - let proposed = state(&app).proposed.clone().unwrap(); - assert_eq!(app.document.bundle(), &before.bundle); - apply(&mut app); - assert!(app.workflow.is_none(), "{}", app.status); - assert_eq!(app.project(), &proposed); - assert_eq!( - contains(app.project(), target, &facet, oa, sa), - decision != SemanticDecision::KeepTarget - ); - assert!(contains(app.project(), target, &local_facet, oa, sa)); - assert!(app.project().elements.contains_key(&local)); - assert_eq!( - app.project().traces[&trace] - .baseline - .as_ref() - .unwrap() - .semantic - .as_ref() - .unwrap() - .facets - .len(), - 2 - ); - let _ = app.update(Message::Undo); - assert_eq!(app.document.bundle(), &before.bundle); - } - } - } -} - -#[test] -fn function_port_members_use_existing_typed_choices_and_preserve_oa_legacy() { - for collection in [ - ReferenceCollection::FunctionInputItems, - ReferenceCollection::FunctionOutputItems, - ] { - let (p, oa, sa, _, facet, _) = member_pair(collection); - let FacetSelector::Member { keys, .. } = &facet else { - unreachable!() - }; - let mut app = App::with_project(p); - start(&mut app, oa); - assert_eq!( - state(&app).member_families(app.project()), - vec![keyed::Family::Member(collection)] - ); - send(&mut app, RowEvent::EditPair(facet.clone())); - assert!( - state(&app) - .member_choices(app.project(), Layer::Sa) - .iter() - .any(|c| Some(c.id) == keys.sa) - ); - send(&mut app, RowEvent::StageMember); - decide(&mut app, facet.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(facet.clone(), true)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - apply(&mut app); - assert!(contains(app.project(), sa, &facet, oa, sa)); - assert!(matches!( - app.project().elements[&oa].shared(), - Some(SharedBody::Port { - contract: PortContract::Legacy, - .. - }) - )); - start(&mut app, sa); - stage(&mut app, &facet); - decide(&mut app, facet.clone(), SemanticDecision::KeepTarget); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - decide(&mut app, facet.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(facet, true)); - assert!( - !state(&app).can_apply(), - "OA typed membership must remain inapplicable" - ); - } -} - -#[test] -fn exact_literal_new_rows_and_removals_commit_both_directions_without_default_loss() { - for reverse in [false, true] { - let (mut p, oa, sa, _) = enum_pair(); - let (source, target) = if reverse { (sa, oa) } else { (oa, sa) }; - let incoming = literal(" New value "); - put(&mut p, source, &incoming, true); - let removed = literal("old"); - put(&mut p, target, &removed, true); - let created = literal("new value"); - let mut app = App::with_project(p); - start(&mut app, source); - for (facet, decision) in [ - (&incoming, SemanticDecision::TakeSource), - (&removed, SemanticDecision::Candidate), - (&created, SemanticDecision::Candidate), - ] { - stage(&mut app, facet); - decide(&mut app, facet.clone(), decision); - if decision == SemanticDecision::Candidate { - send( - &mut app, - RowEvent::Presence(facet.clone(), facet == &created), - ); - } - } - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - apply(&mut app); - assert!(contains(app.project(), target, &incoming, oa, sa)); - assert!(contains(app.project(), target, &created, oa, sa)); - assert!(!contains(app.project(), target, &removed, oa, sa)); - assert!( - matches!(app.project().elements[&target].shared(),Some(SharedBody::DataType { default: Some(DataValue::Enumeration(value)), .. }) if value=="retained") - ); - let reopened = Document::from_bundle( - app.document.bundle().clone(), - source_project::ProjectLimits::default(), - ) - .unwrap(); - assert_eq!(reopened.project(), app.project()); - } -} - -#[test] -fn exact_reset_only_is_one_history_source_transaction_and_undo_redo_both_directions() { - for reverse in [false, true] { - let (mut p, oa, sa, trace) = enum_pair(); - let reset = literal("retained"); - let kept = FacetSelector::Header(HeaderFacet::Name); - history(&mut p, trace, &[reset.clone(), kept.clone()]); - let previous = p.traces[&trace].baseline.clone().unwrap(); - let mut app = App::with_project(p); - let mut bundle = app.document.bundle().clone(); - bundle - .files - .get_mut(&bundle.entry) - .unwrap() - .insert_str(0, "// Exact review rationale survives reset.\n"); - app.document.apply_bundle(bundle).unwrap(); - let before = app.document.snapshot(); - let revision = app.document.revision(); - start(&mut app, if reverse { sa } else { oa }); - send(&mut app, RowEvent::Reset(reset.clone(), true)); - assert!(state(&app).decisions.is_empty()); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - assert_eq!( - state(&app) - .facts - .iter() - .find(|f| f.selector == reset) - .unwrap() - .drift, - Drift::Unreviewed - ); - send(&mut app, RowEvent::Reset(reset.clone(), false)); - assert!(!state(&app).can_apply()); - assert_eq!( - state(&app) - .facts - .iter() - .find(|f| f.selector == reset) - .unwrap() - .drift, - Drift::Unchanged - ); - send(&mut app, RowEvent::Reset(reset, true)); - apply(&mut app); - assert_eq!(app.document.revision(), revision + 1); - assert_eq!(app.project().elements, before.decoded.project.elements); - let after = &app.project().traces[&trace].baseline.as_ref().unwrap(); - assert_eq!(after.oa, previous.oa); - assert_eq!(after.sa, previous.sa); - assert_eq!( - after - .semantic - .as_ref() - .unwrap() - .facets - .iter() - .map(FacetReview::selector) - .collect::>(), - vec![kept] - ); - assert!( - app.document.bundle().files[&app.document.bundle().entry] - .contains("// Exact review rationale survives reset.") - ); - let accepted = app.document.snapshot(); - let _ = app.update(Message::Undo); - assert_eq!(app.document.bundle(), &before.bundle); - let _ = app.update(Message::Redo); - assert_eq!(app.document.bundle(), &accepted.bundle); - } -} - -#[test] -fn historical_one_sided_pair_migration_requires_exact_reset_and_explicit_overlap_resolution() { - for reverse in [false, true] { - let (mut p, oa, sa, trace, paired, _) = member_pair(ReferenceCollection::SignalItems); - let FacetSelector::Member { collection, keys } = &paired else { - unreachable!() - }; - let (source, target) = if reverse { (sa, oa) } else { (oa, sa) }; - let previous = FacetSelector::Member { - collection: *collection, - keys: KeyPair { - oa: if reverse { None } else { keys.oa }, - sa: if reverse { keys.sa } else { None }, - }, - }; - put(&mut p, source, &paired, true); - history(&mut p, trace, std::slice::from_ref(&previous)); - let mut app = App::with_project(p); - start(&mut app, source); - decide(&mut app, previous.clone(), SemanticDecision::KeepTarget); - stage(&mut app, &paired); - decide(&mut app, paired.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(paired.clone(), true)); - assert!(!state(&app).can_apply()); - assert_eq!(state(&app).decisions.len(), 2); - send(&mut app, RowEvent::Decide(previous.clone(), None)); - assert!( - !state(&app).can_apply(), - "Historic pair cannot silently migrate" - ); - send(&mut app, RowEvent::Reset(previous.clone(), true)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - send(&mut app, RowEvent::Reset(previous, false)); - assert!(!state(&app).can_apply()); - assert!(state(&app).presence[&paired]); - let old = state(&app).resets.clone(); - assert!(old.is_empty()); - let historic = app.project().traces[&trace] - .baseline - .as_ref() - .unwrap() - .semantic - .as_ref() - .unwrap() - .facets[0] - .selector(); - send(&mut app, RowEvent::Reset(historic, true)); - apply(&mut app); - assert!(contains(app.project(), target, &paired, oa, sa)); - assert_eq!( - app.project().traces[&trace] - .baseline - .as_ref() - .unwrap() - .semantic - .as_ref() - .unwrap() - .facets[0] - .selector(), - paired - ); - } -} - -#[test] -fn reset_precedes_equal_constructor_candidate_and_preserves_independent_literal_draft() { - for reverse in [false, true] { - let (mut p, oa, sa, trace) = enum_pair(); - let ctor = FacetSelector::Data(DataFacet::Constructor); - history(&mut p, trace, std::slice::from_ref(&ctor)); - let mut app = App::with_project(p); - start(&mut app, if reverse { sa } else { oa }); - let created = literal("separate"); - stage(&mut app, &created); - decide(&mut app, created.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(created.clone(), true)); - send(&mut app, RowEvent::Reset(ctor.clone(), true)); - decide(&mut app, ctor.clone(), SemanticDecision::Candidate); - assert!(state(&app).can_acknowledge_constructor(app.project(), &ctor)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - let reviewed = &state(&app).plan.as_ref().unwrap().reviews[0]; - assert_eq!( - reviewed - .acknowledged - .iter() - .cloned() - .collect::>(), - BTreeSet::from([ctor.clone(), created.clone()]) - ); - assert!(reviewed.retired.is_empty()); - send(&mut app, RowEvent::Reset(ctor.clone(), false)); - assert!(!state(&app).can_apply()); - assert!(state(&app).presence[&created]); - send(&mut app, RowEvent::Reset(ctor, true)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - } -} - -#[test] -fn invalid_literal_removal_and_unselected_scalar_buffers_are_retained_and_repairable() { - let (p, oa, _, _) = enum_pair(); - let mut app = App::with_project(p); - start(&mut app, oa); - let unit = FacetSelector::Data(DataFacet::Unit); - decide(&mut app, unit.clone(), SemanticDecision::Candidate); - send( - &mut app, - RowEvent::Input( - "/body/Sa/Shared/DataType/unit".into(), - " untouched unit ".into(), - ), - ); - decide(&mut app, unit.clone(), SemanticDecision::KeepTarget); - let retained = literal("retained"); - decide(&mut app, retained.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(retained.clone(), false)); - assert!( - !state(&app).can_apply(), - "Existing default must prevent removal" - ); - assert!(!state(&app).presence[&retained]); - send(&mut app, RowEvent::Presence(retained.clone(), true)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - assert_eq!( - state(&app).draft.as_ref().unwrap().inputs["/body/Sa/Shared/DataType/unit"].raw, - " untouched unit " - ); - decide(&mut app, unit, SemanticDecision::Candidate); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); -} - -#[test] -fn unsupported_independent_families_and_missing_identities_do_not_gain_edit_authority() { - let (p, oa, _, _) = enum_pair(); - let mut app = App::with_project(p); - start(&mut app, oa); - let unsupported = FacetSelector::Member { - collection: ReferenceCollection::Steps, - keys: KeyPair { - oa: Some(oa), - sa: None, - }, - }; - decide(&mut app, unsupported.clone(), SemanticDecision::Candidate); - assert!(!state(&app).decisions.contains_key(&unsupported)); - send(&mut app, RowEvent::MemberFamily(keyed::Family::Literal)); - send(&mut app, RowEvent::Literal("unpaired".into())); - send(&mut app, RowEvent::LiteralSide(Layer::Sa, false)); - send(&mut app, RowEvent::StageMember); - let facet = state(&app).selected.clone().unwrap(); - decide(&mut app, facet.clone(), SemanticDecision::Candidate); - assert!(!state(&app).can_apply()); - send(&mut app, RowEvent::LiteralSide(Layer::Oa, false)); - send(&mut app, RowEvent::StageMember); - assert!(state(&app).error.is_some()); - assert_eq!( - state(&app).decisions.get(&facet), - Some(&SemanticDecision::Candidate) - ); -} - -#[test] -fn deleted_member_source_observation_removes_only_mapped_membership_both_directions() { - for reverse in [false, true] { - let (mut p, oa, sa, trace, facet, locals) = member_pair(ReferenceCollection::SignalItems); - let FacetSelector::Member { keys, .. } = &facet else { - unreachable!() - }; - let (source, target, deleted, local) = if reverse { - (sa, oa, keys.sa.unwrap(), locals[0]) - } else { - (oa, sa, keys.oa.unwrap(), locals[1]) - }; - let target_only = FacetSelector::Member { - collection: ReferenceCollection::SignalItems, - keys: KeyPair { - oa: reverse.then_some(local), - sa: (!reverse).then_some(local), - }, - }; - put(&mut p, source, &facet, true); - put(&mut p, target, &facet, true); - put(&mut p, target, &target_only, true); - history(&mut p, trace, std::slice::from_ref(&facet)); - p = prepare_command(&p, &Command::Delete(deleted)) - .unwrap() - .project; - let mut app = App::with_project(p); - start(&mut app, source); - stage(&mut app, &facet); - decide(&mut app, facet.clone(), SemanticDecision::TakeSource); - // Force mixed final-record composition, as well as the core Take projection. - let name = FacetSelector::Header(HeaderFacet::Name); - decide(&mut app, name, SemanticDecision::Candidate); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - apply(&mut app); - assert!(!contains(app.project(), target, &facet, oa, sa)); - assert!(contains(app.project(), target, &target_only, oa, sa)); - let retained = if reverse { - keys.oa.unwrap() - } else { - keys.sa.unwrap() - }; - assert!(app.project().elements.contains_key(&retained)); - } -} - -#[test] -fn stale_or_source_failed_reset_and_membership_apply_retain_exact_choices() { - let (mut p, oa, _, trace) = enum_pair(); - let reset = FacetSelector::Header(HeaderFacet::Name); - history(&mut p, trace, std::slice::from_ref(&reset)); - let mut app = App::with_project(p.clone()); - start(&mut app, oa); - send(&mut app, RowEvent::Reset(reset.clone(), true)); - app.document - .execute(Command::Rename(oa, "Changed after preview".into())) - .unwrap(); - let before = app.document.snapshot(); - apply(&mut app); - assert!(state(&app).error.as_ref().unwrap().contains("stale")); - assert!(state(&app).resets.contains(&reset)); - assert_eq!(app.document.bundle(), &before.bundle); - let mut app = App::with_project(p); - let bundle = app.document.bundle().clone(); - let mut limits = source_project::ProjectLimits::default(); - limits.model.max_source_bytes = bundle.files.values().map(String::len).sum::() + 10; - app.document = Document::from_bundle(bundle, limits).unwrap(); - start(&mut app, oa); - send(&mut app, RowEvent::Reset(reset.clone(), true)); - let added = literal(&" exact long literal ".repeat(100)); - stage(&mut app, &added); - decide(&mut app, added.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(added.clone(), true)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - let before = app.document.snapshot(); - apply(&mut app); - assert!(state(&app).error.is_some()); - assert!(state(&app).resets.contains(&reset)); - assert_eq!( - state(&app).decisions.get(&added), - Some(&SemanticDecision::Candidate) - ); - assert!(state(&app).presence[&added]); - assert_eq!(app.document.bundle(), &before.bundle); - assert!(!app.document.can_undo()); -} - -#[test] -fn member_identity_labels_and_new_tabs_preserve_exact_names_and_deleted_keys() { - let (mut p, oa, _, trace, facet, _) = member_pair(ReferenceCollection::SignalItems); - let FacetSelector::Member { collection, keys } = &facet else { - unreachable!() - }; - p.elements.get_mut(&keys.oa.unwrap()).unwrap().name = "Absent intentionally in name".into(); - let name = view::title(&p, &facet); - assert!(name.contains("Absent intentionally in name")); - let deleted1 = Id::new_v4(); - let deleted2 = Id::new_v4(); - let old1 = FacetSelector::Member { - collection: *collection, - keys: KeyPair { - oa: Some(deleted1), - sa: None, - }, - }; - let old2 = FacetSelector::Member { - collection: *collection, - keys: KeyPair { - oa: Some(deleted2), - sa: None, - }, - }; - history(&mut p, trace, &[old1.clone(), old2.clone()]); - assert!(view::title(&p, &old1).contains(&deleted1.to_string())); - assert_ne!(view::title(&p, &old1), view::title(&p, &old2)); - assert_ne!( - view::title(&p, &literal("Absent")), - view::title( - &p, - &FacetSelector::Literal(KeyPair { - oa: None, - sa: Some("Absent".into()) - }) - ) - ); - let mut app = App::with_project(p); - start(&mut app, oa); - for tab in [Tab::History, Tab::Members, Tab::Facts] { - send(&mut app, RowEvent::Tab(tab)); - let _ = state(&app).view(app.project()); - } - send(&mut app, RowEvent::Reset(old1.clone(), true)); - assert!(state(&app).can_apply(), "{}", errors(state(&app))); - assert!(!state(&app).facts.iter().any(|f| f.selector == old1)); - assert!(state(&app).facts.iter().any(|f| f.selector == old2)); -} - -#[test] -fn staging_removal_and_obsolete_reset_recovery_keep_other_decisions_and_raw_presence() { - let (mut p, oa, _, trace) = enum_pair(); - let reset = FacetSelector::Header(HeaderFacet::Name); - history(&mut p, trace, std::slice::from_ref(&reset)); - let mut app = App::with_project(p.clone()); - start(&mut app, oa); - send(&mut app, RowEvent::Reset(reset.clone(), true)); - let added = literal("staged draft"); - stage(&mut app, &added); - decide(&mut app, added.clone(), SemanticDecision::Candidate); - send(&mut app, RowEvent::Presence(added.clone(), true)); - send(&mut app, RowEvent::DropStaged(added.clone())); - assert!(state(&app).decisions.is_empty()); - assert!(!state(&app).staged.contains(&added)); - assert!(state(&app).presence[&added]); - assert!(state(&app).can_apply()); - stage(&mut app, &added); - decide(&mut app, added.clone(), SemanticDecision::Candidate); - assert!(state(&app).presence[&added]); - p.traces - .get_mut(&trace) - .unwrap() - .baseline - .as_mut() - .unwrap() - .semantic - .as_mut() - .unwrap() - .facets - .clear(); - let crate::workflow::Workflow::Semantic(s) = app.workflow.as_mut().unwrap() else { - unreachable!() - }; - s.refresh(&p); - assert!(!s.can_apply()); - s.tab = Tab::History; - let _ = s.view(&p); - s.update(&p, RowEvent::Reset(reset.clone(), false)); - // The old single-row test implicitly adopted externally changed history on - // refresh. A batch has one frozen accepted base: withdrawing raw reset intent - // survives, while application requires an explicitly reopened review. - assert!(!s.resets.contains(&reset)); - assert!(!s.can_apply()); - assert!(s.error.as_ref().unwrap().contains("stale")); - assert!(s.plan.is_none() && s.proposed.is_none()); - assert_eq!(s.decisions.get(&added), Some(&SemanticDecision::Candidate)); - assert!(s.presence[&added]); -} +use super::{tests::*, *}; +use crate::{app::App, document::Document}; + +fn errors(s: &State) -> String { + s.error + .iter() + .chain(s.plan.iter().flat_map(|p| &p.conflicts)) + .cloned() + .collect::>() + .join("; ") +} +fn put(p: &mut Project, owner: Id, facet: &FacetSelector, present: bool) { + let layer = p.layer_of(owner).unwrap(); + let mut value = Draft::from_project(p, owner).unwrap().value; + keyed::project_presence(&mut value, facet, layer, present).unwrap(); + if let Some(element) = p.elements.get_mut(&owner) { + *element = serde_json::from_value(value).unwrap(); + } else { + p.relations + .insert(owner, serde_json::from_value(value).unwrap()); + } +} +fn contains(p: &Project, owner: Id, facet: &FacetSelector, oa: Id, sa: Id) -> bool { + scalar::value( + &capture_facet(p, oa, sa, facet).unwrap(), + p.layer_of(owner).unwrap(), + ) + .unwrap() + .as_bool() + .unwrap() +} +fn history(p: &mut Project, trace: Id, selectors: &[FacetSelector]) { + let t = &p.traces[&trace]; + let baseline = TraceBaseline { + oa: p.properties(t.oa).unwrap(), + sa: p.properties(t.sa).unwrap(), + semantic: Some(SemanticBaseline { + facets: selectors + .iter() + .map(|s| capture_facet(p, t.oa, t.sa, s).unwrap()) + .collect(), + }), + }; + p.traces.get_mut(&trace).unwrap().baseline = Some(baseline); +} +fn literal(name: &str) -> FacetSelector { + FacetSelector::Literal(KeyPair { + oa: Some(name.into()), + sa: Some(name.into()), + }) +} +fn enum_pair() -> (Project, Id, Id, Id) { + let mut p = Project::empty(); + let mut ids = vec![]; + for layer in [Layer::Oa, Layer::Sa] { + let id = p.add_element(Kind::DataType, layer, "Enum", None).unwrap(); + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::DataType { + definition: DataTypeDefinition::Enumeration { + literals: vec!["retained".into()], + }, + default: Some(DataValue::Enumeration("retained".into())), + unit: String::new(), + }; + ids.push(id); + } + let trace = add_trace(&mut p, ids[0], ids[1], "Literal review"); + (p, ids[0], ids[1], trace) +} +fn member_pair(collection: ReferenceCollection) -> (Project, Id, Id, Id, FacetSelector, [Id; 2]) { + let mut p = Project::empty(); + let mut owners = vec![]; + let mut keys = vec![]; + let mut locals = vec![]; + for layer in [Layer::Oa, Layer::Sa] { + let work = if layer == Layer::Oa { + Kind::Activity + } else { + Kind::Function + }; + let kind = match collection { + ReferenceCollection::TransitionTriggers => Kind::Event, + ReferenceCollection::TransitionEffects | ReferenceCollection::Constrained => work, + _ => Kind::ExchangeItem, + }; + let key = p.add_element(kind, layer, "Mapped member", None).unwrap(); + let local = p.add_element(kind, layer, "Local member", None).unwrap(); + let owner = match collection { + ReferenceCollection::SignalItems => { + let id = p.add_element(Kind::Event, layer, "Signal", None).unwrap(); + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Event { + body: EventBody::Signal { items: vec![] }, + }; + id + } + ReferenceCollection::Constrained => p + .add_element(Kind::Constraint, layer, "Constraint", None) + .unwrap(), + ReferenceCollection::ExchangeItems => { + let a = p.add_element(work, layer, "From", None).unwrap(); + let b = p.add_element(work, layer, "To", None).unwrap(); + let id = p.relate( + if layer == Layer::Oa { + RelationKind::Interaction + } else { + RelationKind::FunctionalExchange + }, + "Exchange", + a, + b, + ); + if let RelationBody::FunctionalExchange { + source_port, + target_port, + .. + } = p.relations[&id].body + { + for port in [source_port, target_port] { + if let Some(SharedBody::Port { contract, .. }) = + p.elements.get_mut(&port).unwrap().shared_mut() + { + match contract { + PortContract::FunctionInput { incoming_items } => { + *incoming_items = vec![key, local] + } + PortContract::FunctionOutput { outgoing_items } => { + *outgoing_items = vec![key, local] + } + _ => unreachable!(), + } + } + } + } + id + } + ReferenceCollection::TransitionTriggers | ReferenceCollection::TransitionEffects => { + let machine = p + .add_element(Kind::StateMachine, layer, "Machine", None) + .unwrap(); + let region = p + .add_element(Kind::Region, layer, "Region", Some(machine)) + .unwrap(); + let state = p + .add_element(Kind::State, layer, "State", Some(region)) + .unwrap(); + p.relate(RelationKind::StateTransition, "Transition", state, state) + } + ReferenceCollection::FunctionInputItems | ReferenceCollection::FunctionOutputItems => { + let parent = p.add_element(work, layer, "Owner", None).unwrap(); + let id = Id::new_v4(); + let contract = if layer == Layer::Oa { + PortContract::Legacy + } else if collection == ReferenceCollection::FunctionInputItems { + PortContract::FunctionInput { + incoming_items: vec![], + } + } else { + PortContract::FunctionOutput { + outgoing_items: vec![], + } + }; + let mut port = ports::make_port(&p, id, parent, "Port", contract).unwrap(); + if layer == Layer::Oa + && let Some(SharedBody::Port { legacy, .. }) = port.shared_mut() + { + *legacy = Some(LegacyPortContract { + direction: PortDirection::InOut, + interface: None, + }); + } + p.elements.insert(id, port); + id + } + _ => unreachable!(), + }; + owners.push(owner); + keys.push(key); + locals.push(local); + } + let trace = add_trace(&mut p, owners[0], owners[1], "Membership review"); + let facet = FacetSelector::Member { + collection, + keys: KeyPair { + oa: Some(keys[0]), + sa: Some(keys[1]), + }, + }; + ( + p, + owners[0], + owners[1], + trace, + facet, + [locals[0], locals[1]], + ) +} +fn stage(app: &mut App, facet: &FacetSelector) { + send(app, RowEvent::EditPair(facet.clone())); + send(app, RowEvent::StageMember); + assert_eq!( + state(app).selected.as_ref(), + Some(facet), + "{}", + errors(state(app)) + ); +} + +#[test] +fn unordered_members_keep_take_candidate_preserve_other_rows_both_directions() { + for collection in [ + ReferenceCollection::SignalItems, + ReferenceCollection::ExchangeItems, + ReferenceCollection::TransitionTriggers, + ReferenceCollection::TransitionEffects, + ReferenceCollection::Constrained, + ] { + for reverse in [false, true] { + for decision in [ + SemanticDecision::KeepTarget, + SemanticDecision::TakeSource, + SemanticDecision::Candidate, + ] { + let (mut p, oa, sa, trace, facet, locals) = member_pair(collection); + let (source, target, local) = if reverse { + (sa, oa, locals[0]) + } else { + (oa, sa, locals[1]) + }; + put(&mut p, source, &facet, true); + let local_facet = FacetSelector::Member { + collection, + keys: KeyPair { + oa: reverse.then_some(local), + sa: (!reverse).then_some(local), + }, + }; + put(&mut p, target, &local_facet, true); + let mut app = App::with_project(p); + let before = app.document.snapshot(); + start(&mut app, source); + stage(&mut app, &facet); + decide(&mut app, facet.clone(), decision); + if decision == SemanticDecision::Candidate { + send(&mut app, RowEvent::Presence(facet.clone(), true)); + } + // A target-only Take is an explicit acknowledgment, never implicit removal. + decide(&mut app, local_facet.clone(), SemanticDecision::TakeSource); + assert!( + state(&app).can_apply(), + "{collection:?} reverse={reverse} {decision:?}: {}", + errors(state(&app)) + ); + let proposed = state(&app).proposed.clone().unwrap(); + assert_eq!(app.document.bundle(), &before.bundle); + apply(&mut app); + assert!(app.workflow.is_none(), "{}", app.status); + assert_eq!(app.project(), &proposed); + assert_eq!( + contains(app.project(), target, &facet, oa, sa), + decision != SemanticDecision::KeepTarget + ); + assert!(contains(app.project(), target, &local_facet, oa, sa)); + assert!(app.project().elements.contains_key(&local)); + assert_eq!( + app.project().traces[&trace] + .baseline + .as_ref() + .unwrap() + .semantic + .as_ref() + .unwrap() + .facets + .len(), + 2 + ); + let _ = app.update(Message::Undo); + assert_eq!(app.document.bundle(), &before.bundle); + } + } + } +} + +#[test] +fn function_port_members_use_existing_typed_choices_and_preserve_oa_legacy() { + for collection in [ + ReferenceCollection::FunctionInputItems, + ReferenceCollection::FunctionOutputItems, + ] { + let (p, oa, sa, _, facet, _) = member_pair(collection); + let FacetSelector::Member { keys, .. } = &facet else { + unreachable!() + }; + let mut app = App::with_project(p); + start(&mut app, oa); + assert_eq!( + state(&app).member_families(app.project()), + vec![keyed::Family::Member(collection)] + ); + send(&mut app, RowEvent::EditPair(facet.clone())); + assert!( + state(&app) + .member_choices(app.project(), Layer::Sa) + .iter() + .any(|c| Some(c.id) == keys.sa) + ); + send(&mut app, RowEvent::StageMember); + decide(&mut app, facet.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(facet.clone(), true)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + apply(&mut app); + assert!(contains(app.project(), sa, &facet, oa, sa)); + assert!(matches!( + app.project().elements[&oa].shared(), + Some(SharedBody::Port { + contract: PortContract::Legacy, + .. + }) + )); + start(&mut app, sa); + stage(&mut app, &facet); + decide(&mut app, facet.clone(), SemanticDecision::KeepTarget); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + decide(&mut app, facet.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(facet, true)); + assert!( + !state(&app).can_apply(), + "OA typed membership must remain inapplicable" + ); + } +} + +#[test] +fn exact_literal_new_rows_and_removals_commit_both_directions_without_default_loss() { + for reverse in [false, true] { + let (mut p, oa, sa, _) = enum_pair(); + let (source, target) = if reverse { (sa, oa) } else { (oa, sa) }; + let incoming = literal(" New value "); + put(&mut p, source, &incoming, true); + let removed = literal("old"); + put(&mut p, target, &removed, true); + let created = literal("new value"); + let mut app = App::with_project(p); + start(&mut app, source); + for (facet, decision) in [ + (&incoming, SemanticDecision::TakeSource), + (&removed, SemanticDecision::Candidate), + (&created, SemanticDecision::Candidate), + ] { + stage(&mut app, facet); + decide(&mut app, facet.clone(), decision); + if decision == SemanticDecision::Candidate { + send( + &mut app, + RowEvent::Presence(facet.clone(), facet == &created), + ); + } + } + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + apply(&mut app); + assert!(contains(app.project(), target, &incoming, oa, sa)); + assert!(contains(app.project(), target, &created, oa, sa)); + assert!(!contains(app.project(), target, &removed, oa, sa)); + assert!( + matches!(app.project().elements[&target].shared(),Some(SharedBody::DataType { default: Some(DataValue::Enumeration(value)), .. }) if value=="retained") + ); + let reopened = Document::from_bundle( + app.document.bundle().clone(), + source_project::ProjectLimits::default(), + ) + .unwrap(); + assert_eq!(reopened.project(), app.project()); + } +} + +#[test] +fn exact_reset_only_is_one_history_source_transaction_and_undo_redo_both_directions() { + for reverse in [false, true] { + let (mut p, oa, sa, trace) = enum_pair(); + let reset = literal("retained"); + let kept = FacetSelector::Header(HeaderFacet::Name); + history(&mut p, trace, &[reset.clone(), kept.clone()]); + let previous = p.traces[&trace].baseline.clone().unwrap(); + let mut app = App::with_project(p); + let mut bundle = app.document.bundle().clone(); + bundle + .files + .get_mut(&bundle.entry) + .unwrap() + .insert_str(0, "// Exact review rationale survives reset.\n"); + app.document.apply_bundle(bundle).unwrap(); + let before = app.document.snapshot(); + let revision = app.document.revision(); + start(&mut app, if reverse { sa } else { oa }); + send(&mut app, RowEvent::Reset(reset.clone(), true)); + assert!(state(&app).decisions.is_empty()); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + assert_eq!( + state(&app) + .facts + .iter() + .find(|f| f.selector == reset) + .unwrap() + .drift, + Drift::Unreviewed + ); + send(&mut app, RowEvent::Reset(reset.clone(), false)); + assert!(!state(&app).can_apply()); + assert_eq!( + state(&app) + .facts + .iter() + .find(|f| f.selector == reset) + .unwrap() + .drift, + Drift::Unchanged + ); + send(&mut app, RowEvent::Reset(reset, true)); + apply(&mut app); + assert_eq!(app.document.revision(), revision + 1); + assert_eq!(app.project().elements, before.decoded.project.elements); + let after = &app.project().traces[&trace].baseline.as_ref().unwrap(); + assert_eq!(after.oa, previous.oa); + assert_eq!(after.sa, previous.sa); + assert_eq!( + after + .semantic + .as_ref() + .unwrap() + .facets + .iter() + .map(FacetReview::selector) + .collect::>(), + vec![kept] + ); + assert!( + app.document.bundle().files[&app.document.bundle().entry] + .contains("// Exact review rationale survives reset.") + ); + let accepted = app.document.snapshot(); + let _ = app.update(Message::Undo); + assert_eq!(app.document.bundle(), &before.bundle); + let _ = app.update(Message::Redo); + assert_eq!(app.document.bundle(), &accepted.bundle); + } +} + +#[test] +fn historical_one_sided_pair_migration_requires_exact_reset_and_explicit_overlap_resolution() { + for reverse in [false, true] { + let (mut p, oa, sa, trace, paired, _) = member_pair(ReferenceCollection::SignalItems); + let FacetSelector::Member { collection, keys } = &paired else { + unreachable!() + }; + let (source, target) = if reverse { (sa, oa) } else { (oa, sa) }; + let previous = FacetSelector::Member { + collection: *collection, + keys: KeyPair { + oa: if reverse { None } else { keys.oa }, + sa: if reverse { keys.sa } else { None }, + }, + }; + put(&mut p, source, &paired, true); + history(&mut p, trace, std::slice::from_ref(&previous)); + let mut app = App::with_project(p); + start(&mut app, source); + decide(&mut app, previous.clone(), SemanticDecision::KeepTarget); + stage(&mut app, &paired); + decide(&mut app, paired.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(paired.clone(), true)); + assert!(!state(&app).can_apply()); + assert_eq!(state(&app).decisions.len(), 2); + send(&mut app, RowEvent::Decide(previous.clone(), None)); + assert!( + !state(&app).can_apply(), + "Historic pair cannot silently migrate" + ); + send(&mut app, RowEvent::Reset(previous.clone(), true)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + send(&mut app, RowEvent::Reset(previous, false)); + assert!(!state(&app).can_apply()); + assert!(state(&app).presence[&paired]); + let old = state(&app).resets.clone(); + assert!(old.is_empty()); + let historic = app.project().traces[&trace] + .baseline + .as_ref() + .unwrap() + .semantic + .as_ref() + .unwrap() + .facets[0] + .selector(); + send(&mut app, RowEvent::Reset(historic, true)); + apply(&mut app); + assert!(contains(app.project(), target, &paired, oa, sa)); + assert_eq!( + app.project().traces[&trace] + .baseline + .as_ref() + .unwrap() + .semantic + .as_ref() + .unwrap() + .facets[0] + .selector(), + paired + ); + } +} + +#[test] +fn reset_precedes_equal_constructor_candidate_and_preserves_independent_literal_draft() { + for reverse in [false, true] { + let (mut p, oa, sa, trace) = enum_pair(); + let ctor = FacetSelector::Data(DataFacet::Constructor); + history(&mut p, trace, std::slice::from_ref(&ctor)); + let mut app = App::with_project(p); + start(&mut app, if reverse { sa } else { oa }); + let created = literal("separate"); + stage(&mut app, &created); + decide(&mut app, created.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(created.clone(), true)); + send(&mut app, RowEvent::Reset(ctor.clone(), true)); + decide(&mut app, ctor.clone(), SemanticDecision::Candidate); + assert!(state(&app).can_acknowledge_constructor(app.project(), &ctor)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + let reviewed = &state(&app).plan.as_ref().unwrap().reviews[0]; + assert_eq!( + reviewed + .acknowledged + .iter() + .cloned() + .collect::>(), + BTreeSet::from([ctor.clone(), created.clone()]) + ); + assert!(reviewed.retired.is_empty()); + send(&mut app, RowEvent::Reset(ctor.clone(), false)); + assert!(!state(&app).can_apply()); + assert!(state(&app).presence[&created]); + send(&mut app, RowEvent::Reset(ctor, true)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + } +} + +#[test] +fn invalid_literal_removal_and_unselected_scalar_buffers_are_retained_and_repairable() { + let (p, oa, _, _) = enum_pair(); + let mut app = App::with_project(p); + start(&mut app, oa); + let unit = FacetSelector::Data(DataFacet::Unit); + decide(&mut app, unit.clone(), SemanticDecision::Candidate); + send( + &mut app, + RowEvent::Input( + "/body/Sa/Shared/DataType/unit".into(), + " untouched unit ".into(), + ), + ); + decide(&mut app, unit.clone(), SemanticDecision::KeepTarget); + let retained = literal("retained"); + decide(&mut app, retained.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(retained.clone(), false)); + assert!( + !state(&app).can_apply(), + "Existing default must prevent removal" + ); + assert!(!state(&app).presence[&retained]); + send(&mut app, RowEvent::Presence(retained.clone(), true)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + assert_eq!( + state(&app).draft.as_ref().unwrap().inputs["/body/Sa/Shared/DataType/unit"].raw, + " untouched unit " + ); + decide(&mut app, unit, SemanticDecision::Candidate); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); +} + +#[test] +fn unsupported_independent_families_and_missing_identities_do_not_gain_edit_authority() { + let (p, oa, _, _) = enum_pair(); + let mut app = App::with_project(p); + start(&mut app, oa); + let unsupported = FacetSelector::Member { + collection: ReferenceCollection::Steps, + keys: KeyPair { + oa: Some(oa), + sa: None, + }, + }; + decide(&mut app, unsupported.clone(), SemanticDecision::Candidate); + assert!(!state(&app).decisions.contains_key(&unsupported)); + send(&mut app, RowEvent::MemberFamily(keyed::Family::Literal)); + send(&mut app, RowEvent::Literal("unpaired".into())); + send(&mut app, RowEvent::LiteralSide(Layer::Sa, false)); + send(&mut app, RowEvent::StageMember); + let facet = state(&app).selected.clone().unwrap(); + decide(&mut app, facet.clone(), SemanticDecision::Candidate); + assert!(!state(&app).can_apply()); + send(&mut app, RowEvent::LiteralSide(Layer::Oa, false)); + send(&mut app, RowEvent::StageMember); + assert!(state(&app).error.is_some()); + assert_eq!( + state(&app).decisions.get(&facet), + Some(&SemanticDecision::Candidate) + ); +} + +#[test] +fn deleted_member_source_observation_removes_only_mapped_membership_both_directions() { + for reverse in [false, true] { + let (mut p, oa, sa, trace, facet, locals) = member_pair(ReferenceCollection::SignalItems); + let FacetSelector::Member { keys, .. } = &facet else { + unreachable!() + }; + let (source, target, deleted, local) = if reverse { + (sa, oa, keys.sa.unwrap(), locals[0]) + } else { + (oa, sa, keys.oa.unwrap(), locals[1]) + }; + let target_only = FacetSelector::Member { + collection: ReferenceCollection::SignalItems, + keys: KeyPair { + oa: reverse.then_some(local), + sa: (!reverse).then_some(local), + }, + }; + put(&mut p, source, &facet, true); + put(&mut p, target, &facet, true); + put(&mut p, target, &target_only, true); + history(&mut p, trace, std::slice::from_ref(&facet)); + p = prepare_command(&p, &Command::Delete(deleted)) + .unwrap() + .project; + let mut app = App::with_project(p); + start(&mut app, source); + stage(&mut app, &facet); + decide(&mut app, facet.clone(), SemanticDecision::TakeSource); + // Force mixed final-record composition, as well as the core Take projection. + let name = FacetSelector::Header(HeaderFacet::Name); + decide(&mut app, name, SemanticDecision::Candidate); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + apply(&mut app); + assert!(!contains(app.project(), target, &facet, oa, sa)); + assert!(contains(app.project(), target, &target_only, oa, sa)); + let retained = if reverse { + keys.oa.unwrap() + } else { + keys.sa.unwrap() + }; + assert!(app.project().elements.contains_key(&retained)); + } +} + +#[test] +fn stale_or_source_failed_reset_and_membership_apply_retain_exact_choices() { + let (mut p, oa, _, trace) = enum_pair(); + let reset = FacetSelector::Header(HeaderFacet::Name); + history(&mut p, trace, std::slice::from_ref(&reset)); + let mut app = App::with_project(p.clone()); + start(&mut app, oa); + send(&mut app, RowEvent::Reset(reset.clone(), true)); + app.document + .execute(Command::Rename(oa, "Changed after preview".into())) + .unwrap(); + let before = app.document.snapshot(); + apply(&mut app); + assert!(state(&app).error.as_ref().unwrap().contains("stale")); + assert!(state(&app).resets.contains(&reset)); + assert_eq!(app.document.bundle(), &before.bundle); + let mut app = App::with_project(p); + let bundle = app.document.bundle().clone(); + let mut limits = source_project::ProjectLimits::default(); + limits.model.max_source_bytes = bundle.files.values().map(String::len).sum::() + 10; + app.document = Document::from_bundle(bundle, limits).unwrap(); + start(&mut app, oa); + send(&mut app, RowEvent::Reset(reset.clone(), true)); + let added = literal(&" exact long literal ".repeat(100)); + stage(&mut app, &added); + decide(&mut app, added.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(added.clone(), true)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + let before = app.document.snapshot(); + apply(&mut app); + assert!(state(&app).error.is_some()); + assert!(state(&app).resets.contains(&reset)); + assert_eq!( + state(&app).decisions.get(&added), + Some(&SemanticDecision::Candidate) + ); + assert!(state(&app).presence[&added]); + assert_eq!(app.document.bundle(), &before.bundle); + assert!(!app.document.can_undo()); +} + +#[test] +fn member_identity_labels_and_new_tabs_preserve_exact_names_and_deleted_keys() { + let (mut p, oa, _, trace, facet, _) = member_pair(ReferenceCollection::SignalItems); + let FacetSelector::Member { collection, keys } = &facet else { + unreachable!() + }; + p.elements.get_mut(&keys.oa.unwrap()).unwrap().name = "Absent intentionally in name".into(); + let name = view::title(&p, &facet); + assert!(name.contains("Absent intentionally in name")); + let deleted1 = Id::new_v4(); + let deleted2 = Id::new_v4(); + let old1 = FacetSelector::Member { + collection: *collection, + keys: KeyPair { + oa: Some(deleted1), + sa: None, + }, + }; + let old2 = FacetSelector::Member { + collection: *collection, + keys: KeyPair { + oa: Some(deleted2), + sa: None, + }, + }; + history(&mut p, trace, &[old1.clone(), old2.clone()]); + assert!(view::title(&p, &old1).contains(&deleted1.to_string())); + assert_ne!(view::title(&p, &old1), view::title(&p, &old2)); + assert_ne!( + view::title(&p, &literal("Absent")), + view::title( + &p, + &FacetSelector::Literal(KeyPair { + oa: None, + sa: Some("Absent".into()) + }) + ) + ); + let mut app = App::with_project(p); + start(&mut app, oa); + for tab in [Tab::History, Tab::Members, Tab::Facts] { + send(&mut app, RowEvent::Tab(tab)); + let _ = state(&app).view(app.project()); + } + send(&mut app, RowEvent::Reset(old1.clone(), true)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + assert!(!state(&app).facts.iter().any(|f| f.selector == old1)); + assert!(state(&app).facts.iter().any(|f| f.selector == old2)); +} + +#[test] +fn staging_removal_and_obsolete_reset_recovery_keep_other_decisions_and_raw_presence() { + let (mut p, oa, _, trace) = enum_pair(); + let reset = FacetSelector::Header(HeaderFacet::Name); + history(&mut p, trace, std::slice::from_ref(&reset)); + let mut app = App::with_project(p.clone()); + start(&mut app, oa); + send(&mut app, RowEvent::Reset(reset.clone(), true)); + let added = literal("staged draft"); + stage(&mut app, &added); + decide(&mut app, added.clone(), SemanticDecision::Candidate); + send(&mut app, RowEvent::Presence(added.clone(), true)); + send(&mut app, RowEvent::DropStaged(added.clone())); + assert!(state(&app).decisions.is_empty()); + assert!(!state(&app).staged.contains(&added)); + assert!(state(&app).presence[&added]); + assert!(state(&app).can_apply()); + stage(&mut app, &added); + decide(&mut app, added.clone(), SemanticDecision::Candidate); + assert!(state(&app).presence[&added]); + p.traces + .get_mut(&trace) + .unwrap() + .baseline + .as_mut() + .unwrap() + .semantic + .as_mut() + .unwrap() + .facets + .clear(); + let crate::workflow::Workflow::Semantic(s) = app.workflow.as_mut().unwrap() else { + unreachable!() + }; + s.refresh(&p); + assert!(!s.can_apply()); + s.tab = Tab::History; + let _ = s.view(&p); + s.update(&p, RowEvent::Reset(reset.clone(), false)); + // The old single-row test implicitly adopted externally changed history on + // refresh. A batch has one frozen accepted base: withdrawing raw reset intent + // survives, while application requires an explicitly reopened review. + assert!(!s.resets.contains(&reset)); + assert!(!s.can_apply()); + assert!(s.error.as_ref().unwrap().contains("stale")); + assert!(s.plan.is_none() && s.proposed.is_none()); + assert_eq!(s.decisions.get(&added), Some(&SemanticDecision::Candidate)); + assert!(s.presence[&added]); +} diff --git a/crates/oa-desktop/src/semantic_review/ordered.rs b/crates/oa-desktop/src/semantic_review/ordered.rs index 2674c335..050af2aa 100644 --- a/crates/oa-desktop/src/semantic_review/ordered.rs +++ b/crates/oa-desktop/src/semantic_review/ordered.rs @@ -1,254 +1,254 @@ -//! Independent membership and complete final order; the core validates every proposal. -use super::*; - -pub(super) fn collection(which: OrderedCollection) -> ReferenceCollection { - match which { - OrderedCollection::Steps => ReferenceCollection::Steps, - OrderedCollection::FragmentBranches => ReferenceCollection::FragmentBranches, - } -} -pub(super) fn ordered(which: ReferenceCollection) -> Option { - match which { - ReferenceCollection::Steps => Some(OrderedCollection::Steps), - ReferenceCollection::FragmentBranches => Some(OrderedCollection::FragmentBranches), - _ => None, - } -} -pub(super) fn read(value: &Value, which: OrderedCollection) -> Result, String> { - let path = keyed::path(value, keyed::Family::Member(collection(which))) - .ok_or("This ordered collection is unavailable on the destination.")?; - serde_json::from_value(value.pointer(&path).unwrap().clone()).map_err(|e| e.to_string()) -} -fn write(value: &mut Value, which: OrderedCollection, ids: Vec) -> Result<(), String> { - let path = keyed::path(value, keyed::Family::Member(collection(which))) - .ok_or("This ordered collection is unavailable on the destination.")?; - *value.pointer_mut(&path).unwrap() = serde_json::to_value(ids).expect("Ordered identities"); - Ok(()) -} - -impl ReviewRow { - pub(super) fn ordered_members( - &self, - project: &Project, - which: OrderedCollection, - ) -> Result, String> { - read(&self.combine(project, true, true)?, which) - } - - pub(super) fn initialize_order( - &mut self, - project: &Project, - which: OrderedCollection, - ) -> Result<(), String> { - if !self.order_drafts.contains_key(&which) { - let ids = self.ordered_members(project, which)?; - self.order_drafts.insert(which, ids); - } - Ok(()) - } - - pub(super) fn reconcile_order( - &mut self, - project: &Project, - which: OrderedCollection, - ) -> Result<(), String> { - self.require_order_candidate(which)?; - let members = self.ordered_members(project, which)?; - let draft = self - .order_drafts - .get_mut(&which) - .ok_or("Choose a final order first.")?; - // This is an explicit user action. Ordinary refresh never discards an authored order. - let mut seen = BTreeSet::new(); - draft.retain(|id| members.contains(id) && seen.insert(*id)); - for id in members { - if seen.insert(id) { - draft.push(id); - } - } - Ok(()) - } - - fn require_order_candidate(&self, which: OrderedCollection) -> Result<(), String> { - let facet = FacetSelector::Order(which); - if self.selected.as_ref() != Some(&facet) - || self.decisions.get(&facet) != Some(&SemanticDecision::Candidate) - { - return Err("Select Set final value for this order before changing it.".into()); - } - Ok(()) - } - - pub(super) fn move_order( - &mut self, - which: OrderedCollection, - index: usize, - delta: i32, - ) -> Result<(), String> { - self.require_order_candidate(which)?; - let ids = self - .order_drafts - .get_mut(&which) - .ok_or("Choose a final order first.")?; - let destination = match delta { - -1 => index.checked_sub(1), - 1 => index.checked_add(1), - _ => None, - }; - let destination = destination - .filter(|i| index < ids.len() && *i < ids.len()) - .ok_or("Choose an existing adjacent position in the final order.")?; - ids.swap(index, destination); - Ok(()) - } - - pub(super) fn project_order( - &self, - project: &Project, - trace: &Trace, - value: &mut Value, - which: OrderedCollection, - decision: SemanticDecision, - ) -> Result<(), String> { - let current = read(value, which)?; - let supplied = match decision { - SemanticDecision::KeepTarget => return Ok(()), - SemanticDecision::Candidate => self - .order_drafts - .get(&which) - .cloned() - .ok_or("Choose a final order.")?, - SemanticDecision::TakeSource => { - let fact = - capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?; - let source: Vec = serde_json::from_value( - scalar::value(&fact, self.direction.source_layer()) - .ok_or("The source has no applicable order.")?, - ) - .map_err(|e| e.to_string())?; - let map = self.order_reference_map()?; - source - .into_iter() - .map(|id| { - map.get(&id).copied().ok_or_else(|| { - format!( - "Pair source order member {} explicitly in Reference pairs.", - project.named(id) - ) - }) - }) - .collect::, _>>()? - } - }; - let actual: BTreeSet<_> = current.iter().copied().collect(); - let desired: BTreeSet<_> = supplied.iter().copied().collect(); - if desired.len() != supplied.len() { - return Err( - "The final order contains a duplicate identity; reconcile its members explicitly." - .into(), - ); - } - if desired != actual { - if decision == SemanticDecision::TakeSource - && desired.is_subset(&actual) - && current - .iter() - .filter(|id| desired.contains(id)) - .copied() - .collect::>() - == supplied - { - return Ok(()); - } - return Err("Final order must contain every surviving destination member exactly once. Reconcile members, then choose their positions; membership is reviewed separately.".into()); - } - write(value, which, supplied) - } - - fn order_reference_map(&self) -> Result, String> { - let mut map = BTreeMap::new(); - for (oa, sa) in &self.references { - let (a, b) = if self.direction == TransitionDirection::OaToSa { - (*oa, *sa) - } else { - (*sa, *oa) - }; - if map.insert(a, b).is_some_and(|old| old != b) { - return Err("Reverse reference correspondence is ambiguous.".into()); - } - } - Ok(map) - } - - pub(super) fn project_ordered_member( - &self, - project: &Project, - trace: &Trace, - value: &mut Value, - facet: &FacetSelector, - decision: SemanticDecision, - form_context: bool, - ) -> Result<(), String> { - let FacetSelector::Member { - collection: member_collection, - keys, - } = facet - else { - unreachable!() - }; - let which = ordered(*member_collection).expect("Ordered member"); - let before = read(value, which)?; - self.keyed_value(project, trace, value, facet, decision)?; - let after = read(value, which)?; - let explicit = self - .decisions - .get(&FacetSelector::Order(which)) - .is_some_and(|d| *d != SemanticDecision::KeepTarget); - if decision != SemanticDecision::TakeSource || explicit || after.len() <= before.len() { - return Ok(()); - } - let (source_key, target_key) = if self.direction == TransitionDirection::OaToSa { - (keys.oa, keys.sa) - } else { - (keys.sa, keys.oa) - }; - let (Some(source_key), Some(target_key)) = (source_key, target_key) else { - return Ok(()); - }; - let fact = capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?; - let source: Vec = serde_json::from_value( - scalar::value(&fact, self.direction.source_layer()) - .ok_or("Source order is unavailable.")?, - ) - .map_err(|e| e.to_string())?; - let position = source - .iter() - .position(|id| *id == source_key) - .ok_or("Source member is absent.")?; - let map = self.order_reference_map()?; - let anchor = |id: &Id| { - map.get(id) - .and_then(|mapped| before.iter().position(|id| id == mapped)) - }; - let left = source[..position].iter().rev().find_map(anchor); - let right = source[position + 1..].iter().find_map(anchor); - let at = match (left, right) { - (Some(a), Some(b)) if a + 1 == b => Some(b), - (Some(a), None) if a + 1 == before.len() => Some(a + 1), - (None, Some(0)) => Some(0), - (None, None) if before.is_empty() => Some(0), - _ => None, - }; - if let Some(at) = at { - let mut ordered = before; - ordered.insert(at, target_key); - write(value, which, ordered) - } else if form_context { - // An uncommitted membership list lets the user author a final order. - // The actual request still rejects ambiguous placement until explicitly decided. - Ok(()) - } else { - Err("Ordered member insertion has no unique unchanged anchor; select an explicit final Order in Properties.".into()) - } - } -} +//! Independent membership and complete final order; the core validates every proposal. +use super::*; + +pub(super) fn collection(which: OrderedCollection) -> ReferenceCollection { + match which { + OrderedCollection::Steps => ReferenceCollection::Steps, + OrderedCollection::FragmentBranches => ReferenceCollection::FragmentBranches, + } +} +pub(super) fn ordered(which: ReferenceCollection) -> Option { + match which { + ReferenceCollection::Steps => Some(OrderedCollection::Steps), + ReferenceCollection::FragmentBranches => Some(OrderedCollection::FragmentBranches), + _ => None, + } +} +pub(super) fn read(value: &Value, which: OrderedCollection) -> Result, String> { + let path = keyed::path(value, keyed::Family::Member(collection(which))) + .ok_or("This ordered collection is unavailable on the destination.")?; + serde_json::from_value(value.pointer(&path).unwrap().clone()).map_err(|e| e.to_string()) +} +fn write(value: &mut Value, which: OrderedCollection, ids: Vec) -> Result<(), String> { + let path = keyed::path(value, keyed::Family::Member(collection(which))) + .ok_or("This ordered collection is unavailable on the destination.")?; + *value.pointer_mut(&path).unwrap() = serde_json::to_value(ids).expect("Ordered identities"); + Ok(()) +} + +impl ReviewRow { + pub(super) fn ordered_members( + &self, + project: &Project, + which: OrderedCollection, + ) -> Result, String> { + read(&self.combine(project, true, true)?, which) + } + + pub(super) fn initialize_order( + &mut self, + project: &Project, + which: OrderedCollection, + ) -> Result<(), String> { + if !self.order_drafts.contains_key(&which) { + let ids = self.ordered_members(project, which)?; + self.order_drafts.insert(which, ids); + } + Ok(()) + } + + pub(super) fn reconcile_order( + &mut self, + project: &Project, + which: OrderedCollection, + ) -> Result<(), String> { + self.require_order_candidate(which)?; + let members = self.ordered_members(project, which)?; + let draft = self + .order_drafts + .get_mut(&which) + .ok_or("Choose a final order first.")?; + // This is an explicit user action. Ordinary refresh never discards an authored order. + let mut seen = BTreeSet::new(); + draft.retain(|id| members.contains(id) && seen.insert(*id)); + for id in members { + if seen.insert(id) { + draft.push(id); + } + } + Ok(()) + } + + fn require_order_candidate(&self, which: OrderedCollection) -> Result<(), String> { + let facet = FacetSelector::Order(which); + if self.selected.as_ref() != Some(&facet) + || self.decisions.get(&facet) != Some(&SemanticDecision::Candidate) + { + return Err("Select Set final value for this order before changing it.".into()); + } + Ok(()) + } + + pub(super) fn move_order( + &mut self, + which: OrderedCollection, + index: usize, + delta: i32, + ) -> Result<(), String> { + self.require_order_candidate(which)?; + let ids = self + .order_drafts + .get_mut(&which) + .ok_or("Choose a final order first.")?; + let destination = match delta { + -1 => index.checked_sub(1), + 1 => index.checked_add(1), + _ => None, + }; + let destination = destination + .filter(|i| index < ids.len() && *i < ids.len()) + .ok_or("Choose an existing adjacent position in the final order.")?; + ids.swap(index, destination); + Ok(()) + } + + pub(super) fn project_order( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + which: OrderedCollection, + decision: SemanticDecision, + ) -> Result<(), String> { + let current = read(value, which)?; + let supplied = match decision { + SemanticDecision::KeepTarget => return Ok(()), + SemanticDecision::Candidate => self + .order_drafts + .get(&which) + .cloned() + .ok_or("Choose a final order.")?, + SemanticDecision::TakeSource => { + let fact = + capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?; + let source: Vec = serde_json::from_value( + scalar::value(&fact, self.direction.source_layer()) + .ok_or("The source has no applicable order.")?, + ) + .map_err(|e| e.to_string())?; + let map = self.order_reference_map()?; + source + .into_iter() + .map(|id| { + map.get(&id).copied().ok_or_else(|| { + format!( + "Pair source order member {} explicitly in Reference pairs.", + project.named(id) + ) + }) + }) + .collect::, _>>()? + } + }; + let actual: BTreeSet<_> = current.iter().copied().collect(); + let desired: BTreeSet<_> = supplied.iter().copied().collect(); + if desired.len() != supplied.len() { + return Err( + "The final order contains a duplicate identity; reconcile its members explicitly." + .into(), + ); + } + if desired != actual { + if decision == SemanticDecision::TakeSource + && desired.is_subset(&actual) + && current + .iter() + .filter(|id| desired.contains(id)) + .copied() + .collect::>() + == supplied + { + return Ok(()); + } + return Err("Final order must contain every surviving destination member exactly once. Reconcile members, then choose their positions; membership is reviewed separately.".into()); + } + write(value, which, supplied) + } + + fn order_reference_map(&self) -> Result, String> { + let mut map = BTreeMap::new(); + for (oa, sa) in &self.references { + let (a, b) = if self.direction == TransitionDirection::OaToSa { + (*oa, *sa) + } else { + (*sa, *oa) + }; + if map.insert(a, b).is_some_and(|old| old != b) { + return Err("Reverse reference correspondence is ambiguous.".into()); + } + } + Ok(map) + } + + pub(super) fn project_ordered_member( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + facet: &FacetSelector, + decision: SemanticDecision, + form_context: bool, + ) -> Result<(), String> { + let FacetSelector::Member { + collection: member_collection, + keys, + } = facet + else { + unreachable!() + }; + let which = ordered(*member_collection).expect("Ordered member"); + let before = read(value, which)?; + self.keyed_value(project, trace, value, facet, decision)?; + let after = read(value, which)?; + let explicit = self + .decisions + .get(&FacetSelector::Order(which)) + .is_some_and(|d| *d != SemanticDecision::KeepTarget); + if decision != SemanticDecision::TakeSource || explicit || after.len() <= before.len() { + return Ok(()); + } + let (source_key, target_key) = if self.direction == TransitionDirection::OaToSa { + (keys.oa, keys.sa) + } else { + (keys.sa, keys.oa) + }; + let (Some(source_key), Some(target_key)) = (source_key, target_key) else { + return Ok(()); + }; + let fact = capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?; + let source: Vec = serde_json::from_value( + scalar::value(&fact, self.direction.source_layer()) + .ok_or("Source order is unavailable.")?, + ) + .map_err(|e| e.to_string())?; + let position = source + .iter() + .position(|id| *id == source_key) + .ok_or("Source member is absent.")?; + let map = self.order_reference_map()?; + let anchor = |id: &Id| { + map.get(id) + .and_then(|mapped| before.iter().position(|id| id == mapped)) + }; + let left = source[..position].iter().rev().find_map(anchor); + let right = source[position + 1..].iter().find_map(anchor); + let at = match (left, right) { + (Some(a), Some(b)) if a + 1 == b => Some(b), + (Some(a), None) if a + 1 == before.len() => Some(a + 1), + (None, Some(0)) => Some(0), + (None, None) if before.is_empty() => Some(0), + _ => None, + }; + if let Some(at) = at { + let mut ordered = before; + ordered.insert(at, target_key); + write(value, which, ordered) + } else if form_context { + // An uncommitted membership list lets the user author a final order. + // The actual request still rejects ambiguous placement until explicitly decided. + Ok(()) + } else { + Err("Ordered member insertion has no unique unchanged anchor; select an explicit final Order in Properties.".into()) + } + } +} diff --git a/docs/openapi.json b/docs/openapi.json index 0fd0be3a..715de45a 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -1,12837 +1,12837 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "Arcadia Studio model API", - "version": "1.0.0", - "description": "Current schema-7 snapshots and conditional atomic writes. Request body limit: 8 MiB; at most 200 nested commands; no retained revisions, request receipts, automatic retries or persistent transition plans. See docs/api.rst. Fixed store capacity: 128 models / 256 MiB serialized envelopes. Stateless transition preview/apply. Artifact URI/revision strings remain opaque core fields. Model admission before structural validation: 5,000 graph objects, 20,000 total records and 50,000 ordered references. Preparation uses snapshots outside the catalog lock; final commits recheck the exact ETag. Each accepted envelope couples model and authored source under one revision. Source-only edits advance the shared ETag. Source bundles resolve logical paths only, preserve authored files/comments, and have a stabilized JSON cap of 8 MiB, 256 files, 32 include edges along a path and 1,024 include edges overall." - }, - "servers": [ - { - "url": "http://127.0.0.1:8787" - } - ], - "security": [ - { - "bearerAuth": [] - } - ], - "paths": { - "/health": { - "get": { - "operationId": "_health", - "summary": "Store was opened and HTTP service is responding", - "parameters": [], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "const": "ok" - } - }, - "required": [ - "status" - ], - "additionalProperties": false - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "security": [] - } - }, - "/health/live": { - "get": { - "operationId": "_health_live", - "summary": "Store was opened and HTTP service is responding", - "parameters": [], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "const": "ok" - } - }, - "required": [ - "status" - ], - "additionalProperties": false - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "security": [] - } - }, - "/health/ready": { - "get": { - "operationId": "_health_ready", - "summary": "Store was opened and HTTP service is responding", - "parameters": [], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "const": "ok" - } - }, - "required": [ - "status" - ], - "additionalProperties": false - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "security": [] - } - }, - "/openapi.json": { - "get": { - "operationId": "_openapi_json", - "summary": "OpenAPI contract", - "parameters": [], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "security": [] - } - }, - "/api/v1/openapi.json": { - "get": { - "operationId": "_api_v1_openapi_json", - "summary": "OpenAPI contract", - "parameters": [], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "security": [] - } - }, - "/api/v1/projects": { - "get": { - "operationId": "listProjects", - "summary": "List current hosted projects in UUID order", - "parameters": [ - { - "name": "limit", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 100 - } - }, - { - "name": "after", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "q", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectPage" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - }, - "post": { - "operationId": "createProject", - "summary": "Create and persist an empty model", - "parameters": [], - "responses": { - "201": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectSummary" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NameRequest" - } - } - } - } - } - }, - "/api/v1/projects/import": { - "post": { - "operationId": "importProject", - "summary": "Import JSON or UTF-8 Arcadia source as a new hosted resource", - "parameters": [], - "responses": { - "201": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectSummary" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - }, - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "description": "Schema-7 native JSON is described below. Schema-1/2/3/4/5/6 JSON and source are accepted through explicit frozen readers and migration, also on replacement and candidate validation. Earlier versions reject later constructors, fields and semantic facets, including explicit null. Prior prose and typed reviews remain intact; absent semantic review stays Unreviewed. Export emits schema 7 / arcadia 7. See docs/language.rst." - } - }, - "/api/v1/projects/{id}": { - "get": { - "operationId": "getProject", - "summary": "Get complete current envelope", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-None-Match", - "in": "header", - "required": false, - "description": "Return 304 when the current representation's revision matches.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/StoredProject" - } - } - } - }, - "304": { - "description": "Unchanged; empty body." - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - }, - "patch": { - "operationId": "renameProject", - "summary": "Rename the project atomically", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MutationResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NameRequest" - } - } - } - } - }, - "delete": { - "operationId": "deleteProject", - "summary": "Permanently delete the current hosted envelope", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - } - }, - "/api/v1/projects/{id}/model": { - "get": { - "operationId": "getModel", - "summary": "Export native model JSON", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-None-Match", - "in": "header", - "required": false, - "description": "Return 304 when the current representation's revision matches.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - } - } - }, - "304": { - "description": "Unchanged; empty body." - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - }, - "put": { - "operationId": "replaceModel", - "summary": "Conditionally replace model JSON", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MutationResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project" - } - } - } - }, - "description": "Replace semantics while preserving the accepted authored source layout and comments, using common UUIDs as identity correspondence. Model and source commit together. Use source-project PUT to replace authored files." - } - }, - "/api/v1/projects/{id}/source": { - "get": { - "operationId": "getSource", - "summary": "Export canonical Arcadia source", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-None-Match", - "in": "header", - "required": false, - "description": "Return 304 when the current representation's revision matches.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "304": { - "description": "Unchanged; empty body." - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "description": "Canonical single-file interchange view of the current model. Use source-project GET to export exact authored files and comments." - }, - "put": { - "operationId": "replaceSource", - "summary": "Conditionally replace from UTF-8 Arcadia source", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MutationResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } - }, - "description": "Replace semantics from canonical single-file interchange while preserving the existing authored source bundle using common UUIDs. Submitted formatting/comments do not replace the authored workspace. Use source-project PUT for that purpose." - } - }, - "/api/v1/projects/{id}/elements": { - "get": { - "operationId": "listElements", - "summary": "Filter current elements; AND semantics", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 100 - } - }, - { - "name": "after", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "revision", - "in": "query", - "required": false, - "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", - "schema": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - } - }, - { - "name": "kind", - "in": "query", - "required": false, - "description": "", - "schema": { - "$ref": "#/components/schemas/Kind" - } - }, - { - "name": "layer", - "in": "query", - "required": false, - "description": "", - "schema": { - "$ref": "#/components/schemas/Layer" - } - }, - { - "name": "alias", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string" - } - }, - { - "name": "q", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ElementPage" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - } - }, - "/api/v1/projects/{id}/relations": { - "get": { - "operationId": "listRelations", - "summary": "Filter relations by typed kind and endpoints", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 100 - } - }, - { - "name": "after", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "revision", - "in": "query", - "required": false, - "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", - "schema": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - } - }, - { - "name": "kind", - "in": "query", - "required": false, - "description": "", - "schema": { - "$ref": "#/components/schemas/RelationKind" - } - }, - { - "name": "source", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "target", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "incident_to", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RelationPage" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - } - }, - "/api/v1/projects/{id}/artifact-links": { - "get": { - "operationId": "listArtifacts", - "summary": "List model artifact links without fetching URIs", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 100 - } - }, - { - "name": "after", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "revision", - "in": "query", - "required": false, - "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", - "schema": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - } - }, - { - "name": "object", - "in": "query", - "required": false, - "description": "Owner ID.", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ArtifactPage" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - } - }, - "/api/v1/projects/{id}/traces": { - "get": { - "operationId": "listTraces", - "summary": "List OA/SA traces", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "limit", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 100 - } - }, - { - "name": "after", - "in": "query", - "required": false, - "description": "", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "revision", - "in": "query", - "required": false, - "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", - "schema": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - } - }, - { - "name": "object", - "in": "query", - "required": false, - "description": "OA or SA endpoint ID.", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TracePage" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - } - }, - "/api/v1/projects/{id}/query": { - "post": { - "operationId": "queryNeighborhood", - "summary": "Bounded graph neighborhood including relation endpoint closure", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeighborhoodResult" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NeighborhoodRequest" - } - } - } - } - } - }, - "/api/v1/projects/{id}/validation": { - "get": { - "operationId": "validateCurrent", - "summary": "Shared core validation of current snapshot", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationResult" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - } - }, - "post": { - "operationId": "validateCandidate", - "summary": "Check submitted model/source without mutation", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationResult" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationRequest" - } - } - } - }, - "description": "Validate native model JSON, canonical Arcadia source, or a complete source-project bundle without adoption. Source-project diagnostics refer to submitted file paths and positions. Stabilized source/model byte limits are also checked." - } - }, - "/api/v1/projects/{id}/transactions": { - "post": { - "operationId": "executeTransaction", - "summary": "Atomic shared typed commands; optional dry run", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransactionResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransactionRequest" - } - } - } - }, - "description": "Command identity correspondence drives source writeback, including UUID swaps and delete/recreate operations even when final models compare equal. Dry runs return both proposed representations and validate writeback. Failure preserves the accepted pair." - } - }, - "/api/v1/projects/{id}/transitions/preview": { - "post": { - "operationId": "previewTransition", - "summary": "Preview a shared core transition without persisting a plan", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Validated result; preview may include unresolved conflicts.", - "headers": { - "ETag": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransitionEnvelope" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransitionRequest" - } - } - } - }, - "description": "Conflict-free previews also validate authored-source writeback and stabilized model/source capacities." - } - }, - "/api/v1/projects/{id}/transitions/apply": { - "post": { - "operationId": "applyTransition", - "summary": "Apply client-held transition plan against its original revision", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Validated result; preview may include unresolved conflicts.", - "headers": { - "ETag": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MutationResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TransitionEnvelope" - } - } - } - } - } - }, - "/api/v1/projects/import-source-project": { - "post": { - "operationId": "importSourceProject", - "summary": "Import an authored source bundle as a new hosted resource", - "parameters": [], - "responses": { - "201": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - }, - "Location": { - "schema": { - "type": "string" - }, - "description": "Path to the created hosted resource." - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ProjectSummary" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SourceBundle" - } - } - } - }, - "description": "Compile all logical source files, stabilize missing IDs, and persist the accepted model and bundle together. Importing source3 bundles never reads the service filesystem. Export the accepted bundle after import to retain inserted IDs." - } - }, - "/api/v1/projects/{id}/source-project": { - "get": { - "operationId": "getSourceProject", - "summary": "Export the exact accepted authored source bundle", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-None-Match", - "in": "header", - "required": false, - "description": "Return 304 when the current representation's revision matches.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SourceBundle" - } - } - } - }, - "304": { - "description": "Unchanged; empty body." - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "description": "Returns entry and all files with stable IDs, original comments, line endings and module layout. Shares the model ETag; comment-only changes advance that revision." - }, - "put": { - "operationId": "replaceSourceProject", - "summary": "Conditionally replace the complete authored source bundle", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Success", - "headers": { - "ETag": { - "schema": { - "type": "string" - }, - "description": "Project revision validator when scoped to a project." - }, - "X-Request-ID": { - "schema": { - "type": "string", - "format": "uuid" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SourceProjectMutationResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SourceBundle" - } - } - } - }, - "description": "Compile and validate the full submitted bundle; return accepted text with missing UUIDs inserted. Model and source persist together under the exact If-Match precondition. Comment-only or file-layout edits advance revision; an exact pair no-op retains it. Rejected candidates preserve both representations." - } - }, - "/api/v1/projects/{id}/semantic-transitions/query": { - "post": { - "operationId": "querySemanticTransition", - "summary": "Query typed trace drift and linked impacts without mutation", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "responses": { - "200": { - "description": "Validated result; preview may include unresolved conflicts.", - "headers": { - "ETag": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SemanticQueryResult" - } - } - } - }, - "default": { - "$ref": "#/components/responses/Problem" - }, - "409": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SemanticQueryRequest" - } - } - } - }, - "description": "Read one immutable snapshot. Optional revision mismatch returns409; missing trace returns404; duplicate/empty trace selection returns400. References are canonical OA-to-SA correspondence. No write, baseline refresh or external artifact fetch." - } - }, - "/api/v1/projects/{id}/semantic-transitions/preview": { - "post": { - "operationId": "previewSemanticTransition", - "summary": "Preview and source-validate a typed semantic transition without writing", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Validated result; preview may include unresolved conflicts.", - "headers": { - "ETag": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SemanticTransitionEnvelope" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SemanticTransitionRequest" - } - } - } - }, - "description": "Uses the frozen core typed semantic contract. A supplied plan is a client-owned candidate, not a signed receipt; fingerprints check consistency, not original review authorship. Preview validates source writeback when conflict-free. Apply preserves preview-generated IDs and does not replan random identities." - } - }, - "/api/v1/projects/{id}/semantic-transitions/apply": { - "post": { - "operationId": "applySemanticTransition", - "summary": "Conditionally apply a prepared semantic candidate in one model/source revision", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "Hosted resource UUID; distinct from editable model ID.", - "schema": { - "type": "string", - "format": "uuid" - } - }, - { - "name": "If-Match", - "in": "header", - "required": true, - "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Validated result; preview may include unresolved conflicts.", - "headers": { - "ETag": { - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/MutationResult" - } - } - } - }, - "412": { - "$ref": "#/components/responses/Problem" - }, - "428": { - "$ref": "#/components/responses/Problem" - }, - "default": { - "$ref": "#/components/responses/Problem" - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SemanticTransitionEnvelope" - } - } - } - }, - "description": "Uses the frozen core typed semantic contract. A supplied plan is a client-owned candidate, not a signed receipt; fingerprints check consistency, not original review authorship. Preview validates source writeback when conflict-free. Apply preserves preview-generated IDs and does not replan random identities." - } - } - }, - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "description": "Optional for unconfigured loopback; required on non-loopback." - } - }, - "responses": { - "Problem": { - "description": "Structured problem response. See code/detail and docs/api.rst status mapping.", - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/Problem" - } - } - } - } - }, - "schemas": { - "Id": { - "type": "string", - "format": "uuid" - }, - "Revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "Layer": { - "type": "string", - "enum": [ - "Oa", - "Sa" - ] - }, - "Kind": { - "type": "string", - "enum": [ - "Entity", - "Actor", - "Role", - "Activity", - "Capability", - "OperationalMission", - "Process", - "System", - "SystemActor", - "Function", - "SystemCapability", - "Mission", - "FunctionalChain", - "ExchangeItem", - "ExchangeCategory", - "DataType", - "DataField", - "Interface", - "Port", - "Scenario", - "Lifeline", - "Occurrence", - "StateMachine", - "Region", - "State", - "Mode", - "Initial", - "Final", - "Choice", - "Junction", - "Fork", - "Join", - "ShallowHistory", - "DeepHistory", - "EntryPoint", - "ExitPoint", - "Terminate", - "Event", - "Constraint" - ] - }, - "RelationKind": { - "type": "string", - "enum": [ - "InterfaceItemAllocation", - "DataAssociation", - "Allocation", - "PortAllocation", - "Interaction", - "CapabilityInvolvement", - "ChainInvolvement", - "RoleRealization", - "RoleAssignment", - "FunctionalExchange", - "BoundaryExchange", - "CommunicationMean", - "MissionCapability", - "CapabilityInclude", - "CapabilityExtend", - "CapabilityGeneralization", - "MissionInvolvement", - "SequenceLink", - "ContextualLink", - "StateTransition", - "Availability", - "ExchangeAllocation", - "InterfaceProvision", - "InterfaceRequirement", - "StateAction" - ] - }, - "Position": { - "type": "object", - "properties": { - "x": { - "type": "number", - "minimum": -1000000, - "maximum": 1000000 - }, - "y": { - "type": "number", - "minimum": -1000000, - "maximum": 1000000 - } - }, - "required": [ - "x", - "y" - ], - "additionalProperties": false - }, - "EdgeLabel": { - "type": "object", - "description": "Authored label placement relative to the computed polyline. Both fields are required.", - "properties": { - "fraction": { - "type": "number", - "minimum": 0, - "maximum": 1, - "description": "Fraction of source-to-target polyline arc length." - }, - "offset": { - "$ref": "#/components/schemas/Position", - "description": "Relative diagram-space displacement." - } - }, - "required": [ - "fraction", - "offset" - ], - "additionalProperties": false - }, - "EdgeRoute": { - "type": "object", - "description": "One diagram's authored route for an existing Relation or Trace, including currently hidden edges. No separate identity. Only pinned points are stored; explicit empty routes are retained.", - "properties": { - "edge": { - "type": "string", - "format": "uuid", - "description": "Existing Relation or Trace identity, never a node placement." - }, - "waypoints": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Position" - }, - "maxItems": 128, - "default": [], - "description": "Absolute diagram positions in traversal order." - }, - "label": { - "oneOf": [ - { - "$ref": "#/components/schemas/EdgeLabel" - }, - { - "type": "null" - } - ], - "default": null - } - }, - "required": [ - "edge" - ], - "additionalProperties": false - }, - "Size": { - "type": "object", - "properties": { - "width": { - "type": "number", - "exclusiveMinimum": 0 - }, - "height": { - "type": "number", - "exclusiveMinimum": 0 - } - }, - "required": [ - "width", - "height" - ], - "additionalProperties": false - }, - "DataTypeDefinition": { - "oneOf": [ - { - "type": "string", - "enum": [ - "Boolean" - ] - }, - { - "type": "object", - "properties": { - "Integer": { - "type": "object", - "properties": { - "minimum": { - "anyOf": [ - { - "type": "integer", - "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807 - }, - { - "type": "null" - } - ] - }, - "maximum": { - "anyOf": [ - { - "type": "integer", - "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "minimum", - "maximum" - ], - "additionalProperties": false - } - }, - "required": [ - "Integer" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Real": { - "type": "object", - "properties": { - "minimum": { - "anyOf": [ - { - "type": "number", - "format": "double", - "description": "Finite IEEE-754 binary64; no coercion from typed Integer values." - }, - { - "type": "null" - } - ] - }, - "maximum": { - "anyOf": [ - { - "type": "number", - "format": "double", - "description": "Finite IEEE-754 binary64; no coercion from typed Integer values." - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "minimum", - "maximum" - ], - "additionalProperties": false - } - }, - "required": [ - "Real" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "String": { - "type": "object", - "properties": { - "minimum_length": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "maximum_length": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "minimum_length", - "maximum_length" - ], - "additionalProperties": false - } - }, - "required": [ - "String" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Enumeration": { - "type": "object", - "properties": { - "literals": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "literals" - ], - "additionalProperties": false - } - }, - "required": [ - "Enumeration" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Record": { - "type": "object", - "properties": { - "supertype": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "supertype" - ], - "additionalProperties": false - } - }, - "required": [ - "Record" - ], - "additionalProperties": false - } - ] - }, - "TypeRef": { - "oneOf": [ - { - "type": "string", - "enum": [ - "Boolean", - "Integer", - "Real", - "String" - ] - }, - { - "oneOf": [ - { - "type": "object", - "properties": { - "Named": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "Named" - ], - "additionalProperties": false - } - ] - } - ] - }, - "OccurrenceBody": { - "oneOf": [ - { - "type": "object", - "properties": { - "Exchange": { - "type": "object", - "properties": { - "exchange": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "sender": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "receiver": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "message_kind": { - "$ref": "#/components/schemas/MessageKind", - "default": "Asynchronous" - }, - "reply_to": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ], - "default": null - }, - "values": { - "type": "array", - "items": { - "$ref": "#/components/schemas/MessageValue" - }, - "default": [] - }, - "operation": { - "anyOf": [ - { - "$ref": "#/components/schemas/OperationUse" - }, - { - "type": "null" - } - ], - "default": null - } - }, - "required": [ - "exchange", - "sender", - "receiver" - ], - "additionalProperties": false, - "description": "Absent/null operation retains ordinary channel semantics and is omitted on output. Present operation is allowed only on SA BoundaryInterface Call/Asynchronous messages. Reply stores no operation and derives its exact Call's selection. Existing message defaults remain unchanged." - } - }, - "required": [ - "Exchange" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Action": { - "type": "object", - "properties": { - "action": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "lifeline": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "action", - "lifeline" - ], - "additionalProperties": false - } - }, - "required": [ - "Action" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "State": { - "type": "object", - "properties": { - "state": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "lifeline": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "state", - "lifeline" - ], - "additionalProperties": false - } - }, - "required": [ - "State" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Fragment": { - "type": "object", - "properties": { - "operator": { - "type": "string", - "enum": [ - "Alternative", - "Parallel", - "Loop", - "Optional" - ] - }, - "branches": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "operator", - "branches" - ], - "additionalProperties": false - } - }, - "required": [ - "Fragment" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Branch": { - "type": "object", - "properties": { - "guard": { - "type": "string" - }, - "occurrences": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "guard", - "occurrences" - ], - "additionalProperties": false - } - }, - "required": [ - "Branch" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Control": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/components/schemas/ControlKind" - } - }, - "required": [ - "kind" - ], - "additionalProperties": false - } - }, - "required": [ - "Control" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SequenceReference": { - "type": "object", - "properties": { - "sequence": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "sequence" - ], - "additionalProperties": false - } - }, - "required": [ - "SequenceReference" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ScenarioUse": { - "type": "object", - "properties": { - "scenario": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "bindings": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LifelineBinding" - } - } - }, - "required": [ - "scenario", - "bindings" - ], - "additionalProperties": false - } - }, - "required": [ - "ScenarioUse" - ], - "additionalProperties": false - } - ] - }, - "SharedBody": { - "oneOf": [ - { - "type": "string", - "enum": [ - "StateMachine", - "Region" - ] - }, - { - "oneOf": [ - { - "type": "object", - "properties": { - "ExchangeItem": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/components/schemas/ExchangeItemKind" - }, - "mechanism": { - "$ref": "#/components/schemas/ExchangeMechanism" - }, - "supertype": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "kind", - "mechanism", - "supertype" - ], - "additionalProperties": false - } - }, - "required": [ - "ExchangeItem" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "DataType": { - "type": "object", - "properties": { - "definition": { - "$ref": "#/components/schemas/DataTypeDefinition" - }, - "default": { - "anyOf": [ - { - "$ref": "#/components/schemas/DataValue" - }, - { - "type": "null" - } - ] - }, - "unit": { - "type": "string" - } - }, - "required": [ - "definition", - "default", - "unit" - ], - "additionalProperties": false - } - }, - "required": [ - "DataType" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "DataField": { - "type": "object", - "properties": { - "type_ref": { - "$ref": "#/components/schemas/TypeRef" - }, - "minimum": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "maximum": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - { - "type": "null" - } - ] - }, - "unit": { - "type": "string" - }, - "direction": { - "$ref": "#/components/schemas/ParameterDirection" - }, - "storage": { - "$ref": "#/components/schemas/FieldStorage" - }, - "default": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/DataValue" - } - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "type_ref", - "minimum", - "maximum", - "unit", - "direction", - "storage", - "default" - ], - "additionalProperties": false - } - }, - "required": [ - "DataField" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Interface": { - "type": "object", - "properties": { - "supertype": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "supertype" - ], - "additionalProperties": false - } - }, - "required": [ - "Interface" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Port": { - "type": "object", - "properties": { - "contract": { - "$ref": "#/components/schemas/PortContract" - }, - "legacy": { - "anyOf": [ - { - "$ref": "#/components/schemas/LegacyPortContract" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "contract", - "legacy" - ], - "additionalProperties": false - } - }, - "required": [ - "Port" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Scenario": { - "type": "object", - "properties": { - "context": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "precondition": { - "type": "string" - }, - "postcondition": { - "type": "string" - }, - "steps": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - }, - "viewpoint": { - "$ref": "#/components/schemas/ScenarioViewpoint", - "default": "MixedLegacy", - "description": "Omitted schema-2 viewpoint retains legacy mixed lifelines. New graphical factories select OperationalActivity or Functional explicitly." - } - }, - "required": [ - "context", - "precondition", - "postcondition", - "steps" - ], - "additionalProperties": false - } - }, - "required": [ - "Scenario" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Lifeline": { - "type": "object", - "properties": { - "represented": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "represented" - ], - "additionalProperties": false - } - }, - "required": [ - "Lifeline" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Occurrence": { - "type": "object", - "properties": { - "body": { - "$ref": "#/components/schemas/OccurrenceBody" - }, - "precondition": { - "type": "string" - }, - "postcondition": { - "type": "string" - } - }, - "required": [ - "body", - "precondition", - "postcondition" - ], - "additionalProperties": false - } - }, - "required": [ - "Occurrence" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "State": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/components/schemas/StateKind" - }, - "entry": { - "type": "string" - }, - "exit": { - "type": "string" - }, - "do_activity": { - "type": "string" - } - }, - "required": [ - "kind", - "entry", - "exit", - "do_activity" - ], - "additionalProperties": false - } - }, - "required": [ - "State" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Constraint": { - "type": "object", - "properties": { - "expression": { - "type": "string" - }, - "constrained": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "expression", - "constrained" - ], - "additionalProperties": false - } - }, - "required": [ - "Constraint" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Event": { - "type": "object", - "properties": { - "body": { - "$ref": "#/components/schemas/EventBody" - } - }, - "required": [ - "body" - ], - "additionalProperties": false - } - }, - "required": [ - "Event" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ExchangeCategory": { - "type": "object", - "properties": { - "family": { - "$ref": "#/components/schemas/ExchangeFamily" - }, - "exchanges": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - } - }, - "required": [ - "family", - "exchanges" - ], - "additionalProperties": false - } - }, - "required": [ - "ExchangeCategory" - ], - "additionalProperties": false - } - ] - } - ] - }, - "OaBody": { - "oneOf": [ - { - "type": "string", - "enum": [ - "Entity", - "Actor", - "Role", - "Activity", - "Capability", - "Mission" - ] - }, - { - "oneOf": [ - { - "type": "object", - "properties": { - "Process": { - "type": "object", - "properties": { - "steps": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "steps" - ], - "additionalProperties": false - } - }, - "required": [ - "Process" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Shared": { - "$ref": "#/components/schemas/SharedBody" - } - }, - "required": [ - "Shared" - ], - "additionalProperties": false - } - ] - } - ] - }, - "SaBody": { - "oneOf": [ - { - "type": "string", - "enum": [ - "System", - "Actor", - "Function", - "Capability", - "Mission" - ] - }, - { - "oneOf": [ - { - "type": "object", - "properties": { - "FunctionalChain": { - "type": "object", - "properties": { - "steps": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "steps" - ], - "additionalProperties": false - } - }, - "required": [ - "FunctionalChain" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Shared": { - "$ref": "#/components/schemas/SharedBody" - } - }, - "required": [ - "Shared" - ], - "additionalProperties": false - } - ] - } - ] - }, - "ElementBody": { - "oneOf": [ - { - "type": "object", - "properties": { - "Oa": { - "$ref": "#/components/schemas/OaBody" - } - }, - "required": [ - "Oa" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Sa": { - "$ref": "#/components/schemas/SaBody" - } - }, - "required": [ - "Sa" - ], - "additionalProperties": false - } - ] - }, - "Element": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "parent": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - }, - "body": { - "$ref": "#/components/schemas/ElementBody" - } - }, - "required": [ - "id", - "alias", - "name", - "description", - "parent", - "body" - ], - "additionalProperties": false - }, - "RelationBody": { - "oneOf": [ - { - "type": "object", - "properties": { - "Allocation": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "Allocation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CapabilityInvolvement": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "CapabilityInvolvement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ChainInvolvement": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "ChainInvolvement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RoleRealization": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "RoleRealization" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RoleAssignment": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "RoleAssignment" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "MissionCapability": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "MissionCapability" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Availability": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "Availability" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ExchangeAllocation": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "ExchangeAllocation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "InterfaceProvision": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "InterfaceProvision" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "InterfaceRequirement": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "InterfaceRequirement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Interaction": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "items": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "source", - "target", - "items" - ], - "additionalProperties": false - } - }, - "required": [ - "Interaction" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "BoundaryExchange": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "items": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - }, - "oriented": { - "type": "boolean", - "description": "Independent of component port kind/orientation. Old V2 exchanges migrate to true." - } - }, - "required": [ - "source", - "target", - "items", - "oriented" - ], - "additionalProperties": false - } - }, - "required": [ - "BoundaryExchange" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "FunctionalExchange": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "source_port": { - "type": "string", - "format": "uuid" - }, - "target_port": { - "type": "string", - "format": "uuid" - }, - "items": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "source", - "target", - "source_port", - "target_port", - "items" - ], - "additionalProperties": false - } - }, - "required": [ - "FunctionalExchange" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "StateTransition": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "trigger": { - "type": "string" - }, - "guard": { - "type": "string" - }, - "effect": { - "type": "string" - }, - "transition_kind": { - "$ref": "#/components/schemas/TransitionKind", - "default": "External", - "description": "Omitted legacy schema-2 value is External." - }, - "triggers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "uniqueItems": true, - "description": "Same-layer Event or Activity/Function references; alternatives, not implicit conjunction.", - "default": [] - }, - "effects": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "uniqueItems": true, - "description": "Same-layer Activity/Function references describing transition effects.", - "default": [] - } - }, - "required": [ - "source", - "target", - "trigger", - "guard", - "effect" - ], - "additionalProperties": false, - "description": "Existing trigger, guard and effect prose is preserved exactly. New typed properties are optional only for staged schema-2 compatibility. Removing the last typed trigger retains a descriptive unresolved-trigger placeholder instead of silently introducing completion semantics." - } - }, - "required": [ - "StateTransition" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "StateAction": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "phase": { - "type": "string", - "enum": [ - "Entry", - "Do", - "Exit" - ] - } - }, - "required": [ - "source", - "target", - "phase" - ], - "additionalProperties": false - } - }, - "required": [ - "StateAction" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CapabilityInclude": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "CapabilityInclude" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CapabilityExtend": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "condition": { - "type": "string", - "description": "Engineering condition under which the extending capability adds behavior." - } - }, - "required": [ - "source", - "target", - "condition" - ], - "additionalProperties": false - } - }, - "required": [ - "CapabilityExtend" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CapabilityGeneralization": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "CapabilityGeneralization" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "MissionInvolvement": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "MissionInvolvement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SequenceLink": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "guard": { - "type": "string", - "description": "Condition for a contextual process/chain path. Choice and loop paths should use explicit conditions or else." - } - }, - "required": [ - "source", - "target", - "guard" - ], - "additionalProperties": false - } - }, - "required": [ - "SequenceLink" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "PortAllocation": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "source", - "target" - ], - "additionalProperties": false - } - }, - "required": [ - "PortAllocation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "InterfaceItemAllocation": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "order": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "transmission": { - "$ref": "#/components/schemas/TransmissionProtocol" - }, - "acquisition": { - "$ref": "#/components/schemas/AcquisitionProtocol" - } - }, - "required": [ - "source", - "target", - "order", - "transmission", - "acquisition" - ], - "additionalProperties": false - } - }, - "required": [ - "InterfaceItemAllocation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "DataAssociation": { - "type": "object", - "properties": { - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "source_role": { - "type": "string" - }, - "target_role": { - "type": "string" - }, - "source_minimum": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "source_maximum": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - { - "type": "null" - } - ] - }, - "target_minimum": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - "target_maximum": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - { - "type": "null" - } - ] - }, - "source_navigable": { - "type": "boolean" - }, - "target_navigable": { - "type": "boolean" - } - }, - "required": [ - "source", - "target", - "source_role", - "target_role", - "source_minimum", - "source_maximum", - "target_minimum", - "target_maximum", - "source_navigable", - "target_navigable" - ], - "additionalProperties": false - } - }, - "required": [ - "DataAssociation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CommunicationMean": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "target": { - "$ref": "#/components/schemas/Id" - }, - "oriented": { - "type": "boolean" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - } - }, - "required": [ - "source", - "target", - "oriented", - "items" - ], - "additionalProperties": false - } - }, - "required": [ - "CommunicationMean" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ContextualLink": { - "type": "object", - "properties": { - "owner": { - "$ref": "#/components/schemas/Id" - }, - "source": { - "$ref": "#/components/schemas/OccurrenceEndpoint" - }, - "target": { - "$ref": "#/components/schemas/OccurrenceEndpoint" - }, - "purpose": { - "$ref": "#/components/schemas/ContextualLinkPurpose" - } - }, - "required": [ - "owner", - "source", - "target", - "purpose" - ], - "additionalProperties": false - } - }, - "required": [ - "ContextualLink" - ], - "additionalProperties": false - } - ] - }, - "Relation": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "body": { - "$ref": "#/components/schemas/RelationBody" - } - }, - "required": [ - "id", - "alias", - "name", - "description", - "body" - ], - "additionalProperties": false - }, - "PortAnchorSide": { - "type": "string", - "enum": [ - "Left", - "Right", - "Top", - "Bottom" - ], - "description": "Presentation side of the owner placement; independent of semantic port direction." - }, - "PortAnchor": { - "type": "object", - "description": "Manual position of a direct child Port on one owner placement. Each port occurs at most once per placement; this record has no separate identity.", - "properties": { - "port": { - "type": "string", - "format": "uuid" - }, - "side": { - "$ref": "#/components/schemas/PortAnchorSide" - }, - "offset": { - "type": "number", - "minimum": 0, - "maximum": 1, - "description": "Finite binary32 fraction of the complete owner edge, measured from top on Left/Right and from left on Top/Bottom." - } - }, - "required": [ - "port", - "side", - "offset" - ], - "additionalProperties": false - }, - "NodePlacement": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "element": { - "type": "string", - "format": "uuid" - }, - "position": { - "$ref": "#/components/schemas/Position" - }, - "size": { - "$ref": "#/components/schemas/Size" - }, - "collapsed": { - "type": "boolean" - }, - "port_anchors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PortAnchor" - }, - "default": [], - "description": "Per-placement manual port positions. Omitted and empty both select automatic positioning; empty lists are omitted on serialization. Duplicate port references are invalid." - } - }, - "required": [ - "id", - "alias", - "element", - "position", - "size", - "collapsed" - ], - "additionalProperties": false - }, - "ProjectionKind": { - "description": "Existing projections remain strings. StateMachine carries one required UUID reference to an existing StateMachine in the diagram layer (state_diagram_context). Its placements may contain only that machine and its regions/state vertices, including nested members (state_diagram_member). Empty and partial views are valid. Incompatible placements must be removed or relocated in the same transaction when changing context; accepting or reading a view does not synthesize semantic records or placements.", - "oneOf": [ - { - "type": "string", - "enum": [ - "FunctionalDataflow", - "Capabilities", - "Overview", - "Decomposition", - "Allocation", - "Traceability" - ] - }, - { - "type": "object", - "properties": { - "StateMachine": { - "type": "object", - "properties": { - "machine": { - "type": "string", - "format": "uuid", - "description": "Required identity of the same-layer StateMachine that owns this view's members." - } - }, - "required": [ - "machine" - ], - "additionalProperties": false - } - }, - "required": [ - "StateMachine" - ], - "additionalProperties": false - } - ], - "examples": [ - "Overview", - { - "StateMachine": { - "machine": "11111111-1111-4111-8111-111111111111" - } - } - ] - }, - "Diagram": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "layer": { - "$ref": "#/components/schemas/Layer" - }, - "projection": { - "$ref": "#/components/schemas/ProjectionKind" - }, - "nodes": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/NodePlacement" - } - }, - "routes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EdgeRoute" - }, - "default": [], - "description": "Unique by edge identity within this diagram. Stored order is retained. Each entry counts against the total model record admission limit. Missing routes in earlier schema-3 models default to empty." - }, - "view_options": { - "$ref": "#/components/schemas/ViewOptions", - "default": {} - } - }, - "required": [ - "id", - "alias", - "name", - "description", - "layer", - "projection", - "nodes" - ], - "additionalProperties": false - }, - "ArtifactLink": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "owner": { - "type": "string", - "format": "uuid" - }, - "relation": { - "type": "string", - "enum": [ - "References", - "Satisfies", - "Verifies", - "Implements", - "DerivedFrom" - ] - }, - "uri": { - "type": "string", - "description": "Opaque external artifact reference. Server never fetches or executes it." - }, - "revision": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "fragment": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "id", - "alias", - "name", - "description", - "owner", - "relation", - "uri", - "revision", - "fragment" - ], - "additionalProperties": false - }, - "TracedProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": [ - "name", - "description" - ], - "additionalProperties": false - }, - "TraceBaseline": { - "type": "object", - "properties": { - "oa": { - "$ref": "#/components/schemas/TracedProperties" - }, - "sa": { - "$ref": "#/components/schemas/TracedProperties" - }, - "semantic": { - "anyOf": [ - { - "$ref": "#/components/schemas/SemanticBaseline" - }, - { - "type": "null" - } - ], - "default": null, - "description": "Absent/null means Unreviewed. Historical typed facts never become live graph references." - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "Trace": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "oa": { - "type": "string", - "format": "uuid" - }, - "sa": { - "type": "string", - "format": "uuid" - }, - "kind": { - "type": "string", - "enum": [ - "Refinement", - "Participant", - "Capability", - "Mission", - "Exchange", - "Sequence", - "Item", - "Scenario", - "State", - "Manual" - ] - }, - "baseline": { - "anyOf": [ - { - "$ref": "#/components/schemas/TraceBaseline" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "id", - "alias", - "name", - "description", - "oa", - "sa", - "kind", - "baseline" - ], - "additionalProperties": false - }, - "Project": { - "type": "object", - "properties": { - "schema_version": { - "type": "integer", - "const": 7 - }, - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "elements": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Element" - } - }, - "relations": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Relation" - } - }, - "diagrams": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Diagram" - } - }, - "artifact_links": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/ArtifactLink" - } - }, - "traces": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Trace" - } - } - }, - "required": [ - "schema_version", - "id", - "alias", - "name", - "description", - "elements", - "relations", - "diagrams", - "artifact_links", - "traces" - ], - "additionalProperties": false - }, - "StoredProject": { - "type": "object", - "properties": { - "resource_id": { - "type": "string", - "format": "uuid" - }, - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "model": { - "$ref": "#/components/schemas/Project" - }, - "migration": { - "anyOf": [ - { - "$ref": "#/components/schemas/MigrationReport" - }, - { - "type": "null" - } - ], - "description": "Present after legacy import/reopen; excluded from persisted envelopes. Resource identity and revision are unchanged by reopening." - }, - "source_project": { - "$ref": "#/components/schemas/SourceBundle" - } - }, - "required": [ - "resource_id", - "revision", - "model", - "source_project" - ], - "additionalProperties": false, - "description": "Accepted coupled model/source snapshot. Compiling source_project reproduces model; revision changes once if either representation changes. Older model-only or schema3 coupled envelopes migrate in memory without a write or revision change; both representations must independently migrate to the same graph." - }, - "ProjectSummary": { - "type": "object", - "properties": { - "resource_id": { - "type": "string", - "format": "uuid" - }, - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "model_id": { - "type": "string", - "format": "uuid" - }, - "schema_version": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "elements": { - "type": "integer", - "minimum": 0 - }, - "relations": { - "type": "integer", - "minimum": 0 - }, - "migration": { - "anyOf": [ - { - "$ref": "#/components/schemas/MigrationReport" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "resource_id", - "revision", - "model_id", - "schema_version", - "name", - "elements", - "relations" - ], - "additionalProperties": false - }, - "Issue": { - "type": "object", - "properties": { - "severity": { - "type": "string", - "enum": [ - "error", - "warning" - ] - }, - "message": { - "type": "string" - }, - "object_id": { - "type": "string", - "format": "uuid" - }, - "line": { - "type": "integer", - "minimum": 1 - }, - "column": { - "type": "integer", - "minimum": 1, - "description": "One-based Unicode scalar column." - }, - "code": { - "type": "string" - }, - "field": { - "type": "string" - }, - "path": { - "type": "string", - "description": "Logical source file path, when a source project supplied the diagnostic." - }, - "span": { - "$ref": "#/components/schemas/SourceSpan" - }, - "related": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SourceLocation" - } - } - }, - "required": [ - "severity", - "message", - "code" - ], - "additionalProperties": false - }, - "Problem": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "title": { - "type": "string" - }, - "status": { - "type": "integer" - }, - "code": { - "type": "string" - }, - "detail": { - "type": "string" - }, - "current_revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Issue" - } - } - }, - "required": [ - "type", - "title", - "status", - "code", - "detail" - ], - "additionalProperties": false - }, - "NameRequest": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - }, - "required": [ - "name" - ], - "additionalProperties": false - }, - "Command": { - "oneOf": [ - { - "type": "object", - "properties": { - "Replace": { - "$ref": "#/components/schemas/Project" - } - }, - "required": [ - "Replace" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Batch": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Command" - } - } - }, - "required": [ - "Batch" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Add": { - "$ref": "#/components/schemas/Kind" - } - }, - "required": [ - "Add" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "AddElement": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/components/schemas/Kind" - }, - "layer": { - "$ref": "#/components/schemas/Layer" - }, - "name": { - "type": "string" - }, - "parent": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "kind", - "layer", - "name", - "parent" - ], - "additionalProperties": false - } - }, - "required": [ - "AddElement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Connect": { - "type": "array", - "prefixItems": [ - { - "$ref": "#/components/schemas/RelationKind" - }, - { - "type": "string" - }, - { - "type": "string", - "format": "uuid" - }, - { - "type": "string", - "format": "uuid" - } - ], - "minItems": 4, - "maxItems": 4 - } - }, - "required": [ - "Connect" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditProject": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string", - "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", - "description": "Unique persistent alias; core reserved words are forbidden." - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": [ - "id", - "alias", - "name", - "description" - ], - "additionalProperties": false - } - }, - "required": [ - "EditProject" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Reparent": { - "type": "object", - "properties": { - "element": { - "type": "string", - "format": "uuid" - }, - "parent": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "element", - "parent" - ], - "additionalProperties": false - } - }, - "required": [ - "Reparent" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Reidentify": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "string", - "format": "uuid" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "Reidentify" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Rename": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "string" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "Rename" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Describe": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "string" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "Describe" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SetAlias": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "string" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "SetAlias" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Delete": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "Delete" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "AddStep": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "string", - "format": "uuid" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "AddStep" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RemoveStep": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "integer", - "minimum": 0 - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "RemoveStep" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "MoveStep": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "integer", - "minimum": 0 - }, - { - "type": "integer", - "minimum": 0 - } - ], - "minItems": 3, - "maxItems": 3 - } - }, - "required": [ - "MoveStep" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SetSteps": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "SetSteps" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Layout": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "Layout" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CreateNode": { - "type": "object", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "node": { - "$ref": "#/components/schemas/NodePlacement" - } - }, - "required": [ - "diagram", - "node" - ], - "additionalProperties": false - } - }, - "required": [ - "CreateNode" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditNode": { - "type": "object", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "original": { - "type": "string", - "format": "uuid" - }, - "node": { - "$ref": "#/components/schemas/NodePlacement" - } - }, - "required": [ - "diagram", - "original", - "node" - ], - "additionalProperties": false - } - }, - "required": [ - "EditNode" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RelocateNode": { - "type": "object", - "description": "Move an existing placement to another diagram while preserving its UUID, alias, geometry, collapse state and dependent artifact references. The target diagram must accept the element and must not already contain its placement.", - "properties": { - "node": { - "type": "string", - "format": "uuid" - }, - "diagram": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "node", - "diagram" - ], - "additionalProperties": false - } - }, - "required": [ - "RelocateNode" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "MoveNode": { - "type": "object", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "node": { - "type": "string", - "format": "uuid" - }, - "position": { - "$ref": "#/components/schemas/Position" - }, - "with_descendants": { - "type": "boolean" - } - }, - "required": [ - "diagram", - "node", - "position", - "with_descendants" - ], - "additionalProperties": false - } - }, - "required": [ - "MoveNode" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SetEdgeRoute": { - "type": "object", - "description": "Upsert by edge identity; preserves unrelated route entries and order. Uses existing transaction validation and ETag checks.", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "route": { - "$ref": "#/components/schemas/EdgeRoute" - } - }, - "required": [ - "diagram", - "route" - ], - "additionalProperties": false - } - }, - "required": [ - "SetEdgeRoute" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RemoveEdgeRoute": { - "type": "object", - "description": "Reset to automatic routing. No-op if this existing diagram has no route for the existing edge.", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "edge": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "diagram", - "edge" - ], - "additionalProperties": false - } - }, - "required": [ - "RemoveEdgeRoute" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SetPortAnchor": { - "type": "object", - "description": "Upsert manual port position by port identity on an existing owner placement, preserving unrelated entries. Port must be a direct child of the placement's semantic element.", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "node": { - "type": "string", - "format": "uuid" - }, - "anchor": { - "$ref": "#/components/schemas/PortAnchor" - } - }, - "required": [ - "diagram", - "node", - "anchor" - ], - "additionalProperties": false - } - }, - "required": [ - "SetPortAnchor" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RemovePortAnchor": { - "type": "object", - "description": "Restore automatic positioning for an existing direct child Port. No-op when that valid owner placement has no manual anchor for the port; wrong or stale ownership is rejected.", - "properties": { - "diagram": { - "type": "string", - "format": "uuid" - }, - "node": { - "type": "string", - "format": "uuid" - }, - "port": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "diagram", - "node", - "port" - ], - "additionalProperties": false - } - }, - "required": [ - "RemovePortAnchor" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CreateElement": { - "$ref": "#/components/schemas/Element" - } - }, - "required": [ - "CreateElement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditElement": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "$ref": "#/components/schemas/Element" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "EditElement" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CreateRelation": { - "$ref": "#/components/schemas/Relation" - } - }, - "required": [ - "CreateRelation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditRelation": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "$ref": "#/components/schemas/Relation" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "EditRelation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CreateDiagram": { - "$ref": "#/components/schemas/Diagram" - } - }, - "required": [ - "CreateDiagram" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditDiagram": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "$ref": "#/components/schemas/Diagram" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "EditDiagram" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CreateArtifact": { - "$ref": "#/components/schemas/ArtifactLink" - } - }, - "required": [ - "CreateArtifact" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditArtifact": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "$ref": "#/components/schemas/ArtifactLink" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "EditArtifact" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CreateTrace": { - "$ref": "#/components/schemas/Trace" - } - }, - "required": [ - "CreateTrace" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "EditTrace": { - "type": "array", - "prefixItems": [ - { - "type": "string", - "format": "uuid" - }, - { - "$ref": "#/components/schemas/Trace" - } - ], - "minItems": 2, - "maxItems": 2 - } - }, - "required": [ - "EditTrace" - ], - "additionalProperties": false - } - ] - }, - "TransactionRequest": { - "type": "object", - "properties": { - "commands": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Command" - }, - "minItems": 1, - "maxItems": 200 - }, - "dry_run": { - "type": "boolean", - "default": false - } - }, - "required": [ - "commands" - ], - "additionalProperties": false - }, - "TransactionResult": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "dry_run": { - "type": "boolean" - }, - "changed": { - "type": "boolean" - }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Issue" - } - }, - "model": { - "$ref": "#/components/schemas/Project", - "description": "Proposed model, present only for a dry run." - }, - "source_project": { - "$ref": "#/components/schemas/SourceBundle", - "description": "Proposed accepted bundle, present only for a dry run. Source writeback is validated before returning a successful candidate." - } - }, - "required": [ - "revision", - "dry_run", - "changed", - "issues" - ], - "additionalProperties": false - }, - "MutationResult": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "changed": { - "type": "boolean" - }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Issue" - } - }, - "migration": { - "anyOf": [ - { - "$ref": "#/components/schemas/MigrationReport" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "changed" - ], - "additionalProperties": false - }, - "ValidationRequest": { - "oneOf": [ - { - "type": "object", - "properties": { - "format": { - "const": "json" - }, - "model": { - "$ref": "#/components/schemas/Project" - } - }, - "required": [ - "format", - "model" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "format": { - "const": "arcadia" - }, - "source": { - "type": "string" - } - }, - "required": [ - "format", - "source" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "format": { - "const": "source-project" - }, - "source_project": { - "$ref": "#/components/schemas/SourceBundle" - } - }, - "required": [ - "format", - "source_project" - ], - "additionalProperties": false - } - ] - }, - "ValidationResult": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "valid": { - "type": "boolean" - }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Issue" - } - }, - "migration": { - "anyOf": [ - { - "$ref": "#/components/schemas/MigrationReport" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "valid", - "issues" - ], - "additionalProperties": false - }, - "NeighborhoodRequest": { - "type": "object", - "properties": { - "roots": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "minItems": 1, - "maxItems": 100 - }, - "direction": { - "type": "string", - "enum": [ - "incoming", - "outgoing", - "both" - ], - "default": "both" - }, - "relation_kinds": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RelationKind" - } - }, - "max_depth": { - "type": "integer", - "minimum": 0, - "maximum": 4, - "default": 1 - }, - "max_objects": { - "type": "integer", - "minimum": 1, - "maximum": 1000, - "default": 200 - }, - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - } - }, - "required": [ - "roots" - ], - "additionalProperties": false - }, - "NeighborhoodResult": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "elements": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Element" - } - }, - "relations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Relation" - } - }, - "truncated": { - "type": "boolean" - } - }, - "required": [ - "revision", - "elements", - "relations", - "truncated" - ], - "additionalProperties": false - }, - "ElementPage": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Element" - } - }, - "next_after": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "items", - "next_after" - ], - "additionalProperties": false - }, - "RelationPage": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Relation" - } - }, - "next_after": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "items", - "next_after" - ], - "additionalProperties": false - }, - "ArtifactPage": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ArtifactLink" - } - }, - "next_after": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "items", - "next_after" - ], - "additionalProperties": false - }, - "TracePage": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Trace" - } - }, - "next_after": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "items", - "next_after" - ], - "additionalProperties": false - }, - "ProjectPage": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ProjectSummary" - } - }, - "next_after": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "items", - "next_after" - ], - "additionalProperties": false - }, - "TransitionDirection": { - "type": "string", - "enum": [ - "OaToSa", - "SaToOa" - ] - }, - "TransitionField": { - "type": "string", - "enum": [ - "Name", - "Description" - ] - }, - "TransitionMapping": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "target": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - }, - "target_kind": { - "anyOf": [ - { - "$ref": "#/components/schemas/Kind" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "source", - "target", - "target_kind" - ], - "additionalProperties": false - }, - "FieldDecision": { - "oneOf": [ - { - "type": "string", - "enum": [ - "TakeSource", - "KeepTarget" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "string" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "FieldResolution": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "field": { - "$ref": "#/components/schemas/TransitionField" - }, - "decision": { - "$ref": "#/components/schemas/FieldDecision" - } - }, - "required": [ - "source", - "field", - "decision" - ], - "additionalProperties": false - }, - "TransitionRequest": { - "type": "object", - "properties": { - "direction": { - "$ref": "#/components/schemas/TransitionDirection" - }, - "sources": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "maxItems": 100 - }, - "scope": { - "type": "string", - "enum": [ - "SelectedOnly", - "Required", - "Subtree" - ] - }, - "mappings": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TransitionMapping" - }, - "maxItems": 200 - }, - "fields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TransitionField" - } - }, - "resolutions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FieldResolution" - }, - "maxItems": 400 - }, - "destination_diagram": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - }, - "communication_endpoints": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CommunicationEndpointDecision" - }, - "maxItems": 200, - "default": [], - "description": "Explicit SA component-port pair per selected OA CommunicationMean. Reuse or Create only. Owners follow reviewed participant mappings; no participant-to-port substitution or automatic interface allocation." - }, - "operation_omissions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OperationOmission" - }, - "maxItems": 200, - "default": [], - "description": "Explicit SA-to-OA abstraction per selected invocation with operation intent. Nonempty rationale required by core; duplicates, irrelevant sources, and other directions are refused. Replies derive the Call abstraction. Source operation identities and values are preserved." - } - }, - "required": [ - "direction", - "sources", - "scope", - "mappings", - "fields", - "resolutions", - "destination_diagram" - ], - "additionalProperties": false - }, - "FieldChange": { - "type": "object", - "properties": { - "field": { - "$ref": "#/components/schemas/TransitionField" - }, - "before": { - "type": "string" - }, - "after": { - "type": "string" - } - }, - "required": [ - "field", - "before", - "after" - ], - "additionalProperties": false - }, - "TransitionChange": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "target": { - "$ref": "#/components/schemas/Id" - }, - "action": { - "type": "string", - "enum": [ - "Create", - "Update", - "Reuse" - ] - }, - "fields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FieldChange" - } - } - }, - "required": [ - "source", - "target", - "action", - "fields" - ], - "additionalProperties": false - }, - "TransitionConflict": { - "type": "object", - "properties": { - "source": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - }, - "target": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - }, - "field": { - "anyOf": [ - { - "$ref": "#/components/schemas/TransitionField" - }, - { - "type": "null" - } - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "source", - "target", - "field", - "message" - ], - "additionalProperties": false - }, - "TransitionPlan": { - "type": "object", - "properties": { - "base_fingerprint": { - "type": "string" - }, - "request": { - "$ref": "#/components/schemas/TransitionRequest" - }, - "changes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TransitionChange" - } - }, - "conflicts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TransitionConflict" - } - }, - "warnings": { - "type": "array", - "items": { - "type": "string" - } - }, - "commands": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Command" - } - } - }, - "required": [ - "base_fingerprint", - "request", - "changes", - "conflicts", - "warnings", - "commands" - ], - "additionalProperties": false - }, - "TransitionEnvelope": { - "type": "object", - "properties": { - "base_revision": { - "$ref": "#/components/schemas/Revision" - }, - "plan": { - "$ref": "#/components/schemas/TransitionPlan" - } - }, - "required": [ - "base_revision", - "plan" - ], - "additionalProperties": false - }, - "ControlKind": { - "type": "string", - "enum": [ - "Choice", - "Merge", - "Fork", - "Join", - "Loop" - ] - }, - "ScenarioViewpoint": { - "type": "string", - "enum": [ - "OperationalActivity", - "ParticipantInteraction", - "Functional", - "BoundaryInterface", - "MixedLegacy" - ] - }, - "MessageKind": { - "oneOf": [ - { - "type": "string", - "enum": [ - "Asynchronous", - "Call", - "Reply", - "Create", - "Delete" - ] - }, - { - "type": "object", - "properties": { - "Timer": { - "type": "object", - "properties": { - "expression": { - "type": "string" - } - }, - "required": [ - "expression" - ], - "additionalProperties": false - } - }, - "required": [ - "Timer" - ], - "additionalProperties": false - } - ] - }, - "MessageValue": { - "type": "object", - "properties": { - "path": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - }, - "default": [] - }, - "field": { - "type": "string", - "format": "uuid" - }, - "expression": { - "type": "string" - } - }, - "required": [ - "field", - "expression" - ], - "additionalProperties": false, - "description": "Identity is (path, field). Path lists enclosing DataField UUIDs, excluding the terminal field. Value storage traverses named Records; Reference storage stops traversal. Operation Call accepts In/InOut and Reply accepts Out/InOut/Return. Unspecified direction and retained ambiguous legacy empty paths warn. Expressions are descriptive, never executed." - }, - "LifelineBinding": { - "type": "object", - "properties": { - "referenced": { - "type": "string", - "format": "uuid" - }, - "local": { - "type": "string", - "format": "uuid" - } - }, - "required": [ - "referenced", - "local" - ], - "additionalProperties": false - }, - "StateKind": { - "type": "string", - "enum": [ - "State", - "Mode", - "Initial", - "Final", - "Choice", - "Junction", - "Fork", - "Join", - "ShallowHistory", - "DeepHistory", - "EntryPoint", - "ExitPoint", - "Terminate" - ] - }, - "TimeEventKind": { - "type": "string", - "enum": [ - "After", - "At" - ], - "description": "After is relative to source-state activation; At describes an absolute instant. Expressions are descriptive, not executed." - }, - "TransitionKind": { - "type": "string", - "enum": [ - "External", - "Local", - "Internal" - ] - }, - "EventBody": { - "oneOf": [ - { - "type": "object", - "properties": { - "Signal": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "uniqueItems": true, - "description": "Same-layer ExchangeItem identities carried by the signal." - } - }, - "required": [ - "items" - ], - "additionalProperties": false - } - }, - "required": [ - "Signal" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Time": { - "type": "object", - "properties": { - "kind": { - "$ref": "#/components/schemas/TimeEventKind" - }, - "expression": { - "type": "string" - } - }, - "required": [ - "kind", - "expression" - ], - "additionalProperties": false - } - }, - "required": [ - "Time" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Change": { - "type": "object", - "properties": { - "expression": { - "type": "string" - } - }, - "required": [ - "expression" - ], - "additionalProperties": false - } - }, - "required": [ - "Change" - ], - "additionalProperties": false - } - ] - }, - "PortContract": { - "oneOf": [ - { - "type": "object", - "properties": { - "FunctionInput": { - "type": "object", - "properties": { - "incoming_items": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "incoming_items" - ], - "additionalProperties": false - } - }, - "required": [ - "FunctionInput" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "FunctionOutput": { - "type": "object", - "properties": { - "outgoing_items": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - }, - "required": [ - "outgoing_items" - ], - "additionalProperties": false - } - }, - "required": [ - "FunctionOutput" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Component": { - "type": "object", - "properties": { - "kind": { - "type": "string", - "enum": [ - "Standard", - "Flow" - ] - }, - "orientation": { - "type": "string", - "enum": [ - "Unset", - "In", - "Out", - "InOut" - ] - } - }, - "required": [ - "kind", - "orientation" - ], - "additionalProperties": false - } - }, - "required": [ - "Component" - ], - "additionalProperties": false - }, - { - "type": "string", - "enum": [ - "Legacy" - ] - } - ], - "description": "FunctionInput/Output are SA Function-owned item contracts. Component is SA System/SystemActor-owned; Standard requires Unset. Legacy requires a preserved legacy record and emits compatibility warnings. Mixed Standard/Flow component connections are invalid." - }, - "LegacyPortContract": { - "type": "object", - "properties": { - "direction": { - "type": "string", - "enum": [ - "In", - "Out", - "InOut" - ] - }, - "interface": { - "anyOf": [ - { - "type": "string", - "format": "uuid" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "direction", - "interface" - ], - "additionalProperties": false - }, - "MigrationReport": { - "type": "object", - "properties": { - "from_schema": { - "type": "integer", - "enum": [ - 1, - 2, - 3, - 4, - 5, - 6 - ] - }, - "to_schema": { - "type": "integer", - "const": 7 - }, - "preserved_ports": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string" - }, - "required_resolution": { - "type": "string" - } - }, - "required": [ - "id", - "alias", - "required_resolution" - ], - "additionalProperties": false - } - }, - "generated_endpoints": { - "type": "array", - "items": { - "type": "object", - "properties": { - "exchange": { - "type": "string", - "format": "uuid" - }, - "side": { - "type": "string", - "enum": [ - "source", - "target" - ] - }, - "port": { - "type": "string", - "format": "uuid" - }, - "owner": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string" - } - }, - "required": [ - "exchange", - "side", - "port", - "owner", - "alias" - ], - "additionalProperties": false - } - }, - "orientation_defaults": { - "type": "array", - "items": { - "type": "object", - "properties": { - "exchange": { - "type": "string", - "format": "uuid" - }, - "oriented": { - "type": "boolean", - "const": true - } - }, - "required": [ - "exchange", - "oriented" - ], - "additionalProperties": false - } - }, - "required_resolutions": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - }, - "semantic_defaults": { - "type": "array", - "items": { - "type": "object", - "properties": { - "object": { - "type": "string", - "format": "uuid" - }, - "field": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "required": [ - "object", - "field", - "description" - ], - "additionalProperties": false - } - }, - "generated_interface_allocations": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string", - "format": "uuid" - }, - "source": { - "type": "string", - "format": "uuid" - }, - "target": { - "type": "string", - "format": "uuid" - }, - "alias": { - "type": "string" - }, - "order": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - } - }, - "required": [ - "id", - "source", - "target", - "alias", - "order" - ], - "additionalProperties": false - } - }, - "unresolved_message_values": { - "type": "array", - "items": { - "type": "object", - "properties": { - "occurrence": { - "type": "string", - "format": "uuid" - }, - "field": { - "type": "string", - "format": "uuid" - }, - "reason": { - "type": "string" - } - }, - "required": [ - "occurrence", - "field", - "reason" - ], - "additionalProperties": false - } - } - }, - "required": [ - "from_schema", - "to_schema", - "preserved_ports", - "generated_endpoints", - "orientation_defaults", - "required_resolutions" - ], - "additionalProperties": false, - "description": "Transient observations from explicit V1/V2/V3/V4/V5/V6 to V7 migration. New report lists default to empty when reading older responses. Reading an old envelope neither saves nor changes its revision. Allocation UUIDv5 uses project namespace and v2-interface-item::." - }, - "SourceBundle": { - "type": "object", - "description": "Complete source7 workspace (source3/4/5/6 migrate explicitly). The entry is a root filename. Files use canonical portable slash-separated relative .arcadia paths and all must be reachable. Includes resolve only against this map, never the service filesystem. Up to 256 files, 32 include edges along a path and 1,024 include edges overall. Serialized bundle JSON is at most 8 MiB, checked again after missing UUIDs are inserted. GET returns exact accepted stable text.", - "properties": { - "entry": { - "type": "string" - }, - "files": { - "type": "object", - "maxProperties": 256, - "additionalProperties": { - "type": "string" - } - } - }, - "required": [ - "entry", - "files" - ], - "additionalProperties": false - }, - "SourceSpan": { - "type": "object", - "description": "Half-open UTF-8 byte offsets [start,end) in the named source file.", - "properties": { - "start": { - "type": "integer", - "minimum": 0 - }, - "end": { - "type": "integer", - "minimum": 0 - } - }, - "required": [ - "start", - "end" - ], - "additionalProperties": false - }, - "SourceLocation": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "span": { - "$ref": "#/components/schemas/SourceSpan" - } - }, - "required": [ - "path", - "span" - ], - "additionalProperties": false - }, - "SourceProjectMutationResult": { - "type": "object", - "properties": { - "revision": { - "type": "integer", - "minimum": 1, - "description": "Current revision counter; use ETag as the opaque write validator." - }, - "changed": { - "type": "boolean" - }, - "issues": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Issue" - } - }, - "source_project": { - "$ref": "#/components/schemas/SourceBundle" - }, - "migration": { - "anyOf": [ - { - "$ref": "#/components/schemas/MigrationReport" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "revision", - "changed", - "issues", - "source_project", - "migration" - ], - "additionalProperties": false - }, - "ExchangeItemKind": { - "type": "string", - "enum": [ - "Information", - "Material", - "Energy" - ] - }, - "ExchangeMechanism": { - "type": "string", - "enum": [ - "Unspecified", - "Flow", - "Event", - "Operation", - "SharedData" - ] - }, - "ParameterDirection": { - "type": "string", - "enum": [ - "Unspecified", - "In", - "Out", - "InOut", - "Return" - ] - }, - "FieldStorage": { - "type": "string", - "enum": [ - "Value", - "Reference" - ] - }, - "TransmissionProtocol": { - "type": "string", - "enum": [ - "Unspecified", - "Synchronous", - "Asynchronous", - "Unicast", - "Multicast", - "Broadcast" - ] - }, - "AcquisitionProtocol": { - "type": "string", - "enum": [ - "Unspecified", - "Read", - "Accept" - ] - }, - "DataValue": { - "oneOf": [ - { - "type": "object", - "properties": { - "Boolean": { - "type": "boolean" - } - }, - "required": [ - "Boolean" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Integer": { - "type": "integer", - "format": "int64", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807 - } - }, - "required": [ - "Integer" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Real": { - "type": "number", - "format": "double", - "description": "Finite IEEE-754 binary64; no coercion from typed Integer values." - } - }, - "required": [ - "Real" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "String": { - "type": "string" - } - }, - "required": [ - "String" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Enumeration": { - "type": "string" - } - }, - "required": [ - "Enumeration" - ], - "additionalProperties": false - } - ] - }, - "DataConstructor": { - "type": "string", - "enum": [ - "Boolean", - "Integer", - "Real", - "String", - "Enumeration", - "Record" - ] - }, - "PortConstructor": { - "type": "string", - "enum": [ - "FunctionInput", - "FunctionOutput", - "Component", - "Legacy" - ] - }, - "OccurrenceConstructor": { - "type": "string", - "enum": [ - "Exchange", - "Action", - "Control", - "SequenceReference", - "ScenarioUse", - "State", - "Fragment", - "Branch" - ] - }, - "EventConstructor": { - "type": "string", - "enum": [ - "Signal", - "Time", - "Change" - ] - }, - "MessageConstructor": { - "type": "string", - "enum": [ - "Asynchronous", - "Call", - "Reply", - "Create", - "Delete", - "Timer" - ] - }, - "ReferenceCollection": { - "type": "string", - "enum": [ - "Steps", - "FragmentBranches", - "FunctionInputItems", - "FunctionOutputItems", - "SignalItems", - "ExchangeItems", - "CategoryExchanges", - "TransitionTriggers", - "TransitionEffects", - "Constrained" - ] - }, - "OrderedCollection": { - "type": "string", - "enum": [ - "Steps", - "FragmentBranches" - ] - }, - "IncidentEnd": { - "type": "string", - "enum": [ - "Source", - "Target" - ] - }, - "ArtifactFacet": { - "type": "string", - "enum": [ - "Presence", - "Name", - "Description", - "Owner", - "Relation", - "Uri", - "Revision", - "Fragment" - ] - }, - "ComponentPortKind": { - "type": "string", - "enum": [ - "Standard", - "Flow" - ] - }, - "ComponentPortOrientation": { - "type": "string", - "enum": [ - "Unset", - "In", - "Out", - "InOut" - ] - }, - "PortDirection": { - "type": "string", - "enum": [ - "In", - "Out", - "InOut" - ] - }, - "FragmentOperator": { - "type": "string", - "enum": [ - "Alternative", - "Parallel", - "Loop", - "Optional" - ] - }, - "StateActionKind": { - "type": "string", - "enum": [ - "Entry", - "Do", - "Exit" - ] - }, - "ArtifactRelation": { - "type": "string", - "enum": [ - "References", - "Satisfies", - "Verifies", - "Implements", - "DerivedFrom" - ] - }, - "HeaderFacet": { - "type": "string", - "enum": [ - "Name", - "Description", - "Parent" - ] - }, - "ReviewedString": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "string" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "string" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionId": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "HeaderReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Name": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Name" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Description": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Description" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Parent": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Parent" - ], - "additionalProperties": false - } - ] - }, - "ExchangeItemFacet": { - "type": "string", - "enum": [ - "Nature", - "Mechanism", - "Supertype" - ] - }, - "ReviewedExchangeItemKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ExchangeItemKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ExchangeItemKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedExchangeMechanism": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ExchangeMechanism" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ExchangeMechanism" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ExchangeItemReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Nature": { - "$ref": "#/components/schemas/ReviewedExchangeItemKind" - } - }, - "required": [ - "Nature" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Mechanism": { - "$ref": "#/components/schemas/ReviewedExchangeMechanism" - } - }, - "required": [ - "Mechanism" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Supertype": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Supertype" - ], - "additionalProperties": false - } - ] - }, - "InterfaceFacet": { - "type": "string", - "enum": [ - "Supertype" - ] - }, - "InterfaceReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Supertype": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Supertype" - ], - "additionalProperties": false - } - ] - }, - "DataFacet": { - "type": "string", - "enum": [ - "Constructor", - "IntegerMinimum", - "IntegerMaximum", - "RealMinimum", - "RealMaximum", - "StringMinimumLength", - "StringMaximumLength", - "RecordSupertype", - "Default", - "Unit" - ] - }, - "ReviewedDataConstructor": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/DataConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/DataConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptioni64": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "integer", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "integer", - "minimum": -9223372036854775808, - "maximum": 9223372036854775807 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionf64": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "Reviewedu32": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionu32": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "integer", - "minimum": 0, - "maximum": 4294967295 - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionDataValue": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/DataValue" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/DataValue" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "DataReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Constructor": { - "$ref": "#/components/schemas/ReviewedDataConstructor" - } - }, - "required": [ - "Constructor" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "IntegerMinimum": { - "$ref": "#/components/schemas/ReviewedOptioni64" - } - }, - "required": [ - "IntegerMinimum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "IntegerMaximum": { - "$ref": "#/components/schemas/ReviewedOptioni64" - } - }, - "required": [ - "IntegerMaximum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RealMinimum": { - "$ref": "#/components/schemas/ReviewedOptionf64" - } - }, - "required": [ - "RealMinimum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RealMaximum": { - "$ref": "#/components/schemas/ReviewedOptionf64" - } - }, - "required": [ - "RealMaximum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "StringMinimumLength": { - "$ref": "#/components/schemas/Reviewedu32" - } - }, - "required": [ - "StringMinimumLength" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "StringMaximumLength": { - "$ref": "#/components/schemas/ReviewedOptionu32" - } - }, - "required": [ - "StringMaximumLength" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "RecordSupertype": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "RecordSupertype" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Default": { - "$ref": "#/components/schemas/ReviewedOptionDataValue" - } - }, - "required": [ - "Default" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Unit": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Unit" - ], - "additionalProperties": false - } - ] - }, - "FieldFacet": { - "type": "string", - "enum": [ - "Type", - "Minimum", - "Maximum", - "Unit", - "Direction", - "Storage", - "Default" - ] - }, - "ReviewedTypeRef": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TypeRef" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TypeRef" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedParameterDirection": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ParameterDirection" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ParameterDirection" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedFieldStorage": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/FieldStorage" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/FieldStorage" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionVecDataValue": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/DataValue" - } - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "array", - "items": { - "$ref": "#/components/schemas/DataValue" - } - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "FieldReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Type": { - "$ref": "#/components/schemas/ReviewedTypeRef" - } - }, - "required": [ - "Type" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Minimum": { - "$ref": "#/components/schemas/Reviewedu32" - } - }, - "required": [ - "Minimum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Maximum": { - "$ref": "#/components/schemas/ReviewedOptionu32" - } - }, - "required": [ - "Maximum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Unit": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Unit" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Direction": { - "$ref": "#/components/schemas/ReviewedParameterDirection" - } - }, - "required": [ - "Direction" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Storage": { - "$ref": "#/components/schemas/ReviewedFieldStorage" - } - }, - "required": [ - "Storage" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Default": { - "$ref": "#/components/schemas/ReviewedOptionVecDataValue" - } - }, - "required": [ - "Default" - ], - "additionalProperties": false - } - ] - }, - "PortFacet": { - "type": "string", - "enum": [ - "Constructor", - "ComponentKind", - "Orientation", - "LegacyDirection", - "LegacyInterface" - ] - }, - "ReviewedPortConstructor": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/PortConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/PortConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedComponentPortKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ComponentPortKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ComponentPortKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedComponentPortOrientation": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ComponentPortOrientation" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ComponentPortOrientation" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedPortDirection": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/PortDirection" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/PortDirection" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "PortReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Constructor": { - "$ref": "#/components/schemas/ReviewedPortConstructor" - } - }, - "required": [ - "Constructor" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ComponentKind": { - "$ref": "#/components/schemas/ReviewedComponentPortKind" - } - }, - "required": [ - "ComponentKind" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Orientation": { - "$ref": "#/components/schemas/ReviewedComponentPortOrientation" - } - }, - "required": [ - "Orientation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "LegacyDirection": { - "$ref": "#/components/schemas/ReviewedPortDirection" - } - }, - "required": [ - "LegacyDirection" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "LegacyInterface": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "LegacyInterface" - ], - "additionalProperties": false - } - ] - }, - "ScenarioFacet": { - "type": "string", - "enum": [ - "Viewpoint", - "Context", - "Precondition", - "Postcondition" - ] - }, - "ReviewedScenarioViewpoint": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ScenarioViewpoint" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ScenarioViewpoint" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ScenarioReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Viewpoint": { - "$ref": "#/components/schemas/ReviewedScenarioViewpoint" - } - }, - "required": [ - "Viewpoint" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Context": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Context" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Precondition": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Precondition" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Postcondition": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Postcondition" - ], - "additionalProperties": false - } - ] - }, - "LifelineFacet": { - "type": "string", - "enum": [ - "Represented" - ] - }, - "LifelineReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Represented": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Represented" - ], - "additionalProperties": false - } - ] - }, - "OccurrenceFacet": { - "type": "string", - "enum": [ - "Constructor", - "Precondition", - "Postcondition", - "Exchange", - "Sender", - "Receiver", - "MessageKind", - "TimerExpression", - "ReplyTo", - "Operation", - "Action", - "ActionLifeline", - "ControlKind", - "Sequence", - "Scenario", - "State", - "StateLifeline", - "FragmentOperator", - "BranchGuard" - ] - }, - "ReviewedOccurrenceConstructor": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/OccurrenceConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/OccurrenceConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedMessageConstructor": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/MessageConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/MessageConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedControlKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ControlKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ControlKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedFragmentOperator": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/FragmentOperator" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/FragmentOperator" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "OccurrenceReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Constructor": { - "$ref": "#/components/schemas/ReviewedOccurrenceConstructor" - } - }, - "required": [ - "Constructor" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Precondition": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Precondition" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Postcondition": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Postcondition" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Exchange": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Exchange" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Sender": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Sender" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Receiver": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Receiver" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "MessageKind": { - "$ref": "#/components/schemas/ReviewedMessageConstructor" - } - }, - "required": [ - "MessageKind" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TimerExpression": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "TimerExpression" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ReplyTo": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "ReplyTo" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Operation": { - "$ref": "#/components/schemas/ReviewedOptionOperationUse" - } - }, - "required": [ - "Operation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Action": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Action" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ActionLifeline": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "ActionLifeline" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ControlKind": { - "$ref": "#/components/schemas/ReviewedControlKind" - } - }, - "required": [ - "ControlKind" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Sequence": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Sequence" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Scenario": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "Scenario" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "State": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "State" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "StateLifeline": { - "$ref": "#/components/schemas/ReviewedOptionId" - } - }, - "required": [ - "StateLifeline" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "FragmentOperator": { - "$ref": "#/components/schemas/ReviewedFragmentOperator" - } - }, - "required": [ - "FragmentOperator" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "BranchGuard": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "BranchGuard" - ], - "additionalProperties": false - } - ] - }, - "EventFacet": { - "type": "string", - "enum": [ - "Constructor", - "TimeKind", - "TimeExpression", - "ChangeExpression" - ] - }, - "ReviewedEventConstructor": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/EventConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/EventConstructor" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedTimeEventKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TimeEventKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TimeEventKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "EventReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Constructor": { - "$ref": "#/components/schemas/ReviewedEventConstructor" - } - }, - "required": [ - "Constructor" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TimeKind": { - "$ref": "#/components/schemas/ReviewedTimeEventKind" - } - }, - "required": [ - "TimeKind" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TimeExpression": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "TimeExpression" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ChangeExpression": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "ChangeExpression" - ], - "additionalProperties": false - } - ] - }, - "StateFacet": { - "type": "string", - "enum": [ - "Kind", - "Entry", - "Exit", - "DoActivity" - ] - }, - "ReviewedStateKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/StateKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/StateKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "StateReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Kind": { - "$ref": "#/components/schemas/ReviewedStateKind" - } - }, - "required": [ - "Kind" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Entry": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Entry" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Exit": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Exit" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "DoActivity": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "DoActivity" - ], - "additionalProperties": false - } - ] - }, - "ConstraintFacet": { - "type": "string", - "enum": [ - "Expression" - ] - }, - "ConstraintReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Expression": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Expression" - ], - "additionalProperties": false - } - ] - }, - "RelationFacet": { - "type": "string", - "enum": [ - "Source", - "Target", - "AllocationOrder", - "Transmission", - "Acquisition", - "SourceRole", - "TargetRole", - "SourceMinimum", - "SourceMaximum", - "TargetMinimum", - "TargetMaximum", - "SourceNavigable", - "TargetNavigable", - "SourcePort", - "TargetPort", - "Oriented", - "ExtendCondition", - "SequenceGuard", - "TransitionKind", - "Trigger", - "Guard", - "Effect", - "StateActionPhase", - "ContextOwner", - "ContextSource", - "ContextTarget", - "ContextPurpose" - ] - }, - "ReviewedId": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedTransmissionProtocol": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TransmissionProtocol" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TransmissionProtocol" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedAcquisitionProtocol": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/AcquisitionProtocol" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/AcquisitionProtocol" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "Reviewedbool": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "boolean" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "boolean" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedTransitionKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TransitionKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/TransitionKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedStateActionKind": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/StateActionKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/StateActionKind" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "RelationReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Source": { - "$ref": "#/components/schemas/ReviewedId" - } - }, - "required": [ - "Source" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Target": { - "$ref": "#/components/schemas/ReviewedId" - } - }, - "required": [ - "Target" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "AllocationOrder": { - "$ref": "#/components/schemas/Reviewedu32" - } - }, - "required": [ - "AllocationOrder" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Transmission": { - "$ref": "#/components/schemas/ReviewedTransmissionProtocol" - } - }, - "required": [ - "Transmission" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Acquisition": { - "$ref": "#/components/schemas/ReviewedAcquisitionProtocol" - } - }, - "required": [ - "Acquisition" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SourceRole": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "SourceRole" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TargetRole": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "TargetRole" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SourceMinimum": { - "$ref": "#/components/schemas/Reviewedu32" - } - }, - "required": [ - "SourceMinimum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SourceMaximum": { - "$ref": "#/components/schemas/ReviewedOptionu32" - } - }, - "required": [ - "SourceMaximum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TargetMinimum": { - "$ref": "#/components/schemas/Reviewedu32" - } - }, - "required": [ - "TargetMinimum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TargetMaximum": { - "$ref": "#/components/schemas/ReviewedOptionu32" - } - }, - "required": [ - "TargetMaximum" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SourceNavigable": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "SourceNavigable" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TargetNavigable": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "TargetNavigable" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SourcePort": { - "$ref": "#/components/schemas/ReviewedId" - } - }, - "required": [ - "SourcePort" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TargetPort": { - "$ref": "#/components/schemas/ReviewedId" - } - }, - "required": [ - "TargetPort" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Oriented": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "Oriented" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ExtendCondition": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "ExtendCondition" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "SequenceGuard": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "SequenceGuard" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "TransitionKind": { - "$ref": "#/components/schemas/ReviewedTransitionKind" - } - }, - "required": [ - "TransitionKind" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Trigger": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Trigger" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Guard": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Guard" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Effect": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Effect" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "StateActionPhase": { - "$ref": "#/components/schemas/ReviewedStateActionKind" - } - }, - "required": [ - "StateActionPhase" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ContextOwner": { - "$ref": "#/components/schemas/ReviewedId" - } - }, - "required": [ - "ContextOwner" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ContextSource": { - "$ref": "#/components/schemas/ReviewedOccurrenceEndpoint" - } - }, - "required": [ - "ContextSource" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ContextTarget": { - "$ref": "#/components/schemas/ReviewedOccurrenceEndpoint" - } - }, - "required": [ - "ContextTarget" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ContextPurpose": { - "$ref": "#/components/schemas/ReviewedContextualLinkPurpose" - } - }, - "required": [ - "ContextPurpose" - ], - "additionalProperties": false - } - ] - }, - "AnnotationKey": { - "type": "object", - "properties": { - "path": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - }, - "field": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "path", - "field" - ], - "additionalProperties": false - }, - "KeyPairId": { - "type": "object", - "properties": { - "oa": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - }, - "sa": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - } - }, - "required": [], - "additionalProperties": false - }, - "KeyPairString": { - "type": "object", - "properties": { - "oa": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "sa": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [], - "additionalProperties": false - }, - "KeyPairAnnotationKey": { - "type": "object", - "properties": { - "oa": { - "anyOf": [ - { - "$ref": "#/components/schemas/AnnotationKey" - }, - { - "type": "null" - } - ] - }, - "sa": { - "anyOf": [ - { - "$ref": "#/components/schemas/AnnotationKey" - }, - { - "type": "null" - } - ] - } - }, - "required": [], - "additionalProperties": false - }, - "ReviewedArtifactRelation": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ArtifactRelation" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ArtifactRelation" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionString": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ArtifactReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Presence": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "Presence" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Name": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Name" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Description": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Description" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Owner": { - "$ref": "#/components/schemas/ReviewedId" - } - }, - "required": [ - "Owner" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Relation": { - "$ref": "#/components/schemas/ReviewedArtifactRelation" - } - }, - "required": [ - "Relation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Uri": { - "$ref": "#/components/schemas/ReviewedString" - } - }, - "required": [ - "Uri" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Revision": { - "$ref": "#/components/schemas/ReviewedOptionString" - } - }, - "required": [ - "Revision" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Fragment": { - "$ref": "#/components/schemas/ReviewedOptionString" - } - }, - "required": [ - "Fragment" - ], - "additionalProperties": false - } - ] - }, - "ReviewedVecId": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionMessageValue": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/MessageValue" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/MessageValue" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOptionLifelineBinding": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/LifelineBinding" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/LifelineBinding" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "FacetSelector": { - "oneOf": [ - { - "type": "object", - "properties": { - "Header": { - "$ref": "#/components/schemas/HeaderFacet" - } - }, - "required": [ - "Header" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ExchangeItem": { - "$ref": "#/components/schemas/ExchangeItemFacet" - } - }, - "required": [ - "ExchangeItem" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Category": { - "$ref": "#/components/schemas/CategoryFacet" - } - }, - "required": [ - "Category" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Interface": { - "$ref": "#/components/schemas/InterfaceFacet" - } - }, - "required": [ - "Interface" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Data": { - "$ref": "#/components/schemas/DataFacet" - } - }, - "required": [ - "Data" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Field": { - "$ref": "#/components/schemas/FieldFacet" - } - }, - "required": [ - "Field" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Port": { - "$ref": "#/components/schemas/PortFacet" - } - }, - "required": [ - "Port" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Scenario": { - "$ref": "#/components/schemas/ScenarioFacet" - } - }, - "required": [ - "Scenario" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Lifeline": { - "$ref": "#/components/schemas/LifelineFacet" - } - }, - "required": [ - "Lifeline" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Occurrence": { - "$ref": "#/components/schemas/OccurrenceFacet" - } - }, - "required": [ - "Occurrence" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Event": { - "$ref": "#/components/schemas/EventFacet" - } - }, - "required": [ - "Event" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "State": { - "$ref": "#/components/schemas/StateFacet" - } - }, - "required": [ - "State" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Constraint": { - "$ref": "#/components/schemas/ConstraintFacet" - } - }, - "required": [ - "Constraint" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Relation": { - "$ref": "#/components/schemas/RelationFacet" - } - }, - "required": [ - "Relation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Member": { - "type": "object", - "properties": { - "collection": { - "$ref": "#/components/schemas/ReferenceCollection" - }, - "keys": { - "$ref": "#/components/schemas/KeyPairId" - } - }, - "required": [ - "collection", - "keys" - ], - "additionalProperties": false - } - }, - "required": [ - "Member" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Order": { - "$ref": "#/components/schemas/OrderedCollection" - } - }, - "required": [ - "Order" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Literal": { - "$ref": "#/components/schemas/KeyPairString" - } - }, - "required": [ - "Literal" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Annotation": { - "$ref": "#/components/schemas/KeyPairAnnotationKey" - } - }, - "required": [ - "Annotation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Binding": { - "$ref": "#/components/schemas/KeyPairId" - } - }, - "required": [ - "Binding" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Child": { - "$ref": "#/components/schemas/KeyPairId" - } - }, - "required": [ - "Child" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Incident": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "end": { - "$ref": "#/components/schemas/IncidentEnd" - } - }, - "required": [ - "keys", - "end" - ], - "additionalProperties": false - } - }, - "required": [ - "Incident" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Artifact": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "facet": { - "$ref": "#/components/schemas/ArtifactFacet" - } - }, - "required": [ - "keys", - "facet" - ], - "additionalProperties": false - } - }, - "required": [ - "Artifact" - ], - "additionalProperties": false - } - ] - }, - "FacetReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Header": { - "$ref": "#/components/schemas/HeaderReview" - } - }, - "required": [ - "Header" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "ExchangeItem": { - "$ref": "#/components/schemas/ExchangeItemReview" - } - }, - "required": [ - "ExchangeItem" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Category": { - "$ref": "#/components/schemas/CategoryReview" - } - }, - "required": [ - "Category" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Interface": { - "$ref": "#/components/schemas/InterfaceReview" - } - }, - "required": [ - "Interface" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Data": { - "$ref": "#/components/schemas/DataReview" - } - }, - "required": [ - "Data" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Field": { - "$ref": "#/components/schemas/FieldReview" - } - }, - "required": [ - "Field" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Port": { - "$ref": "#/components/schemas/PortReview" - } - }, - "required": [ - "Port" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Scenario": { - "$ref": "#/components/schemas/ScenarioReview" - } - }, - "required": [ - "Scenario" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Lifeline": { - "$ref": "#/components/schemas/LifelineReview" - } - }, - "required": [ - "Lifeline" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Occurrence": { - "$ref": "#/components/schemas/OccurrenceReview" - } - }, - "required": [ - "Occurrence" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Event": { - "$ref": "#/components/schemas/EventReview" - } - }, - "required": [ - "Event" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "State": { - "$ref": "#/components/schemas/StateReview" - } - }, - "required": [ - "State" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Constraint": { - "$ref": "#/components/schemas/ConstraintReview" - } - }, - "required": [ - "Constraint" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Relation": { - "$ref": "#/components/schemas/RelationReview" - } - }, - "required": [ - "Relation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Member": { - "type": "object", - "properties": { - "collection": { - "$ref": "#/components/schemas/ReferenceCollection" - }, - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "review": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "collection", - "keys", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Member" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Order": { - "type": "object", - "properties": { - "collection": { - "$ref": "#/components/schemas/OrderedCollection" - }, - "review": { - "$ref": "#/components/schemas/ReviewedVecId" - } - }, - "required": [ - "collection", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Order" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Literal": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairString" - }, - "review": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "keys", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Literal" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Annotation": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairAnnotationKey" - }, - "review": { - "$ref": "#/components/schemas/ReviewedOptionMessageValue" - } - }, - "required": [ - "keys", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Annotation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Binding": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "review": { - "$ref": "#/components/schemas/ReviewedOptionLifelineBinding" - } - }, - "required": [ - "keys", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Binding" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Child": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "review": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "keys", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Child" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Incident": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "end": { - "$ref": "#/components/schemas/IncidentEnd" - }, - "review": { - "$ref": "#/components/schemas/Reviewedbool" - } - }, - "required": [ - "keys", - "end", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Incident" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Artifact": { - "type": "object", - "properties": { - "keys": { - "$ref": "#/components/schemas/KeyPairId" - }, - "review": { - "$ref": "#/components/schemas/ArtifactReview" - } - }, - "required": [ - "keys", - "review" - ], - "additionalProperties": false - } - }, - "required": [ - "Artifact" - ], - "additionalProperties": false - } - ] - }, - "SemanticBaseline": { - "type": "object", - "properties": { - "facets": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetReview" - } - } - }, - "required": [ - "facets" - ], - "additionalProperties": false - }, - "Drift": { - "type": "string", - "enum": [ - "Unreviewed", - "Unchanged", - "OaChanged", - "SaChanged", - "BothChanged" - ] - }, - "FacetDrift": { - "type": "object", - "properties": { - "selector": { - "$ref": "#/components/schemas/FacetSelector" - }, - "current": { - "$ref": "#/components/schemas/FacetReview" - }, - "drift": { - "$ref": "#/components/schemas/Drift" - } - }, - "required": [ - "selector", - "current", - "drift" - ], - "additionalProperties": false - }, - "SemanticDrift": { - "type": "object", - "properties": { - "own": { - "$ref": "#/components/schemas/Drift" - }, - "linked": { - "$ref": "#/components/schemas/Drift" - }, - "own_unreviewed": { - "type": "boolean" - }, - "linked_unreviewed": { - "type": "boolean" - } - }, - "required": [ - "own", - "linked", - "own_unreviewed", - "linked_unreviewed" - ], - "additionalProperties": false - }, - "LinkedSemanticImpact": { - "type": "object", - "properties": { - "objects": { - "$ref": "#/components/schemas/KeyPairId" - }, - "traces": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - }, - "drift": { - "$ref": "#/components/schemas/Drift" - }, - "unreviewed": { - "type": "boolean" - } - }, - "required": [ - "objects", - "traces", - "drift", - "unreviewed" - ], - "additionalProperties": false - }, - "SemanticRecord": { - "oneOf": [ - { - "type": "object", - "properties": { - "Element": { - "$ref": "#/components/schemas/Element" - } - }, - "required": [ - "Element" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Relation": { - "$ref": "#/components/schemas/Relation" - } - }, - "required": [ - "Relation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Artifact": { - "$ref": "#/components/schemas/ArtifactLink" - } - }, - "required": [ - "Artifact" - ], - "additionalProperties": false - } - ] - }, - "ImpactRecord": { - "oneOf": [ - { - "type": "object", - "properties": { - "Element": { - "$ref": "#/components/schemas/Element" - } - }, - "required": [ - "Element" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Relation": { - "$ref": "#/components/schemas/Relation" - } - }, - "required": [ - "Relation" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Artifact": { - "$ref": "#/components/schemas/ArtifactLink" - } - }, - "required": [ - "Artifact" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Diagram": { - "$ref": "#/components/schemas/Diagram" - } - }, - "required": [ - "Diagram" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Trace": { - "$ref": "#/components/schemas/Trace" - } - }, - "required": [ - "Trace" - ], - "additionalProperties": false - } - ] - }, - "SemanticImpact": { - "type": "object", - "properties": { - "object": { - "$ref": "#/components/schemas/Id" - }, - "before": { - "anyOf": [ - { - "$ref": "#/components/schemas/ImpactRecord" - }, - { - "type": "null" - } - ] - }, - "after": { - "anyOf": [ - { - "$ref": "#/components/schemas/ImpactRecord" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "object" - ], - "additionalProperties": false - }, - "SemanticDecision": { - "type": "string", - "enum": [ - "KeepTarget", - "TakeSource", - "Candidate" - ] - }, - "FacetResolution": { - "type": "object", - "properties": { - "facet": { - "$ref": "#/components/schemas/FacetSelector" - }, - "decision": { - "$ref": "#/components/schemas/SemanticDecision" - } - }, - "required": [ - "facet", - "decision" - ], - "additionalProperties": false - }, - "SemanticReviewRequest": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "resolutions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetResolution" - }, - "maxItems": 5000 - }, - "reset": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetSelector" - }, - "maxItems": 5000, - "default": [] - } - }, - "required": [ - "source", - "resolutions" - ], - "additionalProperties": false - }, - "SemanticTransitionRequest": { - "type": "object", - "properties": { - "transition": { - "$ref": "#/components/schemas/TransitionRequest" - }, - "traces": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Id" - }, - "maxProperties": 100, - "default": {} - }, - "references": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Id" - }, - "maxProperties": 5000, - "default": {} - }, - "reviews": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SemanticReviewRequest" - }, - "maxItems": 100, - "default": [] - }, - "candidates": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SemanticRecord" - }, - "maxItems": 200, - "default": [] - }, - "accepted_impacts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SemanticImpact" - }, - "maxItems": 200, - "default": [] - } - }, - "required": [ - "transition" - ], - "additionalProperties": false, - "description": "Canonical OA-to-SA references include historical UUIDs. Traces map selected source IDs to trace IDs. Combined resolutions/reset limit: 5,000. Candidates must equal exactly the result of their selected facets; accepted_impacts explicitly acknowledge exact induced changes." - }, - "SemanticReviewPreview": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "target": { - "$ref": "#/components/schemas/Id" - }, - "facets": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetDrift" - } - }, - "acknowledged": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetSelector" - } - }, - "retired": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetSelector" - } - }, - "linked_impacts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LinkedSemanticImpact" - } - } - }, - "required": [ - "source", - "target", - "facets", - "acknowledged", - "retired", - "linked_impacts" - ], - "additionalProperties": false - }, - "SemanticTransitionPlan": { - "type": "object", - "properties": { - "base_fingerprint": { - "type": "string" - }, - "request": { - "$ref": "#/components/schemas/SemanticTransitionRequest" - }, - "transition": { - "$ref": "#/components/schemas/TransitionPlan" - }, - "reviews": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SemanticReviewPreview" - } - }, - "impacts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SemanticImpact" - } - }, - "conflicts": { - "type": "array", - "items": { - "type": "string" - } - }, - "commands": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Command" - }, - "maxItems": 200 - }, - "prepared_fingerprint": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "base_fingerprint", - "request", - "transition", - "reviews", - "impacts", - "conflicts", - "commands" - ], - "additionalProperties": false - }, - "SemanticTransitionEnvelope": { - "type": "object", - "properties": { - "base_revision": { - "$ref": "#/components/schemas/Revision" - }, - "plan": { - "$ref": "#/components/schemas/SemanticTransitionPlan" - } - }, - "required": [ - "base_revision", - "plan" - ], - "additionalProperties": false - }, - "SemanticQueryRequest": { - "type": "object", - "properties": { - "traces": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "maxItems": 100, - "minItems": 1, - "uniqueItems": true - }, - "references": { - "type": "object", - "propertyNames": { - "format": "uuid" - }, - "additionalProperties": { - "$ref": "#/components/schemas/Id" - }, - "maxProperties": 5000, - "default": {} - }, - "revision": { - "anyOf": [ - { - "$ref": "#/components/schemas/Revision" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "traces" - ], - "additionalProperties": false - }, - "SemanticQueryItem": { - "type": "object", - "properties": { - "trace": { - "$ref": "#/components/schemas/Id" - }, - "oa": { - "$ref": "#/components/schemas/Id" - }, - "sa": { - "$ref": "#/components/schemas/Id" - }, - "facets": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FacetDrift" - } - }, - "drift": { - "$ref": "#/components/schemas/SemanticDrift" - }, - "linked_impacts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/LinkedSemanticImpact" - } - } - }, - "required": [ - "trace", - "oa", - "sa", - "facets", - "drift", - "linked_impacts" - ], - "additionalProperties": false - }, - "SemanticQueryResult": { - "type": "object", - "properties": { - "revision": { - "$ref": "#/components/schemas/Revision" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SemanticQueryItem" - }, - "maxItems": 100 - } - }, - "required": [ - "revision", - "items" - ], - "additionalProperties": false - }, - "ExchangeFamily": { - "type": "string", - "enum": [ - "Work", - "Participant" - ] - }, - "Coverage": { - "type": "string", - "enum": [ - "All", - "InBehavior", - "OutsideBehavior" - ] - }, - "ExchangeLabels": { - "type": "string", - "enum": [ - "Name", - "Items", - "NameAndItems" - ] - }, - "CategoryFacet": { - "type": "string", - "enum": [ - "Family" - ] - }, - "ReviewedExchangeFamily": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ExchangeFamily" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ExchangeFamily" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "CategoryReview": { - "oneOf": [ - { - "type": "object", - "properties": { - "Family": { - "$ref": "#/components/schemas/ReviewedExchangeFamily" - } - }, - "required": [ - "Family" - ], - "additionalProperties": false - } - ] - }, - "EdgeKind": { - "oneOf": [ - { - "type": "string", - "enum": [ - "Trace" - ] - }, - { - "type": "object", - "properties": { - "Relation": { - "$ref": "#/components/schemas/RelationKind" - } - }, - "required": [ - "Relation" - ], - "additionalProperties": false - } - ] - }, - "RelationKinds": { - "oneOf": [ - { - "type": "string", - "enum": [ - "All" - ] - }, - { - "type": "object", - "properties": { - "Only": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EdgeKind" - } - } - }, - "required": [ - "Only" - ], - "additionalProperties": false - } - ] - }, - "CategorySelection": { - "oneOf": [ - { - "type": "string", - "enum": [ - "All" - ] - }, - { - "type": "object", - "properties": { - "Any": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - } - }, - "required": [ - "Any" - ], - "additionalProperties": false - } - ] - }, - "ContextSelection": { - "oneOf": [ - { - "type": "string", - "enum": [ - "All" - ] - }, - { - "type": "object", - "properties": { - "Any": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - } - } - }, - "required": [ - "Any" - ], - "additionalProperties": false - } - ] - }, - "ViewOptions": { - "type": "object", - "properties": { - "hidden_edges": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "default": [] - }, - "relation_kinds": { - "$ref": "#/components/schemas/RelationKinds", - "default": "All" - }, - "work_categories": { - "$ref": "#/components/schemas/CategorySelection", - "default": "All" - }, - "participant_categories": { - "$ref": "#/components/schemas/CategorySelection", - "default": "All" - }, - "coverage": { - "$ref": "#/components/schemas/Coverage", - "default": "All" - }, - "work_contexts": { - "$ref": "#/components/schemas/ContextSelection", - "default": "All" - }, - "exchange_labels": { - "$ref": "#/components/schemas/ExchangeLabels", - "default": "Name" - } - }, - "required": [], - "additionalProperties": false, - "description": "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. Traceability permits both-layer hidden/category/context references; ordinary projections require the diagram layer. Filtering preserves unfiltered automatic port positions." - }, - "CommunicationPortChoice": { - "oneOf": [ - { - "type": "object", - "properties": { - "Reuse": { - "type": "object", - "properties": { - "port": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "port" - ], - "additionalProperties": false - } - }, - "required": [ - "Reuse" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Create": { - "type": "object", - "properties": { - "id": { - "$ref": "#/components/schemas/Id" - }, - "alias": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "kind": { - "$ref": "#/components/schemas/ComponentPortKind" - }, - "orientation": { - "$ref": "#/components/schemas/ComponentPortOrientation" - } - }, - "required": [ - "id", - "alias", - "name", - "description", - "kind", - "orientation" - ], - "additionalProperties": false - } - }, - "required": [ - "Create" - ], - "additionalProperties": false - } - ] - }, - "CommunicationEndpointDecision": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "source_port": { - "$ref": "#/components/schemas/CommunicationPortChoice" - }, - "target_port": { - "$ref": "#/components/schemas/CommunicationPortChoice" - } - }, - "required": [ - "source", - "source_port", - "target_port" - ], - "additionalProperties": false - }, - "ReviewedOptionOperationUse": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/OperationUse" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "anyOf": [ - { - "$ref": "#/components/schemas/OperationUse" - }, - { - "type": "null" - } - ] - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedOccurrenceEndpoint": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/OccurrenceEndpoint" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/OccurrenceEndpoint" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "ReviewedContextualLinkPurpose": { - "type": "object", - "properties": { - "oa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ContextualLinkPurpose" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - }, - "sa": { - "oneOf": [ - { - "type": "string", - "enum": [ - "NotApplicable" - ] - }, - { - "type": "object", - "properties": { - "Value": { - "$ref": "#/components/schemas/ContextualLinkPurpose" - } - }, - "required": [ - "Value" - ], - "additionalProperties": false - } - ] - } - }, - "required": [ - "oa", - "sa" - ], - "additionalProperties": false - }, - "OccurrenceEndpoint": { - "type": "object", - "properties": { - "references": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Id" - }, - "maxItems": 64 - }, - "occurrence": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "references", - "occurrence" - ], - "additionalProperties": false, - "description": "Ordered reference-occurrence IDs in an explicit caller owner, followed by the leaf contribution ID. Empty references means a direct contribution; sequence definition IDs are not path components." - }, - "ContextualLinkPurpose": { - "oneOf": [ - { - "type": "object", - "properties": { - "Precedence": { - "type": "object", - "properties": { - "guard": { - "type": "string" - } - }, - "required": [ - "guard" - ], - "additionalProperties": false - } - }, - "required": [ - "Precedence" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "Exchange": { - "type": "object", - "properties": { - "exchange": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "exchange" - ], - "additionalProperties": false - } - }, - "required": [ - "Exchange" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "CommonWork": { - "type": "object", - "properties": { - "work": { - "$ref": "#/components/schemas/Id" - } - }, - "required": [ - "work" - ], - "additionalProperties": false - } - }, - "required": [ - "CommonWork" - ], - "additionalProperties": false - } - ] - }, - "OperationUse": { - "type": "object", - "properties": { - "allocation": { - "anyOf": [ - { - "$ref": "#/components/schemas/Id" - }, - { - "type": "null" - } - ] - } - }, - "required": [], - "additionalProperties": false, - "description": "Present with absent/null allocation retains unresolved operation intent. A selected UUID identifies exactly an InterfaceItemAllocation, never an item, port, or name. Reply derives operation from its exact correlated Call." - }, - "OperationOmission": { - "type": "object", - "properties": { - "source": { - "$ref": "#/components/schemas/Id" - }, - "rationale": { - "type": "string" - } - }, - "required": [ - "source", - "rationale" - ], - "additionalProperties": false - } - } - } -} +{ + "openapi": "3.1.0", + "info": { + "title": "Arcadia Studio model API", + "version": "1.0.0", + "description": "Current schema-7 snapshots and conditional atomic writes. Request body limit: 8 MiB; at most 200 nested commands; no retained revisions, request receipts, automatic retries or persistent transition plans. See docs/api.rst. Fixed store capacity: 128 models / 256 MiB serialized envelopes. Stateless transition preview/apply. Artifact URI/revision strings remain opaque core fields. Model admission before structural validation: 5,000 graph objects, 20,000 total records and 50,000 ordered references. Preparation uses snapshots outside the catalog lock; final commits recheck the exact ETag. Each accepted envelope couples model and authored source under one revision. Source-only edits advance the shared ETag. Source bundles resolve logical paths only, preserve authored files/comments, and have a stabilized JSON cap of 8 MiB, 256 files, 32 include edges along a path and 1,024 include edges overall." + }, + "servers": [ + { + "url": "http://127.0.0.1:8787" + } + ], + "security": [ + { + "bearerAuth": [] + } + ], + "paths": { + "/health": { + "get": { + "operationId": "_health", + "summary": "Store was opened and HTTP service is responding", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "const": "ok" + } + }, + "required": [ + "status" + ], + "additionalProperties": false + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "security": [] + } + }, + "/health/live": { + "get": { + "operationId": "_health_live", + "summary": "Store was opened and HTTP service is responding", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "const": "ok" + } + }, + "required": [ + "status" + ], + "additionalProperties": false + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "security": [] + } + }, + "/health/ready": { + "get": { + "operationId": "_health_ready", + "summary": "Store was opened and HTTP service is responding", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "const": "ok" + } + }, + "required": [ + "status" + ], + "additionalProperties": false + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "security": [] + } + }, + "/openapi.json": { + "get": { + "operationId": "_openapi_json", + "summary": "OpenAPI contract", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "security": [] + } + }, + "/api/v1/openapi.json": { + "get": { + "operationId": "_api_v1_openapi_json", + "summary": "OpenAPI contract", + "parameters": [], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "security": [] + } + }, + "/api/v1/projects": { + "get": { + "operationId": "listProjects", + "summary": "List current hosted projects in UUID order", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "q", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectPage" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + }, + "post": { + "operationId": "createProject", + "summary": "Create and persist an empty model", + "parameters": [], + "responses": { + "201": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSummary" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NameRequest" + } + } + } + } + } + }, + "/api/v1/projects/import": { + "post": { + "operationId": "importProject", + "summary": "Import JSON or UTF-8 Arcadia source as a new hosted resource", + "parameters": [], + "responses": { + "201": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSummary" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + }, + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "description": "Schema-7 native JSON is described below. Schema-1/2/3/4/5/6 JSON and source are accepted through explicit frozen readers and migration, also on replacement and candidate validation. Earlier versions reject later constructors, fields and semantic facets, including explicit null. Prior prose and typed reviews remain intact; absent semantic review stays Unreviewed. Export emits schema 7 / arcadia 7. See docs/language.rst." + } + }, + "/api/v1/projects/{id}": { + "get": { + "operationId": "getProject", + "summary": "Get complete current envelope", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-None-Match", + "in": "header", + "required": false, + "description": "Return 304 when the current representation's revision matches.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StoredProject" + } + } + } + }, + "304": { + "description": "Unchanged; empty body." + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + }, + "patch": { + "operationId": "renameProject", + "summary": "Rename the project atomically", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MutationResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NameRequest" + } + } + } + } + }, + "delete": { + "operationId": "deleteProject", + "summary": "Permanently delete the current hosted envelope", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + } + }, + "/api/v1/projects/{id}/model": { + "get": { + "operationId": "getModel", + "summary": "Export native model JSON", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-None-Match", + "in": "header", + "required": false, + "description": "Return 304 when the current representation's revision matches.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "304": { + "description": "Unchanged; empty body." + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + }, + "put": { + "operationId": "replaceModel", + "summary": "Conditionally replace model JSON", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MutationResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Project" + } + } + } + }, + "description": "Replace semantics while preserving the accepted authored source layout and comments, using common UUIDs as identity correspondence. Model and source commit together. Use source-project PUT to replace authored files." + } + }, + "/api/v1/projects/{id}/source": { + "get": { + "operationId": "getSource", + "summary": "Export canonical Arcadia source", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-None-Match", + "in": "header", + "required": false, + "description": "Return 304 when the current representation's revision matches.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "304": { + "description": "Unchanged; empty body." + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "description": "Canonical single-file interchange view of the current model. Use source-project GET to export exact authored files and comments." + }, + "put": { + "operationId": "replaceSource", + "summary": "Conditionally replace from UTF-8 Arcadia source", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MutationResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "description": "Replace semantics from canonical single-file interchange while preserving the existing authored source bundle using common UUIDs. Submitted formatting/comments do not replace the authored workspace. Use source-project PUT for that purpose." + } + }, + "/api/v1/projects/{id}/elements": { + "get": { + "operationId": "listElements", + "summary": "Filter current elements; AND semantics", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "revision", + "in": "query", + "required": false, + "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", + "schema": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + } + }, + { + "name": "kind", + "in": "query", + "required": false, + "description": "", + "schema": { + "$ref": "#/components/schemas/Kind" + } + }, + { + "name": "layer", + "in": "query", + "required": false, + "description": "", + "schema": { + "$ref": "#/components/schemas/Layer" + } + }, + { + "name": "alias", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + }, + { + "name": "q", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ElementPage" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + } + }, + "/api/v1/projects/{id}/relations": { + "get": { + "operationId": "listRelations", + "summary": "Filter relations by typed kind and endpoints", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "revision", + "in": "query", + "required": false, + "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", + "schema": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + } + }, + { + "name": "kind", + "in": "query", + "required": false, + "description": "", + "schema": { + "$ref": "#/components/schemas/RelationKind" + } + }, + { + "name": "source", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "target", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "incident_to", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelationPage" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + } + }, + "/api/v1/projects/{id}/artifact-links": { + "get": { + "operationId": "listArtifacts", + "summary": "List model artifact links without fetching URIs", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "revision", + "in": "query", + "required": false, + "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", + "schema": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + } + }, + { + "name": "object", + "in": "query", + "required": false, + "description": "Owner ID.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ArtifactPage" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + } + }, + "/api/v1/projects/{id}/traces": { + "get": { + "operationId": "listTraces", + "summary": "List OA/SA traces", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 100 + } + }, + { + "name": "after", + "in": "query", + "required": false, + "description": "", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "revision", + "in": "query", + "required": false, + "description": "For project lists, pin current revision across pages; 409 if it changed. No historical snapshots.", + "schema": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + } + }, + { + "name": "object", + "in": "query", + "required": false, + "description": "OA or SA endpoint ID.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TracePage" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + } + }, + "/api/v1/projects/{id}/query": { + "post": { + "operationId": "queryNeighborhood", + "summary": "Bounded graph neighborhood including relation endpoint closure", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NeighborhoodResult" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NeighborhoodRequest" + } + } + } + } + } + }, + "/api/v1/projects/{id}/validation": { + "get": { + "operationId": "validateCurrent", + "summary": "Shared core validation of current snapshot", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationResult" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + } + }, + "post": { + "operationId": "validateCandidate", + "summary": "Check submitted model/source without mutation", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationResult" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationRequest" + } + } + } + }, + "description": "Validate native model JSON, canonical Arcadia source, or a complete source-project bundle without adoption. Source-project diagnostics refer to submitted file paths and positions. Stabilized source/model byte limits are also checked." + } + }, + "/api/v1/projects/{id}/transactions": { + "post": { + "operationId": "executeTransaction", + "summary": "Atomic shared typed commands; optional dry run", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionRequest" + } + } + } + }, + "description": "Command identity correspondence drives source writeback, including UUID swaps and delete/recreate operations even when final models compare equal. Dry runs return both proposed representations and validate writeback. Failure preserves the accepted pair." + } + }, + "/api/v1/projects/{id}/transitions/preview": { + "post": { + "operationId": "previewTransition", + "summary": "Preview a shared core transition without persisting a plan", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Validated result; preview may include unresolved conflicts.", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransitionEnvelope" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransitionRequest" + } + } + } + }, + "description": "Conflict-free previews also validate authored-source writeback and stabilized model/source capacities." + } + }, + "/api/v1/projects/{id}/transitions/apply": { + "post": { + "operationId": "applyTransition", + "summary": "Apply client-held transition plan against its original revision", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Validated result; preview may include unresolved conflicts.", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MutationResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransitionEnvelope" + } + } + } + } + } + }, + "/api/v1/projects/import-source-project": { + "post": { + "operationId": "importSourceProject", + "summary": "Import an authored source bundle as a new hosted resource", + "parameters": [], + "responses": { + "201": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + }, + "Location": { + "schema": { + "type": "string" + }, + "description": "Path to the created hosted resource." + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProjectSummary" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SourceBundle" + } + } + } + }, + "description": "Compile all logical source files, stabilize missing IDs, and persist the accepted model and bundle together. Importing source3 bundles never reads the service filesystem. Export the accepted bundle after import to retain inserted IDs." + } + }, + "/api/v1/projects/{id}/source-project": { + "get": { + "operationId": "getSourceProject", + "summary": "Export the exact accepted authored source bundle", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-None-Match", + "in": "header", + "required": false, + "description": "Return 304 when the current representation's revision matches.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SourceBundle" + } + } + } + }, + "304": { + "description": "Unchanged; empty body." + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "description": "Returns entry and all files with stable IDs, original comments, line endings and module layout. Shares the model ETag; comment-only changes advance that revision." + }, + "put": { + "operationId": "replaceSourceProject", + "summary": "Conditionally replace the complete authored source bundle", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "headers": { + "ETag": { + "schema": { + "type": "string" + }, + "description": "Project revision validator when scoped to a project." + }, + "X-Request-ID": { + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SourceProjectMutationResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SourceBundle" + } + } + } + }, + "description": "Compile and validate the full submitted bundle; return accepted text with missing UUIDs inserted. Model and source persist together under the exact If-Match precondition. Comment-only or file-layout edits advance revision; an exact pair no-op retains it. Rejected candidates preserve both representations." + } + }, + "/api/v1/projects/{id}/semantic-transitions/query": { + "post": { + "operationId": "querySemanticTransition", + "summary": "Query typed trace drift and linked impacts without mutation", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Validated result; preview may include unresolved conflicts.", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SemanticQueryResult" + } + } + } + }, + "default": { + "$ref": "#/components/responses/Problem" + }, + "409": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SemanticQueryRequest" + } + } + } + }, + "description": "Read one immutable snapshot. Optional revision mismatch returns409; missing trace returns404; duplicate/empty trace selection returns400. References are canonical OA-to-SA correspondence. No write, baseline refresh or external artifact fetch." + } + }, + "/api/v1/projects/{id}/semantic-transitions/preview": { + "post": { + "operationId": "previewSemanticTransition", + "summary": "Preview and source-validate a typed semantic transition without writing", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Validated result; preview may include unresolved conflicts.", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SemanticTransitionEnvelope" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SemanticTransitionRequest" + } + } + } + }, + "description": "Uses the frozen core typed semantic contract. A supplied plan is a client-owned candidate, not a signed receipt; fingerprints check consistency, not original review authorship. Preview validates source writeback when conflict-free. Apply preserves preview-generated IDs and does not replan random identities." + } + }, + "/api/v1/projects/{id}/semantic-transitions/apply": { + "post": { + "operationId": "applySemanticTransition", + "summary": "Conditionally apply a prepared semantic candidate in one model/source revision", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Hosted resource UUID; distinct from editable model ID.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "If-Match", + "in": "header", + "required": true, + "description": "One exact strong project ETag. Missing: 428; stale: 412. Weak/list/wildcard unsupported.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Validated result; preview may include unresolved conflicts.", + "headers": { + "ETag": { + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MutationResult" + } + } + } + }, + "412": { + "$ref": "#/components/responses/Problem" + }, + "428": { + "$ref": "#/components/responses/Problem" + }, + "default": { + "$ref": "#/components/responses/Problem" + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SemanticTransitionEnvelope" + } + } + } + }, + "description": "Uses the frozen core typed semantic contract. A supplied plan is a client-owned candidate, not a signed receipt; fingerprints check consistency, not original review authorship. Preview validates source writeback when conflict-free. Apply preserves preview-generated IDs and does not replan random identities." + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "Optional for unconfigured loopback; required on non-loopback." + } + }, + "responses": { + "Problem": { + "description": "Structured problem response. See code/detail and docs/api.rst status mapping.", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/Problem" + } + } + } + } + }, + "schemas": { + "Id": { + "type": "string", + "format": "uuid" + }, + "Revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "Layer": { + "type": "string", + "enum": [ + "Oa", + "Sa" + ] + }, + "Kind": { + "type": "string", + "enum": [ + "Entity", + "Actor", + "Role", + "Activity", + "Capability", + "OperationalMission", + "Process", + "System", + "SystemActor", + "Function", + "SystemCapability", + "Mission", + "FunctionalChain", + "ExchangeItem", + "ExchangeCategory", + "DataType", + "DataField", + "Interface", + "Port", + "Scenario", + "Lifeline", + "Occurrence", + "StateMachine", + "Region", + "State", + "Mode", + "Initial", + "Final", + "Choice", + "Junction", + "Fork", + "Join", + "ShallowHistory", + "DeepHistory", + "EntryPoint", + "ExitPoint", + "Terminate", + "Event", + "Constraint" + ] + }, + "RelationKind": { + "type": "string", + "enum": [ + "InterfaceItemAllocation", + "DataAssociation", + "Allocation", + "PortAllocation", + "Interaction", + "CapabilityInvolvement", + "ChainInvolvement", + "RoleRealization", + "RoleAssignment", + "FunctionalExchange", + "BoundaryExchange", + "CommunicationMean", + "MissionCapability", + "CapabilityInclude", + "CapabilityExtend", + "CapabilityGeneralization", + "MissionInvolvement", + "SequenceLink", + "ContextualLink", + "StateTransition", + "Availability", + "ExchangeAllocation", + "InterfaceProvision", + "InterfaceRequirement", + "StateAction" + ] + }, + "Position": { + "type": "object", + "properties": { + "x": { + "type": "number", + "minimum": -1000000, + "maximum": 1000000 + }, + "y": { + "type": "number", + "minimum": -1000000, + "maximum": 1000000 + } + }, + "required": [ + "x", + "y" + ], + "additionalProperties": false + }, + "EdgeLabel": { + "type": "object", + "description": "Authored label placement relative to the computed polyline. Both fields are required.", + "properties": { + "fraction": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Fraction of source-to-target polyline arc length." + }, + "offset": { + "$ref": "#/components/schemas/Position", + "description": "Relative diagram-space displacement." + } + }, + "required": [ + "fraction", + "offset" + ], + "additionalProperties": false + }, + "EdgeRoute": { + "type": "object", + "description": "One diagram's authored route for an existing Relation or Trace, including currently hidden edges. No separate identity. Only pinned points are stored; explicit empty routes are retained.", + "properties": { + "edge": { + "type": "string", + "format": "uuid", + "description": "Existing Relation or Trace identity, never a node placement." + }, + "waypoints": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Position" + }, + "maxItems": 128, + "default": [], + "description": "Absolute diagram positions in traversal order." + }, + "label": { + "oneOf": [ + { + "$ref": "#/components/schemas/EdgeLabel" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "edge" + ], + "additionalProperties": false + }, + "Size": { + "type": "object", + "properties": { + "width": { + "type": "number", + "exclusiveMinimum": 0 + }, + "height": { + "type": "number", + "exclusiveMinimum": 0 + } + }, + "required": [ + "width", + "height" + ], + "additionalProperties": false + }, + "DataTypeDefinition": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Boolean" + ] + }, + { + "type": "object", + "properties": { + "Integer": { + "type": "object", + "properties": { + "minimum": { + "anyOf": [ + { + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + }, + { + "type": "null" + } + ] + }, + "maximum": { + "anyOf": [ + { + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "minimum", + "maximum" + ], + "additionalProperties": false + } + }, + "required": [ + "Integer" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Real": { + "type": "object", + "properties": { + "minimum": { + "anyOf": [ + { + "type": "number", + "format": "double", + "description": "Finite IEEE-754 binary64; no coercion from typed Integer values." + }, + { + "type": "null" + } + ] + }, + "maximum": { + "anyOf": [ + { + "type": "number", + "format": "double", + "description": "Finite IEEE-754 binary64; no coercion from typed Integer values." + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "minimum", + "maximum" + ], + "additionalProperties": false + } + }, + "required": [ + "Real" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "String": { + "type": "object", + "properties": { + "minimum_length": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "maximum_length": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "minimum_length", + "maximum_length" + ], + "additionalProperties": false + } + }, + "required": [ + "String" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Enumeration": { + "type": "object", + "properties": { + "literals": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "literals" + ], + "additionalProperties": false + } + }, + "required": [ + "Enumeration" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Record": { + "type": "object", + "properties": { + "supertype": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "supertype" + ], + "additionalProperties": false + } + }, + "required": [ + "Record" + ], + "additionalProperties": false + } + ] + }, + "TypeRef": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Boolean", + "Integer", + "Real", + "String" + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "Named": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "Named" + ], + "additionalProperties": false + } + ] + } + ] + }, + "OccurrenceBody": { + "oneOf": [ + { + "type": "object", + "properties": { + "Exchange": { + "type": "object", + "properties": { + "exchange": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "sender": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "receiver": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "message_kind": { + "$ref": "#/components/schemas/MessageKind", + "default": "Asynchronous" + }, + "reply_to": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "default": null + }, + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MessageValue" + }, + "default": [] + }, + "operation": { + "anyOf": [ + { + "$ref": "#/components/schemas/OperationUse" + }, + { + "type": "null" + } + ], + "default": null + } + }, + "required": [ + "exchange", + "sender", + "receiver" + ], + "additionalProperties": false, + "description": "Absent/null operation retains ordinary channel semantics and is omitted on output. Present operation is allowed only on SA BoundaryInterface Call/Asynchronous messages. Reply stores no operation and derives its exact Call's selection. Existing message defaults remain unchanged." + } + }, + "required": [ + "Exchange" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Action": { + "type": "object", + "properties": { + "action": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "lifeline": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "action", + "lifeline" + ], + "additionalProperties": false + } + }, + "required": [ + "Action" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "State": { + "type": "object", + "properties": { + "state": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "lifeline": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "state", + "lifeline" + ], + "additionalProperties": false + } + }, + "required": [ + "State" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Fragment": { + "type": "object", + "properties": { + "operator": { + "type": "string", + "enum": [ + "Alternative", + "Parallel", + "Loop", + "Optional" + ] + }, + "branches": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "operator", + "branches" + ], + "additionalProperties": false + } + }, + "required": [ + "Fragment" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Branch": { + "type": "object", + "properties": { + "guard": { + "type": "string" + }, + "occurrences": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "guard", + "occurrences" + ], + "additionalProperties": false + } + }, + "required": [ + "Branch" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Control": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/components/schemas/ControlKind" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + } + }, + "required": [ + "Control" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SequenceReference": { + "type": "object", + "properties": { + "sequence": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "sequence" + ], + "additionalProperties": false + } + }, + "required": [ + "SequenceReference" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ScenarioUse": { + "type": "object", + "properties": { + "scenario": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "bindings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LifelineBinding" + } + } + }, + "required": [ + "scenario", + "bindings" + ], + "additionalProperties": false + } + }, + "required": [ + "ScenarioUse" + ], + "additionalProperties": false + } + ] + }, + "SharedBody": { + "oneOf": [ + { + "type": "string", + "enum": [ + "StateMachine", + "Region" + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "ExchangeItem": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/components/schemas/ExchangeItemKind" + }, + "mechanism": { + "$ref": "#/components/schemas/ExchangeMechanism" + }, + "supertype": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "kind", + "mechanism", + "supertype" + ], + "additionalProperties": false + } + }, + "required": [ + "ExchangeItem" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "DataType": { + "type": "object", + "properties": { + "definition": { + "$ref": "#/components/schemas/DataTypeDefinition" + }, + "default": { + "anyOf": [ + { + "$ref": "#/components/schemas/DataValue" + }, + { + "type": "null" + } + ] + }, + "unit": { + "type": "string" + } + }, + "required": [ + "definition", + "default", + "unit" + ], + "additionalProperties": false + } + }, + "required": [ + "DataType" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "DataField": { + "type": "object", + "properties": { + "type_ref": { + "$ref": "#/components/schemas/TypeRef" + }, + "minimum": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "maximum": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "null" + } + ] + }, + "unit": { + "type": "string" + }, + "direction": { + "$ref": "#/components/schemas/ParameterDirection" + }, + "storage": { + "$ref": "#/components/schemas/FieldStorage" + }, + "default": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataValue" + } + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "type_ref", + "minimum", + "maximum", + "unit", + "direction", + "storage", + "default" + ], + "additionalProperties": false + } + }, + "required": [ + "DataField" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Interface": { + "type": "object", + "properties": { + "supertype": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "supertype" + ], + "additionalProperties": false + } + }, + "required": [ + "Interface" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Port": { + "type": "object", + "properties": { + "contract": { + "$ref": "#/components/schemas/PortContract" + }, + "legacy": { + "anyOf": [ + { + "$ref": "#/components/schemas/LegacyPortContract" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "contract", + "legacy" + ], + "additionalProperties": false + } + }, + "required": [ + "Port" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Scenario": { + "type": "object", + "properties": { + "context": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "precondition": { + "type": "string" + }, + "postcondition": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "viewpoint": { + "$ref": "#/components/schemas/ScenarioViewpoint", + "default": "MixedLegacy", + "description": "Omitted schema-2 viewpoint retains legacy mixed lifelines. New graphical factories select OperationalActivity or Functional explicitly." + } + }, + "required": [ + "context", + "precondition", + "postcondition", + "steps" + ], + "additionalProperties": false + } + }, + "required": [ + "Scenario" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Lifeline": { + "type": "object", + "properties": { + "represented": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "represented" + ], + "additionalProperties": false + } + }, + "required": [ + "Lifeline" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Occurrence": { + "type": "object", + "properties": { + "body": { + "$ref": "#/components/schemas/OccurrenceBody" + }, + "precondition": { + "type": "string" + }, + "postcondition": { + "type": "string" + } + }, + "required": [ + "body", + "precondition", + "postcondition" + ], + "additionalProperties": false + } + }, + "required": [ + "Occurrence" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "State": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/components/schemas/StateKind" + }, + "entry": { + "type": "string" + }, + "exit": { + "type": "string" + }, + "do_activity": { + "type": "string" + } + }, + "required": [ + "kind", + "entry", + "exit", + "do_activity" + ], + "additionalProperties": false + } + }, + "required": [ + "State" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Constraint": { + "type": "object", + "properties": { + "expression": { + "type": "string" + }, + "constrained": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "expression", + "constrained" + ], + "additionalProperties": false + } + }, + "required": [ + "Constraint" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Event": { + "type": "object", + "properties": { + "body": { + "$ref": "#/components/schemas/EventBody" + } + }, + "required": [ + "body" + ], + "additionalProperties": false + } + }, + "required": [ + "Event" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ExchangeCategory": { + "type": "object", + "properties": { + "family": { + "$ref": "#/components/schemas/ExchangeFamily" + }, + "exchanges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + }, + "required": [ + "family", + "exchanges" + ], + "additionalProperties": false + } + }, + "required": [ + "ExchangeCategory" + ], + "additionalProperties": false + } + ] + } + ] + }, + "OaBody": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Entity", + "Actor", + "Role", + "Activity", + "Capability", + "Mission" + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "Process": { + "type": "object", + "properties": { + "steps": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "steps" + ], + "additionalProperties": false + } + }, + "required": [ + "Process" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Shared": { + "$ref": "#/components/schemas/SharedBody" + } + }, + "required": [ + "Shared" + ], + "additionalProperties": false + } + ] + } + ] + }, + "SaBody": { + "oneOf": [ + { + "type": "string", + "enum": [ + "System", + "Actor", + "Function", + "Capability", + "Mission" + ] + }, + { + "oneOf": [ + { + "type": "object", + "properties": { + "FunctionalChain": { + "type": "object", + "properties": { + "steps": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "steps" + ], + "additionalProperties": false + } + }, + "required": [ + "FunctionalChain" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Shared": { + "$ref": "#/components/schemas/SharedBody" + } + }, + "required": [ + "Shared" + ], + "additionalProperties": false + } + ] + } + ] + }, + "ElementBody": { + "oneOf": [ + { + "type": "object", + "properties": { + "Oa": { + "$ref": "#/components/schemas/OaBody" + } + }, + "required": [ + "Oa" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Sa": { + "$ref": "#/components/schemas/SaBody" + } + }, + "required": [ + "Sa" + ], + "additionalProperties": false + } + ] + }, + "Element": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "parent": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "body": { + "$ref": "#/components/schemas/ElementBody" + } + }, + "required": [ + "id", + "alias", + "name", + "description", + "parent", + "body" + ], + "additionalProperties": false + }, + "RelationBody": { + "oneOf": [ + { + "type": "object", + "properties": { + "Allocation": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "Allocation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CapabilityInvolvement": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "CapabilityInvolvement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ChainInvolvement": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "ChainInvolvement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RoleRealization": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "RoleRealization" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RoleAssignment": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "RoleAssignment" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "MissionCapability": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "MissionCapability" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Availability": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "Availability" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ExchangeAllocation": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "ExchangeAllocation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "InterfaceProvision": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "InterfaceProvision" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "InterfaceRequirement": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "InterfaceRequirement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Interaction": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "items": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "source", + "target", + "items" + ], + "additionalProperties": false + } + }, + "required": [ + "Interaction" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "BoundaryExchange": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "items": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "oriented": { + "type": "boolean", + "description": "Independent of component port kind/orientation. Old V2 exchanges migrate to true." + } + }, + "required": [ + "source", + "target", + "items", + "oriented" + ], + "additionalProperties": false + } + }, + "required": [ + "BoundaryExchange" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "FunctionalExchange": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "source_port": { + "type": "string", + "format": "uuid" + }, + "target_port": { + "type": "string", + "format": "uuid" + }, + "items": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "source", + "target", + "source_port", + "target_port", + "items" + ], + "additionalProperties": false + } + }, + "required": [ + "FunctionalExchange" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "StateTransition": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "trigger": { + "type": "string" + }, + "guard": { + "type": "string" + }, + "effect": { + "type": "string" + }, + "transition_kind": { + "$ref": "#/components/schemas/TransitionKind", + "default": "External", + "description": "Omitted legacy schema-2 value is External." + }, + "triggers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "uniqueItems": true, + "description": "Same-layer Event or Activity/Function references; alternatives, not implicit conjunction.", + "default": [] + }, + "effects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "uniqueItems": true, + "description": "Same-layer Activity/Function references describing transition effects.", + "default": [] + } + }, + "required": [ + "source", + "target", + "trigger", + "guard", + "effect" + ], + "additionalProperties": false, + "description": "Existing trigger, guard and effect prose is preserved exactly. New typed properties are optional only for staged schema-2 compatibility. Removing the last typed trigger retains a descriptive unresolved-trigger placeholder instead of silently introducing completion semantics." + } + }, + "required": [ + "StateTransition" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "StateAction": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "phase": { + "type": "string", + "enum": [ + "Entry", + "Do", + "Exit" + ] + } + }, + "required": [ + "source", + "target", + "phase" + ], + "additionalProperties": false + } + }, + "required": [ + "StateAction" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CapabilityInclude": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "CapabilityInclude" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CapabilityExtend": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "condition": { + "type": "string", + "description": "Engineering condition under which the extending capability adds behavior." + } + }, + "required": [ + "source", + "target", + "condition" + ], + "additionalProperties": false + } + }, + "required": [ + "CapabilityExtend" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CapabilityGeneralization": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "CapabilityGeneralization" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "MissionInvolvement": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "MissionInvolvement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SequenceLink": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "guard": { + "type": "string", + "description": "Condition for a contextual process/chain path. Choice and loop paths should use explicit conditions or else." + } + }, + "required": [ + "source", + "target", + "guard" + ], + "additionalProperties": false + } + }, + "required": [ + "SequenceLink" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "PortAllocation": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "source", + "target" + ], + "additionalProperties": false + } + }, + "required": [ + "PortAllocation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "InterfaceItemAllocation": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "order": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "transmission": { + "$ref": "#/components/schemas/TransmissionProtocol" + }, + "acquisition": { + "$ref": "#/components/schemas/AcquisitionProtocol" + } + }, + "required": [ + "source", + "target", + "order", + "transmission", + "acquisition" + ], + "additionalProperties": false + } + }, + "required": [ + "InterfaceItemAllocation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "DataAssociation": { + "type": "object", + "properties": { + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "source_role": { + "type": "string" + }, + "target_role": { + "type": "string" + }, + "source_minimum": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "source_maximum": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "null" + } + ] + }, + "target_minimum": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "target_maximum": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "null" + } + ] + }, + "source_navigable": { + "type": "boolean" + }, + "target_navigable": { + "type": "boolean" + } + }, + "required": [ + "source", + "target", + "source_role", + "target_role", + "source_minimum", + "source_maximum", + "target_minimum", + "target_maximum", + "source_navigable", + "target_navigable" + ], + "additionalProperties": false + } + }, + "required": [ + "DataAssociation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CommunicationMean": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "target": { + "$ref": "#/components/schemas/Id" + }, + "oriented": { + "type": "boolean" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + }, + "required": [ + "source", + "target", + "oriented", + "items" + ], + "additionalProperties": false + } + }, + "required": [ + "CommunicationMean" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ContextualLink": { + "type": "object", + "properties": { + "owner": { + "$ref": "#/components/schemas/Id" + }, + "source": { + "$ref": "#/components/schemas/OccurrenceEndpoint" + }, + "target": { + "$ref": "#/components/schemas/OccurrenceEndpoint" + }, + "purpose": { + "$ref": "#/components/schemas/ContextualLinkPurpose" + } + }, + "required": [ + "owner", + "source", + "target", + "purpose" + ], + "additionalProperties": false + } + }, + "required": [ + "ContextualLink" + ], + "additionalProperties": false + } + ] + }, + "Relation": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/components/schemas/RelationBody" + } + }, + "required": [ + "id", + "alias", + "name", + "description", + "body" + ], + "additionalProperties": false + }, + "PortAnchorSide": { + "type": "string", + "enum": [ + "Left", + "Right", + "Top", + "Bottom" + ], + "description": "Presentation side of the owner placement; independent of semantic port direction." + }, + "PortAnchor": { + "type": "object", + "description": "Manual position of a direct child Port on one owner placement. Each port occurs at most once per placement; this record has no separate identity.", + "properties": { + "port": { + "type": "string", + "format": "uuid" + }, + "side": { + "$ref": "#/components/schemas/PortAnchorSide" + }, + "offset": { + "type": "number", + "minimum": 0, + "maximum": 1, + "description": "Finite binary32 fraction of the complete owner edge, measured from top on Left/Right and from left on Top/Bottom." + } + }, + "required": [ + "port", + "side", + "offset" + ], + "additionalProperties": false + }, + "NodePlacement": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "element": { + "type": "string", + "format": "uuid" + }, + "position": { + "$ref": "#/components/schemas/Position" + }, + "size": { + "$ref": "#/components/schemas/Size" + }, + "collapsed": { + "type": "boolean" + }, + "port_anchors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PortAnchor" + }, + "default": [], + "description": "Per-placement manual port positions. Omitted and empty both select automatic positioning; empty lists are omitted on serialization. Duplicate port references are invalid." + } + }, + "required": [ + "id", + "alias", + "element", + "position", + "size", + "collapsed" + ], + "additionalProperties": false + }, + "ProjectionKind": { + "description": "Existing projections remain strings. StateMachine carries one required UUID reference to an existing StateMachine in the diagram layer (state_diagram_context). Its placements may contain only that machine and its regions/state vertices, including nested members (state_diagram_member). Empty and partial views are valid. Incompatible placements must be removed or relocated in the same transaction when changing context; accepting or reading a view does not synthesize semantic records or placements.", + "oneOf": [ + { + "type": "string", + "enum": [ + "FunctionalDataflow", + "Capabilities", + "Overview", + "Decomposition", + "Allocation", + "Traceability" + ] + }, + { + "type": "object", + "properties": { + "StateMachine": { + "type": "object", + "properties": { + "machine": { + "type": "string", + "format": "uuid", + "description": "Required identity of the same-layer StateMachine that owns this view's members." + } + }, + "required": [ + "machine" + ], + "additionalProperties": false + } + }, + "required": [ + "StateMachine" + ], + "additionalProperties": false + } + ], + "examples": [ + "Overview", + { + "StateMachine": { + "machine": "11111111-1111-4111-8111-111111111111" + } + } + ] + }, + "Diagram": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "layer": { + "$ref": "#/components/schemas/Layer" + }, + "projection": { + "$ref": "#/components/schemas/ProjectionKind" + }, + "nodes": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/NodePlacement" + } + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EdgeRoute" + }, + "default": [], + "description": "Unique by edge identity within this diagram. Stored order is retained. Each entry counts against the total model record admission limit. Missing routes in earlier schema-3 models default to empty." + }, + "view_options": { + "$ref": "#/components/schemas/ViewOptions", + "default": {} + } + }, + "required": [ + "id", + "alias", + "name", + "description", + "layer", + "projection", + "nodes" + ], + "additionalProperties": false + }, + "ArtifactLink": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "owner": { + "type": "string", + "format": "uuid" + }, + "relation": { + "type": "string", + "enum": [ + "References", + "Satisfies", + "Verifies", + "Implements", + "DerivedFrom" + ] + }, + "uri": { + "type": "string", + "description": "Opaque external artifact reference. Server never fetches or executes it." + }, + "revision": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "fragment": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "alias", + "name", + "description", + "owner", + "relation", + "uri", + "revision", + "fragment" + ], + "additionalProperties": false + }, + "TracedProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name", + "description" + ], + "additionalProperties": false + }, + "TraceBaseline": { + "type": "object", + "properties": { + "oa": { + "$ref": "#/components/schemas/TracedProperties" + }, + "sa": { + "$ref": "#/components/schemas/TracedProperties" + }, + "semantic": { + "anyOf": [ + { + "$ref": "#/components/schemas/SemanticBaseline" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Absent/null means Unreviewed. Historical typed facts never become live graph references." + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "Trace": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "oa": { + "type": "string", + "format": "uuid" + }, + "sa": { + "type": "string", + "format": "uuid" + }, + "kind": { + "type": "string", + "enum": [ + "Refinement", + "Participant", + "Capability", + "Mission", + "Exchange", + "Sequence", + "Item", + "Scenario", + "State", + "Manual" + ] + }, + "baseline": { + "anyOf": [ + { + "$ref": "#/components/schemas/TraceBaseline" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "alias", + "name", + "description", + "oa", + "sa", + "kind", + "baseline" + ], + "additionalProperties": false + }, + "Project": { + "type": "object", + "properties": { + "schema_version": { + "type": "integer", + "const": 7 + }, + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "elements": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Element" + } + }, + "relations": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Relation" + } + }, + "diagrams": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Diagram" + } + }, + "artifact_links": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/ArtifactLink" + } + }, + "traces": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Trace" + } + } + }, + "required": [ + "schema_version", + "id", + "alias", + "name", + "description", + "elements", + "relations", + "diagrams", + "artifact_links", + "traces" + ], + "additionalProperties": false + }, + "StoredProject": { + "type": "object", + "properties": { + "resource_id": { + "type": "string", + "format": "uuid" + }, + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "model": { + "$ref": "#/components/schemas/Project" + }, + "migration": { + "anyOf": [ + { + "$ref": "#/components/schemas/MigrationReport" + }, + { + "type": "null" + } + ], + "description": "Present after legacy import/reopen; excluded from persisted envelopes. Resource identity and revision are unchanged by reopening." + }, + "source_project": { + "$ref": "#/components/schemas/SourceBundle" + } + }, + "required": [ + "resource_id", + "revision", + "model", + "source_project" + ], + "additionalProperties": false, + "description": "Accepted coupled model/source snapshot. Compiling source_project reproduces model; revision changes once if either representation changes. Older model-only or schema3 coupled envelopes migrate in memory without a write or revision change; both representations must independently migrate to the same graph." + }, + "ProjectSummary": { + "type": "object", + "properties": { + "resource_id": { + "type": "string", + "format": "uuid" + }, + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "model_id": { + "type": "string", + "format": "uuid" + }, + "schema_version": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "elements": { + "type": "integer", + "minimum": 0 + }, + "relations": { + "type": "integer", + "minimum": 0 + }, + "migration": { + "anyOf": [ + { + "$ref": "#/components/schemas/MigrationReport" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "resource_id", + "revision", + "model_id", + "schema_version", + "name", + "elements", + "relations" + ], + "additionalProperties": false + }, + "Issue": { + "type": "object", + "properties": { + "severity": { + "type": "string", + "enum": [ + "error", + "warning" + ] + }, + "message": { + "type": "string" + }, + "object_id": { + "type": "string", + "format": "uuid" + }, + "line": { + "type": "integer", + "minimum": 1 + }, + "column": { + "type": "integer", + "minimum": 1, + "description": "One-based Unicode scalar column." + }, + "code": { + "type": "string" + }, + "field": { + "type": "string" + }, + "path": { + "type": "string", + "description": "Logical source file path, when a source project supplied the diagnostic." + }, + "span": { + "$ref": "#/components/schemas/SourceSpan" + }, + "related": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SourceLocation" + } + } + }, + "required": [ + "severity", + "message", + "code" + ], + "additionalProperties": false + }, + "Problem": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "title": { + "type": "string" + }, + "status": { + "type": "integer" + }, + "code": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "current_revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Issue" + } + } + }, + "required": [ + "type", + "title", + "status", + "code", + "detail" + ], + "additionalProperties": false + }, + "NameRequest": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + }, + "Command": { + "oneOf": [ + { + "type": "object", + "properties": { + "Replace": { + "$ref": "#/components/schemas/Project" + } + }, + "required": [ + "Replace" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Batch": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + } + } + }, + "required": [ + "Batch" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Add": { + "$ref": "#/components/schemas/Kind" + } + }, + "required": [ + "Add" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "AddElement": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/components/schemas/Kind" + }, + "layer": { + "$ref": "#/components/schemas/Layer" + }, + "name": { + "type": "string" + }, + "parent": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "kind", + "layer", + "name", + "parent" + ], + "additionalProperties": false + } + }, + "required": [ + "AddElement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Connect": { + "type": "array", + "prefixItems": [ + { + "$ref": "#/components/schemas/RelationKind" + }, + { + "type": "string" + }, + { + "type": "string", + "format": "uuid" + }, + { + "type": "string", + "format": "uuid" + } + ], + "minItems": 4, + "maxItems": 4 + } + }, + "required": [ + "Connect" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditProject": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Unique persistent alias; core reserved words are forbidden." + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "id", + "alias", + "name", + "description" + ], + "additionalProperties": false + } + }, + "required": [ + "EditProject" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Reparent": { + "type": "object", + "properties": { + "element": { + "type": "string", + "format": "uuid" + }, + "parent": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "element", + "parent" + ], + "additionalProperties": false + } + }, + "required": [ + "Reparent" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Reidentify": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string", + "format": "uuid" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "Reidentify" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Rename": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "Rename" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Describe": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "Describe" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SetAlias": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "SetAlias" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Delete": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "Delete" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "AddStep": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "string", + "format": "uuid" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "AddStep" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RemoveStep": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "integer", + "minimum": 0 + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "RemoveStep" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "MoveStep": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "integer", + "minimum": 0 + }, + { + "type": "integer", + "minimum": 0 + } + ], + "minItems": 3, + "maxItems": 3 + } + }, + "required": [ + "MoveStep" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SetSteps": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "SetSteps" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Layout": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "Layout" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CreateNode": { + "type": "object", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "node": { + "$ref": "#/components/schemas/NodePlacement" + } + }, + "required": [ + "diagram", + "node" + ], + "additionalProperties": false + } + }, + "required": [ + "CreateNode" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditNode": { + "type": "object", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "original": { + "type": "string", + "format": "uuid" + }, + "node": { + "$ref": "#/components/schemas/NodePlacement" + } + }, + "required": [ + "diagram", + "original", + "node" + ], + "additionalProperties": false + } + }, + "required": [ + "EditNode" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RelocateNode": { + "type": "object", + "description": "Move an existing placement to another diagram while preserving its UUID, alias, geometry, collapse state and dependent artifact references. The target diagram must accept the element and must not already contain its placement.", + "properties": { + "node": { + "type": "string", + "format": "uuid" + }, + "diagram": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "node", + "diagram" + ], + "additionalProperties": false + } + }, + "required": [ + "RelocateNode" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "MoveNode": { + "type": "object", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "node": { + "type": "string", + "format": "uuid" + }, + "position": { + "$ref": "#/components/schemas/Position" + }, + "with_descendants": { + "type": "boolean" + } + }, + "required": [ + "diagram", + "node", + "position", + "with_descendants" + ], + "additionalProperties": false + } + }, + "required": [ + "MoveNode" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SetEdgeRoute": { + "type": "object", + "description": "Upsert by edge identity; preserves unrelated route entries and order. Uses existing transaction validation and ETag checks.", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "route": { + "$ref": "#/components/schemas/EdgeRoute" + } + }, + "required": [ + "diagram", + "route" + ], + "additionalProperties": false + } + }, + "required": [ + "SetEdgeRoute" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RemoveEdgeRoute": { + "type": "object", + "description": "Reset to automatic routing. No-op if this existing diagram has no route for the existing edge.", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "edge": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "diagram", + "edge" + ], + "additionalProperties": false + } + }, + "required": [ + "RemoveEdgeRoute" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SetPortAnchor": { + "type": "object", + "description": "Upsert manual port position by port identity on an existing owner placement, preserving unrelated entries. Port must be a direct child of the placement's semantic element.", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "node": { + "type": "string", + "format": "uuid" + }, + "anchor": { + "$ref": "#/components/schemas/PortAnchor" + } + }, + "required": [ + "diagram", + "node", + "anchor" + ], + "additionalProperties": false + } + }, + "required": [ + "SetPortAnchor" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RemovePortAnchor": { + "type": "object", + "description": "Restore automatic positioning for an existing direct child Port. No-op when that valid owner placement has no manual anchor for the port; wrong or stale ownership is rejected.", + "properties": { + "diagram": { + "type": "string", + "format": "uuid" + }, + "node": { + "type": "string", + "format": "uuid" + }, + "port": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "diagram", + "node", + "port" + ], + "additionalProperties": false + } + }, + "required": [ + "RemovePortAnchor" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CreateElement": { + "$ref": "#/components/schemas/Element" + } + }, + "required": [ + "CreateElement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditElement": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "$ref": "#/components/schemas/Element" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "EditElement" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CreateRelation": { + "$ref": "#/components/schemas/Relation" + } + }, + "required": [ + "CreateRelation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditRelation": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "$ref": "#/components/schemas/Relation" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "EditRelation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CreateDiagram": { + "$ref": "#/components/schemas/Diagram" + } + }, + "required": [ + "CreateDiagram" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditDiagram": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "$ref": "#/components/schemas/Diagram" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "EditDiagram" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CreateArtifact": { + "$ref": "#/components/schemas/ArtifactLink" + } + }, + "required": [ + "CreateArtifact" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditArtifact": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "$ref": "#/components/schemas/ArtifactLink" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "EditArtifact" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CreateTrace": { + "$ref": "#/components/schemas/Trace" + } + }, + "required": [ + "CreateTrace" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "EditTrace": { + "type": "array", + "prefixItems": [ + { + "type": "string", + "format": "uuid" + }, + { + "$ref": "#/components/schemas/Trace" + } + ], + "minItems": 2, + "maxItems": 2 + } + }, + "required": [ + "EditTrace" + ], + "additionalProperties": false + } + ] + }, + "TransactionRequest": { + "type": "object", + "properties": { + "commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + }, + "minItems": 1, + "maxItems": 200 + }, + "dry_run": { + "type": "boolean", + "default": false + } + }, + "required": [ + "commands" + ], + "additionalProperties": false + }, + "TransactionResult": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "dry_run": { + "type": "boolean" + }, + "changed": { + "type": "boolean" + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Issue" + } + }, + "model": { + "$ref": "#/components/schemas/Project", + "description": "Proposed model, present only for a dry run." + }, + "source_project": { + "$ref": "#/components/schemas/SourceBundle", + "description": "Proposed accepted bundle, present only for a dry run. Source writeback is validated before returning a successful candidate." + } + }, + "required": [ + "revision", + "dry_run", + "changed", + "issues" + ], + "additionalProperties": false + }, + "MutationResult": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "changed": { + "type": "boolean" + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Issue" + } + }, + "migration": { + "anyOf": [ + { + "$ref": "#/components/schemas/MigrationReport" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "changed" + ], + "additionalProperties": false + }, + "ValidationRequest": { + "oneOf": [ + { + "type": "object", + "properties": { + "format": { + "const": "json" + }, + "model": { + "$ref": "#/components/schemas/Project" + } + }, + "required": [ + "format", + "model" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "format": { + "const": "arcadia" + }, + "source": { + "type": "string" + } + }, + "required": [ + "format", + "source" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "format": { + "const": "source-project" + }, + "source_project": { + "$ref": "#/components/schemas/SourceBundle" + } + }, + "required": [ + "format", + "source_project" + ], + "additionalProperties": false + } + ] + }, + "ValidationResult": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "valid": { + "type": "boolean" + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Issue" + } + }, + "migration": { + "anyOf": [ + { + "$ref": "#/components/schemas/MigrationReport" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "valid", + "issues" + ], + "additionalProperties": false + }, + "NeighborhoodRequest": { + "type": "object", + "properties": { + "roots": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "minItems": 1, + "maxItems": 100 + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing", + "both" + ], + "default": "both" + }, + "relation_kinds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RelationKind" + } + }, + "max_depth": { + "type": "integer", + "minimum": 0, + "maximum": 4, + "default": 1 + }, + "max_objects": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 200 + }, + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + } + }, + "required": [ + "roots" + ], + "additionalProperties": false + }, + "NeighborhoodResult": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "elements": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Element" + } + }, + "relations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relation" + } + }, + "truncated": { + "type": "boolean" + } + }, + "required": [ + "revision", + "elements", + "relations", + "truncated" + ], + "additionalProperties": false + }, + "ElementPage": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Element" + } + }, + "next_after": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "items", + "next_after" + ], + "additionalProperties": false + }, + "RelationPage": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Relation" + } + }, + "next_after": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "items", + "next_after" + ], + "additionalProperties": false + }, + "ArtifactPage": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ArtifactLink" + } + }, + "next_after": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "items", + "next_after" + ], + "additionalProperties": false + }, + "TracePage": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Trace" + } + }, + "next_after": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "items", + "next_after" + ], + "additionalProperties": false + }, + "ProjectPage": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProjectSummary" + } + }, + "next_after": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "items", + "next_after" + ], + "additionalProperties": false + }, + "TransitionDirection": { + "type": "string", + "enum": [ + "OaToSa", + "SaToOa" + ] + }, + "TransitionField": { + "type": "string", + "enum": [ + "Name", + "Description" + ] + }, + "TransitionMapping": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "target": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + }, + "target_kind": { + "anyOf": [ + { + "$ref": "#/components/schemas/Kind" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "source", + "target", + "target_kind" + ], + "additionalProperties": false + }, + "FieldDecision": { + "oneOf": [ + { + "type": "string", + "enum": [ + "TakeSource", + "KeepTarget" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "string" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "FieldResolution": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "field": { + "$ref": "#/components/schemas/TransitionField" + }, + "decision": { + "$ref": "#/components/schemas/FieldDecision" + } + }, + "required": [ + "source", + "field", + "decision" + ], + "additionalProperties": false + }, + "TransitionRequest": { + "type": "object", + "properties": { + "direction": { + "$ref": "#/components/schemas/TransitionDirection" + }, + "sources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "maxItems": 100 + }, + "scope": { + "type": "string", + "enum": [ + "SelectedOnly", + "Required", + "Subtree" + ] + }, + "mappings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransitionMapping" + }, + "maxItems": 200 + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransitionField" + } + }, + "resolutions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FieldResolution" + }, + "maxItems": 400 + }, + "destination_diagram": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + }, + "communication_endpoints": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CommunicationEndpointDecision" + }, + "maxItems": 200, + "default": [], + "description": "Explicit SA component-port pair per selected OA CommunicationMean. Reuse or Create only. Owners follow reviewed participant mappings; no participant-to-port substitution or automatic interface allocation." + }, + "operation_omissions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OperationOmission" + }, + "maxItems": 200, + "default": [], + "description": "Explicit SA-to-OA abstraction per selected invocation with operation intent. Nonempty rationale required by core; duplicates, irrelevant sources, and other directions are refused. Replies derive the Call abstraction. Source operation identities and values are preserved." + } + }, + "required": [ + "direction", + "sources", + "scope", + "mappings", + "fields", + "resolutions", + "destination_diagram" + ], + "additionalProperties": false + }, + "FieldChange": { + "type": "object", + "properties": { + "field": { + "$ref": "#/components/schemas/TransitionField" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + } + }, + "required": [ + "field", + "before", + "after" + ], + "additionalProperties": false + }, + "TransitionChange": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "target": { + "$ref": "#/components/schemas/Id" + }, + "action": { + "type": "string", + "enum": [ + "Create", + "Update", + "Reuse" + ] + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FieldChange" + } + } + }, + "required": [ + "source", + "target", + "action", + "fields" + ], + "additionalProperties": false + }, + "TransitionConflict": { + "type": "object", + "properties": { + "source": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + }, + "target": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + }, + "field": { + "anyOf": [ + { + "$ref": "#/components/schemas/TransitionField" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "source", + "target", + "field", + "message" + ], + "additionalProperties": false + }, + "TransitionPlan": { + "type": "object", + "properties": { + "base_fingerprint": { + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/TransitionRequest" + }, + "changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransitionChange" + } + }, + "conflicts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TransitionConflict" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + }, + "commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + } + } + }, + "required": [ + "base_fingerprint", + "request", + "changes", + "conflicts", + "warnings", + "commands" + ], + "additionalProperties": false + }, + "TransitionEnvelope": { + "type": "object", + "properties": { + "base_revision": { + "$ref": "#/components/schemas/Revision" + }, + "plan": { + "$ref": "#/components/schemas/TransitionPlan" + } + }, + "required": [ + "base_revision", + "plan" + ], + "additionalProperties": false + }, + "ControlKind": { + "type": "string", + "enum": [ + "Choice", + "Merge", + "Fork", + "Join", + "Loop" + ] + }, + "ScenarioViewpoint": { + "type": "string", + "enum": [ + "OperationalActivity", + "ParticipantInteraction", + "Functional", + "BoundaryInterface", + "MixedLegacy" + ] + }, + "MessageKind": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Asynchronous", + "Call", + "Reply", + "Create", + "Delete" + ] + }, + { + "type": "object", + "properties": { + "Timer": { + "type": "object", + "properties": { + "expression": { + "type": "string" + } + }, + "required": [ + "expression" + ], + "additionalProperties": false + } + }, + "required": [ + "Timer" + ], + "additionalProperties": false + } + ] + }, + "MessageValue": { + "type": "object", + "properties": { + "path": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "default": [] + }, + "field": { + "type": "string", + "format": "uuid" + }, + "expression": { + "type": "string" + } + }, + "required": [ + "field", + "expression" + ], + "additionalProperties": false, + "description": "Identity is (path, field). Path lists enclosing DataField UUIDs, excluding the terminal field. Value storage traverses named Records; Reference storage stops traversal. Operation Call accepts In/InOut and Reply accepts Out/InOut/Return. Unspecified direction and retained ambiguous legacy empty paths warn. Expressions are descriptive, never executed." + }, + "LifelineBinding": { + "type": "object", + "properties": { + "referenced": { + "type": "string", + "format": "uuid" + }, + "local": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "referenced", + "local" + ], + "additionalProperties": false + }, + "StateKind": { + "type": "string", + "enum": [ + "State", + "Mode", + "Initial", + "Final", + "Choice", + "Junction", + "Fork", + "Join", + "ShallowHistory", + "DeepHistory", + "EntryPoint", + "ExitPoint", + "Terminate" + ] + }, + "TimeEventKind": { + "type": "string", + "enum": [ + "After", + "At" + ], + "description": "After is relative to source-state activation; At describes an absolute instant. Expressions are descriptive, not executed." + }, + "TransitionKind": { + "type": "string", + "enum": [ + "External", + "Local", + "Internal" + ] + }, + "EventBody": { + "oneOf": [ + { + "type": "object", + "properties": { + "Signal": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "uniqueItems": true, + "description": "Same-layer ExchangeItem identities carried by the signal." + } + }, + "required": [ + "items" + ], + "additionalProperties": false + } + }, + "required": [ + "Signal" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Time": { + "type": "object", + "properties": { + "kind": { + "$ref": "#/components/schemas/TimeEventKind" + }, + "expression": { + "type": "string" + } + }, + "required": [ + "kind", + "expression" + ], + "additionalProperties": false + } + }, + "required": [ + "Time" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Change": { + "type": "object", + "properties": { + "expression": { + "type": "string" + } + }, + "required": [ + "expression" + ], + "additionalProperties": false + } + }, + "required": [ + "Change" + ], + "additionalProperties": false + } + ] + }, + "PortContract": { + "oneOf": [ + { + "type": "object", + "properties": { + "FunctionInput": { + "type": "object", + "properties": { + "incoming_items": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "incoming_items" + ], + "additionalProperties": false + } + }, + "required": [ + "FunctionInput" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "FunctionOutput": { + "type": "object", + "properties": { + "outgoing_items": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "outgoing_items" + ], + "additionalProperties": false + } + }, + "required": [ + "FunctionOutput" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Component": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "enum": [ + "Standard", + "Flow" + ] + }, + "orientation": { + "type": "string", + "enum": [ + "Unset", + "In", + "Out", + "InOut" + ] + } + }, + "required": [ + "kind", + "orientation" + ], + "additionalProperties": false + } + }, + "required": [ + "Component" + ], + "additionalProperties": false + }, + { + "type": "string", + "enum": [ + "Legacy" + ] + } + ], + "description": "FunctionInput/Output are SA Function-owned item contracts. Component is SA System/SystemActor-owned; Standard requires Unset. Legacy requires a preserved legacy record and emits compatibility warnings. Mixed Standard/Flow component connections are invalid." + }, + "LegacyPortContract": { + "type": "object", + "properties": { + "direction": { + "type": "string", + "enum": [ + "In", + "Out", + "InOut" + ] + }, + "interface": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "direction", + "interface" + ], + "additionalProperties": false + }, + "MigrationReport": { + "type": "object", + "properties": { + "from_schema": { + "type": "integer", + "enum": [ + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + "to_schema": { + "type": "integer", + "const": 7 + }, + "preserved_ports": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string" + }, + "required_resolution": { + "type": "string" + } + }, + "required": [ + "id", + "alias", + "required_resolution" + ], + "additionalProperties": false + } + }, + "generated_endpoints": { + "type": "array", + "items": { + "type": "object", + "properties": { + "exchange": { + "type": "string", + "format": "uuid" + }, + "side": { + "type": "string", + "enum": [ + "source", + "target" + ] + }, + "port": { + "type": "string", + "format": "uuid" + }, + "owner": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string" + } + }, + "required": [ + "exchange", + "side", + "port", + "owner", + "alias" + ], + "additionalProperties": false + } + }, + "orientation_defaults": { + "type": "array", + "items": { + "type": "object", + "properties": { + "exchange": { + "type": "string", + "format": "uuid" + }, + "oriented": { + "type": "boolean", + "const": true + } + }, + "required": [ + "exchange", + "oriented" + ], + "additionalProperties": false + } + }, + "required_resolutions": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "semantic_defaults": { + "type": "array", + "items": { + "type": "object", + "properties": { + "object": { + "type": "string", + "format": "uuid" + }, + "field": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "object", + "field", + "description" + ], + "additionalProperties": false + } + }, + "generated_interface_allocations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "source": { + "type": "string", + "format": "uuid" + }, + "target": { + "type": "string", + "format": "uuid" + }, + "alias": { + "type": "string" + }, + "order": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "required": [ + "id", + "source", + "target", + "alias", + "order" + ], + "additionalProperties": false + } + }, + "unresolved_message_values": { + "type": "array", + "items": { + "type": "object", + "properties": { + "occurrence": { + "type": "string", + "format": "uuid" + }, + "field": { + "type": "string", + "format": "uuid" + }, + "reason": { + "type": "string" + } + }, + "required": [ + "occurrence", + "field", + "reason" + ], + "additionalProperties": false + } + } + }, + "required": [ + "from_schema", + "to_schema", + "preserved_ports", + "generated_endpoints", + "orientation_defaults", + "required_resolutions" + ], + "additionalProperties": false, + "description": "Transient observations from explicit V1/V2/V3/V4/V5/V6 to V7 migration. New report lists default to empty when reading older responses. Reading an old envelope neither saves nor changes its revision. Allocation UUIDv5 uses project namespace and v2-interface-item::." + }, + "SourceBundle": { + "type": "object", + "description": "Complete source7 workspace (source3/4/5/6 migrate explicitly). The entry is a root filename. Files use canonical portable slash-separated relative .arcadia paths and all must be reachable. Includes resolve only against this map, never the service filesystem. Up to 256 files, 32 include edges along a path and 1,024 include edges overall. Serialized bundle JSON is at most 8 MiB, checked again after missing UUIDs are inserted. GET returns exact accepted stable text.", + "properties": { + "entry": { + "type": "string" + }, + "files": { + "type": "object", + "maxProperties": 256, + "additionalProperties": { + "type": "string" + } + } + }, + "required": [ + "entry", + "files" + ], + "additionalProperties": false + }, + "SourceSpan": { + "type": "object", + "description": "Half-open UTF-8 byte offsets [start,end) in the named source file.", + "properties": { + "start": { + "type": "integer", + "minimum": 0 + }, + "end": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "start", + "end" + ], + "additionalProperties": false + }, + "SourceLocation": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "span": { + "$ref": "#/components/schemas/SourceSpan" + } + }, + "required": [ + "path", + "span" + ], + "additionalProperties": false + }, + "SourceProjectMutationResult": { + "type": "object", + "properties": { + "revision": { + "type": "integer", + "minimum": 1, + "description": "Current revision counter; use ETag as the opaque write validator." + }, + "changed": { + "type": "boolean" + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Issue" + } + }, + "source_project": { + "$ref": "#/components/schemas/SourceBundle" + }, + "migration": { + "anyOf": [ + { + "$ref": "#/components/schemas/MigrationReport" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "revision", + "changed", + "issues", + "source_project", + "migration" + ], + "additionalProperties": false + }, + "ExchangeItemKind": { + "type": "string", + "enum": [ + "Information", + "Material", + "Energy" + ] + }, + "ExchangeMechanism": { + "type": "string", + "enum": [ + "Unspecified", + "Flow", + "Event", + "Operation", + "SharedData" + ] + }, + "ParameterDirection": { + "type": "string", + "enum": [ + "Unspecified", + "In", + "Out", + "InOut", + "Return" + ] + }, + "FieldStorage": { + "type": "string", + "enum": [ + "Value", + "Reference" + ] + }, + "TransmissionProtocol": { + "type": "string", + "enum": [ + "Unspecified", + "Synchronous", + "Asynchronous", + "Unicast", + "Multicast", + "Broadcast" + ] + }, + "AcquisitionProtocol": { + "type": "string", + "enum": [ + "Unspecified", + "Read", + "Accept" + ] + }, + "DataValue": { + "oneOf": [ + { + "type": "object", + "properties": { + "Boolean": { + "type": "boolean" + } + }, + "required": [ + "Boolean" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Integer": { + "type": "integer", + "format": "int64", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + } + }, + "required": [ + "Integer" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Real": { + "type": "number", + "format": "double", + "description": "Finite IEEE-754 binary64; no coercion from typed Integer values." + } + }, + "required": [ + "Real" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "String": { + "type": "string" + } + }, + "required": [ + "String" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Enumeration": { + "type": "string" + } + }, + "required": [ + "Enumeration" + ], + "additionalProperties": false + } + ] + }, + "DataConstructor": { + "type": "string", + "enum": [ + "Boolean", + "Integer", + "Real", + "String", + "Enumeration", + "Record" + ] + }, + "PortConstructor": { + "type": "string", + "enum": [ + "FunctionInput", + "FunctionOutput", + "Component", + "Legacy" + ] + }, + "OccurrenceConstructor": { + "type": "string", + "enum": [ + "Exchange", + "Action", + "Control", + "SequenceReference", + "ScenarioUse", + "State", + "Fragment", + "Branch" + ] + }, + "EventConstructor": { + "type": "string", + "enum": [ + "Signal", + "Time", + "Change" + ] + }, + "MessageConstructor": { + "type": "string", + "enum": [ + "Asynchronous", + "Call", + "Reply", + "Create", + "Delete", + "Timer" + ] + }, + "ReferenceCollection": { + "type": "string", + "enum": [ + "Steps", + "FragmentBranches", + "FunctionInputItems", + "FunctionOutputItems", + "SignalItems", + "ExchangeItems", + "CategoryExchanges", + "TransitionTriggers", + "TransitionEffects", + "Constrained" + ] + }, + "OrderedCollection": { + "type": "string", + "enum": [ + "Steps", + "FragmentBranches" + ] + }, + "IncidentEnd": { + "type": "string", + "enum": [ + "Source", + "Target" + ] + }, + "ArtifactFacet": { + "type": "string", + "enum": [ + "Presence", + "Name", + "Description", + "Owner", + "Relation", + "Uri", + "Revision", + "Fragment" + ] + }, + "ComponentPortKind": { + "type": "string", + "enum": [ + "Standard", + "Flow" + ] + }, + "ComponentPortOrientation": { + "type": "string", + "enum": [ + "Unset", + "In", + "Out", + "InOut" + ] + }, + "PortDirection": { + "type": "string", + "enum": [ + "In", + "Out", + "InOut" + ] + }, + "FragmentOperator": { + "type": "string", + "enum": [ + "Alternative", + "Parallel", + "Loop", + "Optional" + ] + }, + "StateActionKind": { + "type": "string", + "enum": [ + "Entry", + "Do", + "Exit" + ] + }, + "ArtifactRelation": { + "type": "string", + "enum": [ + "References", + "Satisfies", + "Verifies", + "Implements", + "DerivedFrom" + ] + }, + "HeaderFacet": { + "type": "string", + "enum": [ + "Name", + "Description", + "Parent" + ] + }, + "ReviewedString": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "string" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "string" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionId": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "HeaderReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Name": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Name" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Description": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Description" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Parent": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Parent" + ], + "additionalProperties": false + } + ] + }, + "ExchangeItemFacet": { + "type": "string", + "enum": [ + "Nature", + "Mechanism", + "Supertype" + ] + }, + "ReviewedExchangeItemKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ExchangeItemKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ExchangeItemKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedExchangeMechanism": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ExchangeMechanism" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ExchangeMechanism" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ExchangeItemReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Nature": { + "$ref": "#/components/schemas/ReviewedExchangeItemKind" + } + }, + "required": [ + "Nature" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Mechanism": { + "$ref": "#/components/schemas/ReviewedExchangeMechanism" + } + }, + "required": [ + "Mechanism" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Supertype": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Supertype" + ], + "additionalProperties": false + } + ] + }, + "InterfaceFacet": { + "type": "string", + "enum": [ + "Supertype" + ] + }, + "InterfaceReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Supertype": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Supertype" + ], + "additionalProperties": false + } + ] + }, + "DataFacet": { + "type": "string", + "enum": [ + "Constructor", + "IntegerMinimum", + "IntegerMaximum", + "RealMinimum", + "RealMaximum", + "StringMinimumLength", + "StringMaximumLength", + "RecordSupertype", + "Default", + "Unit" + ] + }, + "ReviewedDataConstructor": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/DataConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/DataConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptioni64": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "integer", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "integer", + "minimum": -9223372036854775808, + "maximum": 9223372036854775807 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionf64": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "Reviewedu32": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionu32": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionDataValue": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/DataValue" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/DataValue" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "DataReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Constructor": { + "$ref": "#/components/schemas/ReviewedDataConstructor" + } + }, + "required": [ + "Constructor" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "IntegerMinimum": { + "$ref": "#/components/schemas/ReviewedOptioni64" + } + }, + "required": [ + "IntegerMinimum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "IntegerMaximum": { + "$ref": "#/components/schemas/ReviewedOptioni64" + } + }, + "required": [ + "IntegerMaximum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RealMinimum": { + "$ref": "#/components/schemas/ReviewedOptionf64" + } + }, + "required": [ + "RealMinimum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RealMaximum": { + "$ref": "#/components/schemas/ReviewedOptionf64" + } + }, + "required": [ + "RealMaximum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "StringMinimumLength": { + "$ref": "#/components/schemas/Reviewedu32" + } + }, + "required": [ + "StringMinimumLength" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "StringMaximumLength": { + "$ref": "#/components/schemas/ReviewedOptionu32" + } + }, + "required": [ + "StringMaximumLength" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "RecordSupertype": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "RecordSupertype" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Default": { + "$ref": "#/components/schemas/ReviewedOptionDataValue" + } + }, + "required": [ + "Default" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Unit": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Unit" + ], + "additionalProperties": false + } + ] + }, + "FieldFacet": { + "type": "string", + "enum": [ + "Type", + "Minimum", + "Maximum", + "Unit", + "Direction", + "Storage", + "Default" + ] + }, + "ReviewedTypeRef": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TypeRef" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TypeRef" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedParameterDirection": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ParameterDirection" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ParameterDirection" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedFieldStorage": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/FieldStorage" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/FieldStorage" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionVecDataValue": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataValue" + } + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/components/schemas/DataValue" + } + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "FieldReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Type": { + "$ref": "#/components/schemas/ReviewedTypeRef" + } + }, + "required": [ + "Type" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Minimum": { + "$ref": "#/components/schemas/Reviewedu32" + } + }, + "required": [ + "Minimum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Maximum": { + "$ref": "#/components/schemas/ReviewedOptionu32" + } + }, + "required": [ + "Maximum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Unit": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Unit" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Direction": { + "$ref": "#/components/schemas/ReviewedParameterDirection" + } + }, + "required": [ + "Direction" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Storage": { + "$ref": "#/components/schemas/ReviewedFieldStorage" + } + }, + "required": [ + "Storage" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Default": { + "$ref": "#/components/schemas/ReviewedOptionVecDataValue" + } + }, + "required": [ + "Default" + ], + "additionalProperties": false + } + ] + }, + "PortFacet": { + "type": "string", + "enum": [ + "Constructor", + "ComponentKind", + "Orientation", + "LegacyDirection", + "LegacyInterface" + ] + }, + "ReviewedPortConstructor": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/PortConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/PortConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedComponentPortKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ComponentPortKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ComponentPortKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedComponentPortOrientation": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ComponentPortOrientation" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ComponentPortOrientation" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedPortDirection": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/PortDirection" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/PortDirection" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "PortReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Constructor": { + "$ref": "#/components/schemas/ReviewedPortConstructor" + } + }, + "required": [ + "Constructor" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ComponentKind": { + "$ref": "#/components/schemas/ReviewedComponentPortKind" + } + }, + "required": [ + "ComponentKind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Orientation": { + "$ref": "#/components/schemas/ReviewedComponentPortOrientation" + } + }, + "required": [ + "Orientation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "LegacyDirection": { + "$ref": "#/components/schemas/ReviewedPortDirection" + } + }, + "required": [ + "LegacyDirection" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "LegacyInterface": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "LegacyInterface" + ], + "additionalProperties": false + } + ] + }, + "ScenarioFacet": { + "type": "string", + "enum": [ + "Viewpoint", + "Context", + "Precondition", + "Postcondition" + ] + }, + "ReviewedScenarioViewpoint": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ScenarioViewpoint" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ScenarioViewpoint" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ScenarioReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Viewpoint": { + "$ref": "#/components/schemas/ReviewedScenarioViewpoint" + } + }, + "required": [ + "Viewpoint" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Context": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Context" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Precondition": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Precondition" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Postcondition": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Postcondition" + ], + "additionalProperties": false + } + ] + }, + "LifelineFacet": { + "type": "string", + "enum": [ + "Represented" + ] + }, + "LifelineReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Represented": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Represented" + ], + "additionalProperties": false + } + ] + }, + "OccurrenceFacet": { + "type": "string", + "enum": [ + "Constructor", + "Precondition", + "Postcondition", + "Exchange", + "Sender", + "Receiver", + "MessageKind", + "TimerExpression", + "ReplyTo", + "Operation", + "Action", + "ActionLifeline", + "ControlKind", + "Sequence", + "Scenario", + "State", + "StateLifeline", + "FragmentOperator", + "BranchGuard" + ] + }, + "ReviewedOccurrenceConstructor": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/OccurrenceConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/OccurrenceConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedMessageConstructor": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/MessageConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/MessageConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedControlKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ControlKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ControlKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedFragmentOperator": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/FragmentOperator" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/FragmentOperator" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "OccurrenceReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Constructor": { + "$ref": "#/components/schemas/ReviewedOccurrenceConstructor" + } + }, + "required": [ + "Constructor" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Precondition": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Precondition" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Postcondition": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Postcondition" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Exchange": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Exchange" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Sender": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Sender" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Receiver": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Receiver" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "MessageKind": { + "$ref": "#/components/schemas/ReviewedMessageConstructor" + } + }, + "required": [ + "MessageKind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TimerExpression": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "TimerExpression" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ReplyTo": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "ReplyTo" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Operation": { + "$ref": "#/components/schemas/ReviewedOptionOperationUse" + } + }, + "required": [ + "Operation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Action": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Action" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ActionLifeline": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "ActionLifeline" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ControlKind": { + "$ref": "#/components/schemas/ReviewedControlKind" + } + }, + "required": [ + "ControlKind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Sequence": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Sequence" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Scenario": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "Scenario" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "State": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "State" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "StateLifeline": { + "$ref": "#/components/schemas/ReviewedOptionId" + } + }, + "required": [ + "StateLifeline" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "FragmentOperator": { + "$ref": "#/components/schemas/ReviewedFragmentOperator" + } + }, + "required": [ + "FragmentOperator" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "BranchGuard": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "BranchGuard" + ], + "additionalProperties": false + } + ] + }, + "EventFacet": { + "type": "string", + "enum": [ + "Constructor", + "TimeKind", + "TimeExpression", + "ChangeExpression" + ] + }, + "ReviewedEventConstructor": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/EventConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/EventConstructor" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedTimeEventKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TimeEventKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TimeEventKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "EventReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Constructor": { + "$ref": "#/components/schemas/ReviewedEventConstructor" + } + }, + "required": [ + "Constructor" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TimeKind": { + "$ref": "#/components/schemas/ReviewedTimeEventKind" + } + }, + "required": [ + "TimeKind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TimeExpression": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "TimeExpression" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ChangeExpression": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "ChangeExpression" + ], + "additionalProperties": false + } + ] + }, + "StateFacet": { + "type": "string", + "enum": [ + "Kind", + "Entry", + "Exit", + "DoActivity" + ] + }, + "ReviewedStateKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/StateKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/StateKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "StateReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Kind": { + "$ref": "#/components/schemas/ReviewedStateKind" + } + }, + "required": [ + "Kind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Entry": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Entry" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Exit": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Exit" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "DoActivity": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "DoActivity" + ], + "additionalProperties": false + } + ] + }, + "ConstraintFacet": { + "type": "string", + "enum": [ + "Expression" + ] + }, + "ConstraintReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Expression": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Expression" + ], + "additionalProperties": false + } + ] + }, + "RelationFacet": { + "type": "string", + "enum": [ + "Source", + "Target", + "AllocationOrder", + "Transmission", + "Acquisition", + "SourceRole", + "TargetRole", + "SourceMinimum", + "SourceMaximum", + "TargetMinimum", + "TargetMaximum", + "SourceNavigable", + "TargetNavigable", + "SourcePort", + "TargetPort", + "Oriented", + "ExtendCondition", + "SequenceGuard", + "TransitionKind", + "Trigger", + "Guard", + "Effect", + "StateActionPhase", + "ContextOwner", + "ContextSource", + "ContextTarget", + "ContextPurpose" + ] + }, + "ReviewedId": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedTransmissionProtocol": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TransmissionProtocol" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TransmissionProtocol" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedAcquisitionProtocol": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/AcquisitionProtocol" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/AcquisitionProtocol" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "Reviewedbool": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "boolean" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "boolean" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedTransitionKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TransitionKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/TransitionKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedStateActionKind": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/StateActionKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/StateActionKind" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "RelationReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Source": { + "$ref": "#/components/schemas/ReviewedId" + } + }, + "required": [ + "Source" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Target": { + "$ref": "#/components/schemas/ReviewedId" + } + }, + "required": [ + "Target" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "AllocationOrder": { + "$ref": "#/components/schemas/Reviewedu32" + } + }, + "required": [ + "AllocationOrder" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Transmission": { + "$ref": "#/components/schemas/ReviewedTransmissionProtocol" + } + }, + "required": [ + "Transmission" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Acquisition": { + "$ref": "#/components/schemas/ReviewedAcquisitionProtocol" + } + }, + "required": [ + "Acquisition" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SourceRole": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "SourceRole" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TargetRole": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "TargetRole" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SourceMinimum": { + "$ref": "#/components/schemas/Reviewedu32" + } + }, + "required": [ + "SourceMinimum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SourceMaximum": { + "$ref": "#/components/schemas/ReviewedOptionu32" + } + }, + "required": [ + "SourceMaximum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TargetMinimum": { + "$ref": "#/components/schemas/Reviewedu32" + } + }, + "required": [ + "TargetMinimum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TargetMaximum": { + "$ref": "#/components/schemas/ReviewedOptionu32" + } + }, + "required": [ + "TargetMaximum" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SourceNavigable": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "SourceNavigable" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TargetNavigable": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "TargetNavigable" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SourcePort": { + "$ref": "#/components/schemas/ReviewedId" + } + }, + "required": [ + "SourcePort" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TargetPort": { + "$ref": "#/components/schemas/ReviewedId" + } + }, + "required": [ + "TargetPort" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Oriented": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "Oriented" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ExtendCondition": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "ExtendCondition" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "SequenceGuard": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "SequenceGuard" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "TransitionKind": { + "$ref": "#/components/schemas/ReviewedTransitionKind" + } + }, + "required": [ + "TransitionKind" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Trigger": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Trigger" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Guard": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Guard" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Effect": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Effect" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "StateActionPhase": { + "$ref": "#/components/schemas/ReviewedStateActionKind" + } + }, + "required": [ + "StateActionPhase" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ContextOwner": { + "$ref": "#/components/schemas/ReviewedId" + } + }, + "required": [ + "ContextOwner" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ContextSource": { + "$ref": "#/components/schemas/ReviewedOccurrenceEndpoint" + } + }, + "required": [ + "ContextSource" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ContextTarget": { + "$ref": "#/components/schemas/ReviewedOccurrenceEndpoint" + } + }, + "required": [ + "ContextTarget" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ContextPurpose": { + "$ref": "#/components/schemas/ReviewedContextualLinkPurpose" + } + }, + "required": [ + "ContextPurpose" + ], + "additionalProperties": false + } + ] + }, + "AnnotationKey": { + "type": "object", + "properties": { + "path": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + }, + "field": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "path", + "field" + ], + "additionalProperties": false + }, + "KeyPairId": { + "type": "object", + "properties": { + "oa": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + }, + "sa": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + } + }, + "required": [], + "additionalProperties": false + }, + "KeyPairString": { + "type": "object", + "properties": { + "oa": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "sa": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [], + "additionalProperties": false + }, + "KeyPairAnnotationKey": { + "type": "object", + "properties": { + "oa": { + "anyOf": [ + { + "$ref": "#/components/schemas/AnnotationKey" + }, + { + "type": "null" + } + ] + }, + "sa": { + "anyOf": [ + { + "$ref": "#/components/schemas/AnnotationKey" + }, + { + "type": "null" + } + ] + } + }, + "required": [], + "additionalProperties": false + }, + "ReviewedArtifactRelation": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ArtifactRelation" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ArtifactRelation" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionString": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ArtifactReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Presence": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "Presence" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Name": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Name" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Description": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Description" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Owner": { + "$ref": "#/components/schemas/ReviewedId" + } + }, + "required": [ + "Owner" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Relation": { + "$ref": "#/components/schemas/ReviewedArtifactRelation" + } + }, + "required": [ + "Relation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Uri": { + "$ref": "#/components/schemas/ReviewedString" + } + }, + "required": [ + "Uri" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Revision": { + "$ref": "#/components/schemas/ReviewedOptionString" + } + }, + "required": [ + "Revision" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Fragment": { + "$ref": "#/components/schemas/ReviewedOptionString" + } + }, + "required": [ + "Fragment" + ], + "additionalProperties": false + } + ] + }, + "ReviewedVecId": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionMessageValue": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/MessageValue" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/MessageValue" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOptionLifelineBinding": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/LifelineBinding" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/LifelineBinding" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "FacetSelector": { + "oneOf": [ + { + "type": "object", + "properties": { + "Header": { + "$ref": "#/components/schemas/HeaderFacet" + } + }, + "required": [ + "Header" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ExchangeItem": { + "$ref": "#/components/schemas/ExchangeItemFacet" + } + }, + "required": [ + "ExchangeItem" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Category": { + "$ref": "#/components/schemas/CategoryFacet" + } + }, + "required": [ + "Category" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Interface": { + "$ref": "#/components/schemas/InterfaceFacet" + } + }, + "required": [ + "Interface" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Data": { + "$ref": "#/components/schemas/DataFacet" + } + }, + "required": [ + "Data" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Field": { + "$ref": "#/components/schemas/FieldFacet" + } + }, + "required": [ + "Field" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Port": { + "$ref": "#/components/schemas/PortFacet" + } + }, + "required": [ + "Port" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Scenario": { + "$ref": "#/components/schemas/ScenarioFacet" + } + }, + "required": [ + "Scenario" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Lifeline": { + "$ref": "#/components/schemas/LifelineFacet" + } + }, + "required": [ + "Lifeline" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Occurrence": { + "$ref": "#/components/schemas/OccurrenceFacet" + } + }, + "required": [ + "Occurrence" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Event": { + "$ref": "#/components/schemas/EventFacet" + } + }, + "required": [ + "Event" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "State": { + "$ref": "#/components/schemas/StateFacet" + } + }, + "required": [ + "State" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Constraint": { + "$ref": "#/components/schemas/ConstraintFacet" + } + }, + "required": [ + "Constraint" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Relation": { + "$ref": "#/components/schemas/RelationFacet" + } + }, + "required": [ + "Relation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Member": { + "type": "object", + "properties": { + "collection": { + "$ref": "#/components/schemas/ReferenceCollection" + }, + "keys": { + "$ref": "#/components/schemas/KeyPairId" + } + }, + "required": [ + "collection", + "keys" + ], + "additionalProperties": false + } + }, + "required": [ + "Member" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Order": { + "$ref": "#/components/schemas/OrderedCollection" + } + }, + "required": [ + "Order" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Literal": { + "$ref": "#/components/schemas/KeyPairString" + } + }, + "required": [ + "Literal" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Annotation": { + "$ref": "#/components/schemas/KeyPairAnnotationKey" + } + }, + "required": [ + "Annotation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Binding": { + "$ref": "#/components/schemas/KeyPairId" + } + }, + "required": [ + "Binding" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Child": { + "$ref": "#/components/schemas/KeyPairId" + } + }, + "required": [ + "Child" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Incident": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "end": { + "$ref": "#/components/schemas/IncidentEnd" + } + }, + "required": [ + "keys", + "end" + ], + "additionalProperties": false + } + }, + "required": [ + "Incident" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Artifact": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "facet": { + "$ref": "#/components/schemas/ArtifactFacet" + } + }, + "required": [ + "keys", + "facet" + ], + "additionalProperties": false + } + }, + "required": [ + "Artifact" + ], + "additionalProperties": false + } + ] + }, + "FacetReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Header": { + "$ref": "#/components/schemas/HeaderReview" + } + }, + "required": [ + "Header" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "ExchangeItem": { + "$ref": "#/components/schemas/ExchangeItemReview" + } + }, + "required": [ + "ExchangeItem" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Category": { + "$ref": "#/components/schemas/CategoryReview" + } + }, + "required": [ + "Category" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Interface": { + "$ref": "#/components/schemas/InterfaceReview" + } + }, + "required": [ + "Interface" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Data": { + "$ref": "#/components/schemas/DataReview" + } + }, + "required": [ + "Data" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Field": { + "$ref": "#/components/schemas/FieldReview" + } + }, + "required": [ + "Field" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Port": { + "$ref": "#/components/schemas/PortReview" + } + }, + "required": [ + "Port" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Scenario": { + "$ref": "#/components/schemas/ScenarioReview" + } + }, + "required": [ + "Scenario" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Lifeline": { + "$ref": "#/components/schemas/LifelineReview" + } + }, + "required": [ + "Lifeline" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Occurrence": { + "$ref": "#/components/schemas/OccurrenceReview" + } + }, + "required": [ + "Occurrence" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Event": { + "$ref": "#/components/schemas/EventReview" + } + }, + "required": [ + "Event" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "State": { + "$ref": "#/components/schemas/StateReview" + } + }, + "required": [ + "State" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Constraint": { + "$ref": "#/components/schemas/ConstraintReview" + } + }, + "required": [ + "Constraint" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Relation": { + "$ref": "#/components/schemas/RelationReview" + } + }, + "required": [ + "Relation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Member": { + "type": "object", + "properties": { + "collection": { + "$ref": "#/components/schemas/ReferenceCollection" + }, + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "review": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "collection", + "keys", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Member" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Order": { + "type": "object", + "properties": { + "collection": { + "$ref": "#/components/schemas/OrderedCollection" + }, + "review": { + "$ref": "#/components/schemas/ReviewedVecId" + } + }, + "required": [ + "collection", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Order" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Literal": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairString" + }, + "review": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "keys", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Literal" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Annotation": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairAnnotationKey" + }, + "review": { + "$ref": "#/components/schemas/ReviewedOptionMessageValue" + } + }, + "required": [ + "keys", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Annotation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Binding": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "review": { + "$ref": "#/components/schemas/ReviewedOptionLifelineBinding" + } + }, + "required": [ + "keys", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Binding" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Child": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "review": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "keys", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Child" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Incident": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "end": { + "$ref": "#/components/schemas/IncidentEnd" + }, + "review": { + "$ref": "#/components/schemas/Reviewedbool" + } + }, + "required": [ + "keys", + "end", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Incident" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Artifact": { + "type": "object", + "properties": { + "keys": { + "$ref": "#/components/schemas/KeyPairId" + }, + "review": { + "$ref": "#/components/schemas/ArtifactReview" + } + }, + "required": [ + "keys", + "review" + ], + "additionalProperties": false + } + }, + "required": [ + "Artifact" + ], + "additionalProperties": false + } + ] + }, + "SemanticBaseline": { + "type": "object", + "properties": { + "facets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetReview" + } + } + }, + "required": [ + "facets" + ], + "additionalProperties": false + }, + "Drift": { + "type": "string", + "enum": [ + "Unreviewed", + "Unchanged", + "OaChanged", + "SaChanged", + "BothChanged" + ] + }, + "FacetDrift": { + "type": "object", + "properties": { + "selector": { + "$ref": "#/components/schemas/FacetSelector" + }, + "current": { + "$ref": "#/components/schemas/FacetReview" + }, + "drift": { + "$ref": "#/components/schemas/Drift" + } + }, + "required": [ + "selector", + "current", + "drift" + ], + "additionalProperties": false + }, + "SemanticDrift": { + "type": "object", + "properties": { + "own": { + "$ref": "#/components/schemas/Drift" + }, + "linked": { + "$ref": "#/components/schemas/Drift" + }, + "own_unreviewed": { + "type": "boolean" + }, + "linked_unreviewed": { + "type": "boolean" + } + }, + "required": [ + "own", + "linked", + "own_unreviewed", + "linked_unreviewed" + ], + "additionalProperties": false + }, + "LinkedSemanticImpact": { + "type": "object", + "properties": { + "objects": { + "$ref": "#/components/schemas/KeyPairId" + }, + "traces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + }, + "drift": { + "$ref": "#/components/schemas/Drift" + }, + "unreviewed": { + "type": "boolean" + } + }, + "required": [ + "objects", + "traces", + "drift", + "unreviewed" + ], + "additionalProperties": false + }, + "SemanticRecord": { + "oneOf": [ + { + "type": "object", + "properties": { + "Element": { + "$ref": "#/components/schemas/Element" + } + }, + "required": [ + "Element" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Relation": { + "$ref": "#/components/schemas/Relation" + } + }, + "required": [ + "Relation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Artifact": { + "$ref": "#/components/schemas/ArtifactLink" + } + }, + "required": [ + "Artifact" + ], + "additionalProperties": false + } + ] + }, + "ImpactRecord": { + "oneOf": [ + { + "type": "object", + "properties": { + "Element": { + "$ref": "#/components/schemas/Element" + } + }, + "required": [ + "Element" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Relation": { + "$ref": "#/components/schemas/Relation" + } + }, + "required": [ + "Relation" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Artifact": { + "$ref": "#/components/schemas/ArtifactLink" + } + }, + "required": [ + "Artifact" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Diagram": { + "$ref": "#/components/schemas/Diagram" + } + }, + "required": [ + "Diagram" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Trace": { + "$ref": "#/components/schemas/Trace" + } + }, + "required": [ + "Trace" + ], + "additionalProperties": false + } + ] + }, + "SemanticImpact": { + "type": "object", + "properties": { + "object": { + "$ref": "#/components/schemas/Id" + }, + "before": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImpactRecord" + }, + { + "type": "null" + } + ] + }, + "after": { + "anyOf": [ + { + "$ref": "#/components/schemas/ImpactRecord" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "object" + ], + "additionalProperties": false + }, + "SemanticDecision": { + "type": "string", + "enum": [ + "KeepTarget", + "TakeSource", + "Candidate" + ] + }, + "FacetResolution": { + "type": "object", + "properties": { + "facet": { + "$ref": "#/components/schemas/FacetSelector" + }, + "decision": { + "$ref": "#/components/schemas/SemanticDecision" + } + }, + "required": [ + "facet", + "decision" + ], + "additionalProperties": false + }, + "SemanticReviewRequest": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "resolutions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetResolution" + }, + "maxItems": 5000 + }, + "reset": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetSelector" + }, + "maxItems": 5000, + "default": [] + } + }, + "required": [ + "source", + "resolutions" + ], + "additionalProperties": false + }, + "SemanticTransitionRequest": { + "type": "object", + "properties": { + "transition": { + "$ref": "#/components/schemas/TransitionRequest" + }, + "traces": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Id" + }, + "maxProperties": 100, + "default": {} + }, + "references": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Id" + }, + "maxProperties": 5000, + "default": {} + }, + "reviews": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SemanticReviewRequest" + }, + "maxItems": 100, + "default": [] + }, + "candidates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SemanticRecord" + }, + "maxItems": 200, + "default": [] + }, + "accepted_impacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SemanticImpact" + }, + "maxItems": 200, + "default": [] + } + }, + "required": [ + "transition" + ], + "additionalProperties": false, + "description": "Canonical OA-to-SA references include historical UUIDs. Traces map selected source IDs to trace IDs. Combined resolutions/reset limit: 5,000. Candidates must equal exactly the result of their selected facets; accepted_impacts explicitly acknowledge exact induced changes." + }, + "SemanticReviewPreview": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "target": { + "$ref": "#/components/schemas/Id" + }, + "facets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetDrift" + } + }, + "acknowledged": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetSelector" + } + }, + "retired": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetSelector" + } + }, + "linked_impacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkedSemanticImpact" + } + } + }, + "required": [ + "source", + "target", + "facets", + "acknowledged", + "retired", + "linked_impacts" + ], + "additionalProperties": false + }, + "SemanticTransitionPlan": { + "type": "object", + "properties": { + "base_fingerprint": { + "type": "string" + }, + "request": { + "$ref": "#/components/schemas/SemanticTransitionRequest" + }, + "transition": { + "$ref": "#/components/schemas/TransitionPlan" + }, + "reviews": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SemanticReviewPreview" + } + }, + "impacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SemanticImpact" + } + }, + "conflicts": { + "type": "array", + "items": { + "type": "string" + } + }, + "commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + }, + "maxItems": 200 + }, + "prepared_fingerprint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "base_fingerprint", + "request", + "transition", + "reviews", + "impacts", + "conflicts", + "commands" + ], + "additionalProperties": false + }, + "SemanticTransitionEnvelope": { + "type": "object", + "properties": { + "base_revision": { + "$ref": "#/components/schemas/Revision" + }, + "plan": { + "$ref": "#/components/schemas/SemanticTransitionPlan" + } + }, + "required": [ + "base_revision", + "plan" + ], + "additionalProperties": false + }, + "SemanticQueryRequest": { + "type": "object", + "properties": { + "traces": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "maxItems": 100, + "minItems": 1, + "uniqueItems": true + }, + "references": { + "type": "object", + "propertyNames": { + "format": "uuid" + }, + "additionalProperties": { + "$ref": "#/components/schemas/Id" + }, + "maxProperties": 5000, + "default": {} + }, + "revision": { + "anyOf": [ + { + "$ref": "#/components/schemas/Revision" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "traces" + ], + "additionalProperties": false + }, + "SemanticQueryItem": { + "type": "object", + "properties": { + "trace": { + "$ref": "#/components/schemas/Id" + }, + "oa": { + "$ref": "#/components/schemas/Id" + }, + "sa": { + "$ref": "#/components/schemas/Id" + }, + "facets": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FacetDrift" + } + }, + "drift": { + "$ref": "#/components/schemas/SemanticDrift" + }, + "linked_impacts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LinkedSemanticImpact" + } + } + }, + "required": [ + "trace", + "oa", + "sa", + "facets", + "drift", + "linked_impacts" + ], + "additionalProperties": false + }, + "SemanticQueryResult": { + "type": "object", + "properties": { + "revision": { + "$ref": "#/components/schemas/Revision" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SemanticQueryItem" + }, + "maxItems": 100 + } + }, + "required": [ + "revision", + "items" + ], + "additionalProperties": false + }, + "ExchangeFamily": { + "type": "string", + "enum": [ + "Work", + "Participant" + ] + }, + "Coverage": { + "type": "string", + "enum": [ + "All", + "InBehavior", + "OutsideBehavior" + ] + }, + "ExchangeLabels": { + "type": "string", + "enum": [ + "Name", + "Items", + "NameAndItems" + ] + }, + "CategoryFacet": { + "type": "string", + "enum": [ + "Family" + ] + }, + "ReviewedExchangeFamily": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ExchangeFamily" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ExchangeFamily" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "CategoryReview": { + "oneOf": [ + { + "type": "object", + "properties": { + "Family": { + "$ref": "#/components/schemas/ReviewedExchangeFamily" + } + }, + "required": [ + "Family" + ], + "additionalProperties": false + } + ] + }, + "EdgeKind": { + "oneOf": [ + { + "type": "string", + "enum": [ + "Trace" + ] + }, + { + "type": "object", + "properties": { + "Relation": { + "$ref": "#/components/schemas/RelationKind" + } + }, + "required": [ + "Relation" + ], + "additionalProperties": false + } + ] + }, + "RelationKinds": { + "oneOf": [ + { + "type": "string", + "enum": [ + "All" + ] + }, + { + "type": "object", + "properties": { + "Only": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EdgeKind" + } + } + }, + "required": [ + "Only" + ], + "additionalProperties": false + } + ] + }, + "CategorySelection": { + "oneOf": [ + { + "type": "string", + "enum": [ + "All" + ] + }, + { + "type": "object", + "properties": { + "Any": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + }, + "required": [ + "Any" + ], + "additionalProperties": false + } + ] + }, + "ContextSelection": { + "oneOf": [ + { + "type": "string", + "enum": [ + "All" + ] + }, + { + "type": "object", + "properties": { + "Any": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + } + } + }, + "required": [ + "Any" + ], + "additionalProperties": false + } + ] + }, + "ViewOptions": { + "type": "object", + "properties": { + "hidden_edges": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "default": [] + }, + "relation_kinds": { + "$ref": "#/components/schemas/RelationKinds", + "default": "All" + }, + "work_categories": { + "$ref": "#/components/schemas/CategorySelection", + "default": "All" + }, + "participant_categories": { + "$ref": "#/components/schemas/CategorySelection", + "default": "All" + }, + "coverage": { + "$ref": "#/components/schemas/Coverage", + "default": "All" + }, + "work_contexts": { + "$ref": "#/components/schemas/ContextSelection", + "default": "All" + }, + "exchange_labels": { + "$ref": "#/components/schemas/ExchangeLabels", + "default": "Name" + } + }, + "required": [], + "additionalProperties": false, + "description": "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. Traceability permits both-layer hidden/category/context references; ordinary projections require the diagram layer. Filtering preserves unfiltered automatic port positions." + }, + "CommunicationPortChoice": { + "oneOf": [ + { + "type": "object", + "properties": { + "Reuse": { + "type": "object", + "properties": { + "port": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "port" + ], + "additionalProperties": false + } + }, + "required": [ + "Reuse" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Create": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/Id" + }, + "alias": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/ComponentPortKind" + }, + "orientation": { + "$ref": "#/components/schemas/ComponentPortOrientation" + } + }, + "required": [ + "id", + "alias", + "name", + "description", + "kind", + "orientation" + ], + "additionalProperties": false + } + }, + "required": [ + "Create" + ], + "additionalProperties": false + } + ] + }, + "CommunicationEndpointDecision": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "source_port": { + "$ref": "#/components/schemas/CommunicationPortChoice" + }, + "target_port": { + "$ref": "#/components/schemas/CommunicationPortChoice" + } + }, + "required": [ + "source", + "source_port", + "target_port" + ], + "additionalProperties": false + }, + "ReviewedOptionOperationUse": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/OperationUse" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "anyOf": [ + { + "$ref": "#/components/schemas/OperationUse" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedOccurrenceEndpoint": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/OccurrenceEndpoint" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/OccurrenceEndpoint" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "ReviewedContextualLinkPurpose": { + "type": "object", + "properties": { + "oa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ContextualLinkPurpose" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + }, + "sa": { + "oneOf": [ + { + "type": "string", + "enum": [ + "NotApplicable" + ] + }, + { + "type": "object", + "properties": { + "Value": { + "$ref": "#/components/schemas/ContextualLinkPurpose" + } + }, + "required": [ + "Value" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "oa", + "sa" + ], + "additionalProperties": false + }, + "OccurrenceEndpoint": { + "type": "object", + "properties": { + "references": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Id" + }, + "maxItems": 64 + }, + "occurrence": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "references", + "occurrence" + ], + "additionalProperties": false, + "description": "Ordered reference-occurrence IDs in an explicit caller owner, followed by the leaf contribution ID. Empty references means a direct contribution; sequence definition IDs are not path components." + }, + "ContextualLinkPurpose": { + "oneOf": [ + { + "type": "object", + "properties": { + "Precedence": { + "type": "object", + "properties": { + "guard": { + "type": "string" + } + }, + "required": [ + "guard" + ], + "additionalProperties": false + } + }, + "required": [ + "Precedence" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "Exchange": { + "type": "object", + "properties": { + "exchange": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "exchange" + ], + "additionalProperties": false + } + }, + "required": [ + "Exchange" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "CommonWork": { + "type": "object", + "properties": { + "work": { + "$ref": "#/components/schemas/Id" + } + }, + "required": [ + "work" + ], + "additionalProperties": false + } + }, + "required": [ + "CommonWork" + ], + "additionalProperties": false + } + ] + }, + "OperationUse": { + "type": "object", + "properties": { + "allocation": { + "anyOf": [ + { + "$ref": "#/components/schemas/Id" + }, + { + "type": "null" + } + ] + } + }, + "required": [], + "additionalProperties": false, + "description": "Present with absent/null allocation retains unresolved operation intent. A selected UUID identifies exactly an InterfaceItemAllocation, never an item, port, or name. Reply derives operation from its exact correlated Call." + }, + "OperationOmission": { + "type": "object", + "properties": { + "source": { + "$ref": "#/components/schemas/Id" + }, + "rationale": { + "type": "string" + } + }, + "required": [ + "source", + "rationale" + ], + "additionalProperties": false + } + } + } +}